diff options
Diffstat (limited to 'thirdparty/xatlas/xatlas.cpp')
-rw-r--r-- | thirdparty/xatlas/xatlas.cpp | 11077 |
1 files changed, 5791 insertions, 5286 deletions
diff --git a/thirdparty/xatlas/xatlas.cpp b/thirdparty/xatlas/xatlas.cpp index 2cc2905eee..c62be4e73a 100644 --- a/thirdparty/xatlas/xatlas.cpp +++ b/thirdparty/xatlas/xatlas.cpp @@ -1,128 +1,441 @@ -// This code is in the public domain -- castanyo@yahoo.es +/* +MIT License + +Copyright (c) 2018-2019 Jonathan Young + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ +/* +thekla_atlas +https://github.com/Thekla/thekla_atlas +MIT License +Copyright (c) 2013 Thekla, Inc +Copyright NVIDIA Corporation 2006 -- Ignacio Castano <icastano@nvidia.com> + +Fast-BVH +https://github.com/brandonpelfrey/Fast-BVH +MIT License +Copyright (c) 2012 Brandon Pelfrey +*/ #include <algorithm> -#include <cmath> -#include <memory> -#include <unordered_map> -#include <vector> +#include <atomic> +#include <condition_variable> +#include <mutex> +#include <thread> #include <assert.h> -#include <float.h> +#include <float.h> // FLT_MAX +#include <limits.h> #include <math.h> -#include <stdarg.h> +#define __STDC_LIMIT_MACROS #include <stdint.h> #include <stdio.h> #include <string.h> -#include <time.h> #include "xatlas.h" -#undef min -#undef max +#ifndef XA_DEBUG +#ifdef NDEBUG +#define XA_DEBUG 0 +#else +#define XA_DEBUG 1 +#endif +#endif -#ifndef xaAssert -#define xaAssert(exp) if (!(exp)) { xaPrint("%s %s %s\n", #exp, __FILE__, __LINE__); } +#ifndef XA_PROFILE +#define XA_PROFILE 0 #endif -#ifndef xaDebugAssert -#define xaDebugAssert(exp) assert(exp) +#if XA_PROFILE +#include <time.h> #endif -#ifndef xaPrint -#define xaPrint(...) if (xatlas::internal::s_print) { xatlas::internal::s_print(__VA_ARGS__); } + +#ifndef XA_MULTITHREADED +#define XA_MULTITHREADED 1 #endif -#ifdef _MSC_VER -// Ignore gcc attributes. -#define __attribute__(X) +#define XA_STR(x) #x +#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__); } #endif -#ifdef _MSC_VER -#define restrict -#define NV_FORCEINLINE __forceinline -#else -#define restrict __restrict__ -#define NV_FORCEINLINE __attribute__((always_inline)) inline +#ifndef XA_DEBUG_ASSERT +#define XA_DEBUG_ASSERT(exp) assert(exp) #endif -#define NV_UINT32_MAX 0xffffffff -#define NV_FLOAT_MAX 3.402823466e+38F +#ifndef XA_PRINT +#define XA_PRINT(...) \ + if (xatlas::internal::s_print && xatlas::internal::s_printVerbose) \ + xatlas::internal::s_print(__VA_ARGS__); +#endif -#ifndef PI -#define PI float(3.1415926535897932384626433833) +#ifndef XA_PRINT_WARNING +#define XA_PRINT_WARNING(...) \ + if (xatlas::internal::s_print) \ + xatlas::internal::s_print(__VA_ARGS__); #endif -#define NV_EPSILON (0.0001f) -#define NV_NORMAL_EPSILON (0.001f) +#define XA_ALLOC(tag, type) (type *)internal::Realloc(nullptr, sizeof(type), tag, __FILE__, __LINE__) +#define XA_ALLOC_ARRAY(tag, type, num) (type *)internal::Realloc(nullptr, sizeof(type) * num, tag, __FILE__, __LINE__) +#define XA_REALLOC(tag, ptr, type, num) (type *)internal::Realloc(ptr, sizeof(type) * num, tag, __FILE__, __LINE__) +#define XA_FREE(ptr) internal::Realloc(ptr, 0, internal::MemTag::Default, __FILE__, __LINE__) +#define XA_NEW(tag, type, ...) new (XA_ALLOC(tag, type)) type(__VA_ARGS__) + +#define XA_UNUSED(a) ((void)(a)) + +#define XA_GROW_CHARTS_COPLANAR 1 +#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_DEBUG_HEAP 0 +#define XA_DEBUG_SINGLE_CHART 0 +#define XA_DEBUG_EXPORT_ATLAS_IMAGES 0 +#define XA_DEBUG_EXPORT_OBJ_SOURCE_MESHES 0 +#define XA_DEBUG_EXPORT_OBJ_CHART_GROUPS 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_NOT_DISK 0 +#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_SOURCE_MESHES \ + || XA_DEBUG_EXPORT_OBJ_CHART_GROUPS \ + || XA_DEBUG_EXPORT_OBJ_CHARTS \ + || XA_DEBUG_EXPORT_OBJ_BEFORE_FIX_TJUNCTION \ + || XA_DEBUG_EXPORT_OBJ_CLOSE_HOLES_ERROR \ + || XA_DEBUG_EXPORT_OBJ_NOT_DISK \ + || 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_SPRINTF(_buffer, _size, _format, ...) sprintf_s(_buffer, _size, _format, __VA_ARGS__) +#else +#define XA_FOPEN(_file, _filename, _mode) _file = fopen(_filename, _mode) +#define XA_SPRINTF(_buffer, _size, _format, ...) sprintf(_buffer, _format, __VA_ARGS__) +#endif namespace xatlas { namespace internal { -static PrintFunc s_print = NULL; +static ReallocFunc s_realloc = realloc; +static PrintFunc s_print = printf; +static bool s_printVerbose = false; + +struct MemTag +{ + enum + { + Default, + Mesh, + MeshBoundaries, + MeshColocals, + MeshEdgeMap, + MeshIndices, + MeshNormals, + MeshPositions, + MeshTexcoords, + Count + }; +}; + +#if XA_DEBUG_HEAP +struct AllocHeader +{ + size_t size; + const char *file; + int line; + int tag; + AllocHeader *prev, *next; + bool free; +}; + +static std::mutex s_allocMutex; +static AllocHeader *s_allocRoot = nullptr; +static size_t s_allocTotalSize = 0, s_allocPeakSize = 0, s_allocTotalTagSize[MemTag::Count] = { 0 }, s_allocPeakTagSize[MemTag::Count] = { 0 }; +static constexpr uint32_t kAllocRedzone = 0x12345678; + +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; + uint8_t *realPtr = nullptr; + AllocHeader *header = nullptr; + if (ptr) { + realPtr = ((uint8_t *)ptr) - sizeof(AllocHeader); + header = (AllocHeader *)realPtr; + } + if (realPtr && size) { + s_allocTotalSize -= header->size; + s_allocTotalTagSize[header->tag] -= header->size; + // realloc, remove. + if (header->prev) + header->prev->next = header->next; + else + s_allocRoot = header->next; + if (header->next) + header->next->prev = header->prev; + } + if (!size) { + s_allocTotalSize -= header->size; + s_allocTotalTagSize[header->tag] -= header->size; + XA_ASSERT(!header->free); // double free + header->free = true; + return nullptr; + } + size += sizeof(AllocHeader) + sizeof(kAllocRedzone); + uint8_t *newPtr = (uint8_t *)s_realloc(realPtr, size); + if (!newPtr) + return nullptr; + header = (AllocHeader *)newPtr; + header->size = size; + header->file = file; + header->line = line; + header->tag = tag; + header->free = false; + if (!s_allocRoot) { + s_allocRoot = header; + header->prev = header->next = 0; + } else { + header->prev = nullptr; + header->next = s_allocRoot; + s_allocRoot = header; + header->next->prev = header; + } + s_allocTotalSize += size; + if (s_allocTotalSize > s_allocPeakSize) + s_allocPeakSize = s_allocTotalSize; + s_allocTotalTagSize[tag] += size; + if (s_allocTotalTagSize[tag] > s_allocPeakTagSize[tag]) + s_allocPeakTagSize[tag] = s_allocTotalTagSize[tag]; + auto redzone = (uint32_t *)(newPtr + size - sizeof(kAllocRedzone)); + *redzone = kAllocRedzone; + return newPtr + sizeof(AllocHeader); +} + +static void ReportLeaks() +{ + printf("Checking for memory leaks...\n"); + bool anyLeaks = false; + AllocHeader *header = s_allocRoot; + while (header) { + if (!header->free) { + printf(" Leak: %zu bytes %s %d\n", header->size, header->file, header->line); + anyLeaks = true; + } + auto redzone = (const uint32_t *)((const uint8_t *)header + header->size - sizeof(kAllocRedzone)); + if (*redzone != kAllocRedzone) + printf(" Redzone corrupted: %zu bytes %s %d\n", header->size, header->file, header->line); + header = header->next; + } + if (!anyLeaks) + printf(" No memory leaks\n"); + header = s_allocRoot; + while (header) { + AllocHeader *destroy = header; + header = header->next; + s_realloc(destroy, 0); + } + s_allocRoot = nullptr; + s_allocTotalSize = s_allocPeakSize = 0; + for (int i = 0; i < MemTag::Count; i++) + s_allocTotalTagSize[i] = s_allocPeakTagSize[i] = 0; +} + +static void PrintMemoryUsage() +{ + 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 + "Default", + "Mesh", + "MeshBoundaries", + "MeshColocals", + "MeshEdgeMap", + "MeshIndices", + "MeshNormals", + "MeshPositions", + "MeshTexcoords" + }; + for (int i = 0; i < MemTag::Count; i++) { + XA_PRINT(" %s: %0.2fMB current, %0.2fMB peak\n", labels[i], internal::s_allocTotalTagSize[i] / 1024.0f / 1024.0f, internal::s_allocPeakTagSize[i] / 1024.0f / 1024.0f); + } +} + +#define XA_PRINT_MEM_USAGE internal::PrintMemoryUsage(); +#else +static void *Realloc(void *ptr, size_t size, int /*tag*/, const char * /*file*/, int /*line*/) +{ + void *mem = s_realloc(ptr, size); + if (size > 0) { + XA_DEBUG_ASSERT(mem); + } + return mem; +} +#define XA_PRINT_MEM_USAGE +#endif + +#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(label, var) XA_PRINT("%s%.2f seconds (%g ms)\n", label, internal::clockToSeconds(internal::s_profile.var), internal::clockToMs(internal::s_profile.var)); + +struct ProfileData +{ + clock_t addMeshConcurrent; + std::atomic<clock_t> addMesh; + std::atomic<clock_t> addMeshCreateColocals; + std::atomic<clock_t> addMeshCreateFaceGroups; + std::atomic<clock_t> addMeshCreateBoundaries; + std::atomic<clock_t> addMeshCreateChartGroupsConcurrent; + std::atomic<clock_t> addMeshCreateChartGroups; + clock_t computeChartsConcurrent; + std::atomic<clock_t> computeCharts; + std::atomic<clock_t> atlasBuilder; + std::atomic<clock_t> atlasBuilderInit; + std::atomic<clock_t> atlasBuilderCreateInitialCharts; + std::atomic<clock_t> atlasBuilderGrowCharts; + std::atomic<clock_t> atlasBuilderMergeCharts; + std::atomic<clock_t> createChartMeshes; + std::atomic<clock_t> fixChartMeshTJunctions; + std::atomic<clock_t> closeChartMeshHoles; + clock_t parameterizeChartsConcurrent; + std::atomic<clock_t> parameterizeCharts; + std::atomic<clock_t> parameterizeChartsOrthogonal; + std::atomic<clock_t> parameterizeChartsLSCM; + std::atomic<clock_t> parameterizeChartsEvaluateQuality; + clock_t packCharts; + clock_t packChartsRasterize; + clock_t packChartsDilate; + clock_t packChartsFindLocation; + clock_t packChartsBlit; +}; + +static ProfileData s_profile; + +static double clockToMs(clock_t c) +{ + return c * 1000.0 / CLOCKS_PER_SEC; +} + +static double clockToSeconds(clock_t c) +{ + return c / (double)CLOCKS_PER_SEC; +} +#else +#define XA_PROFILE_START(var) +#define XA_PROFILE_END(var) +#define XA_PROFILE_PRINT(label, var) +#endif + +static constexpr float kPi = 3.14159265358979323846f; +static constexpr float kPi2 = 6.28318530717958647692f; +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) { return (x + a - 1) & ~(a - 1); } -static bool isAligned(int x, int a) +template <typename T> +static T max(const T &a, const T &b) { - return (x & (a - 1)) == 0; + return a > b ? a : b; +} + +template <typename T> +static T min(const T &a, const T &b) +{ + return a < b ? a : b; } -/// Return the maximum of the three arguments. template <typename T> static T max3(const T &a, const T &b, const T &c) { - return std::max(a, std::max(b, 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) { - return std::min(a, std::min(b, 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) { - return std::min(std::max(x, a), b); + return min(max(x, a), b); } -static float saturate(float f) +template <typename T> +static void swap(T &a, T &b) { - return clamp(f, 0.0f, 1.0f); + T temp; + temp = a; + a = b; + b = temp; + temp = T(); } -// Robust floating point comparisons: -// http://realtimecollisiondetection.net/blog/?p=89 -static bool equal(const float f0, const float f1, const float epsilon = NV_EPSILON) +union FloatUint32 { - //return fabs(f0-f1) <= epsilon; - return fabs(f0 - f1) <= epsilon * max3(1.0f, fabsf(f0), fabsf(f1)); -} + float f; + uint32_t u; +}; -NV_FORCEINLINE static int ftoi_floor(float val) +static bool isFinite(float f) { - return (int)val; + FloatUint32 fu; + fu.f = f; + return fu.u != 0x7F800000u && fu.u != 0x7F800001u; } -NV_FORCEINLINE static int ftoi_ceil(float val) +static bool isNan(float f) { - return (int)ceilf(val); + return f != f; } -NV_FORCEINLINE static int ftoi_round(float f) +// Robust floating point comparisons: +// http://realtimecollisiondetection.net/blog/?p=89 +static bool equal(const float f0, const float f1, const float epsilon) { - return int(floorf(f + 0.5f)); + //return fabs(f0-f1) <= epsilon; + return fabs(f0 - f1) <= epsilon * max3(1.0f, fabsf(f0), fabsf(f1)); } -static bool isZero(const float f, const float epsilon = NV_EPSILON) +static int ftoi_ceil(float val) { - return fabs(f) <= epsilon; + return (int)ceilf(val); } -static float lerp(float f0, float f1, float t) +static bool isZero(const float f, const float epsilon) { - const float s = 1.0f - t; - return f0 * s + f1 * t; + return fabs(f) <= epsilon; } static float square(float f) @@ -130,11 +443,6 @@ static float square(float f) return f * f; } -static int square(int i) -{ - return i * i; -} - /** Return the next power of two. * @see http://graphics.stanford.edu/~seander/bithacks.html * @warning Behaviour for 0 is undefined. @@ -143,7 +451,7 @@ static int square(int i) */ static uint32_t nextPowerOfTwo(uint32_t x) { - xaDebugAssert( x != 0 ); + XA_DEBUG_ASSERT( x != 0 ); // On modern CPUs this is supposed to be as fast as using the bsr instruction. x--; x |= x >> 1; @@ -154,16 +462,6 @@ static uint32_t nextPowerOfTwo(uint32_t x) return x + 1; } -static uint64_t nextPowerOfTwo(uint64_t x) -{ - xaDebugAssert(x != 0); - uint32_t p = 1; - while (x > p) { - p += p; - } - return p; -} - static uint32_t sdbmHash(const void *data_in, uint32_t size, uint32_t h = 5381) { const uint8_t *data = (const uint8_t *) data_in; @@ -174,31 +472,12 @@ static uint32_t sdbmHash(const void *data_in, uint32_t size, uint32_t h = 5381) return h; } -// Note that this hash does not handle NaN properly. -static uint32_t sdbmFloatHash(const float *f, uint32_t count, uint32_t h = 5381) -{ - for (uint32_t i = 0; i < count; i++) { - union { - float f; - uint32_t i; - } x = { f[i] }; - if (x.i == 0x80000000) x.i = 0; - h = sdbmHash(&x, 4, h); - } - return h; -} - template <typename T> static uint32_t hash(const T &t, uint32_t h = 5381) { return sdbmHash(&t, sizeof(T), h); } -static uint32_t hash(const float &f, uint32_t h) -{ - return sdbmFloatHash(&f, 1, h); -} - // Functors for hash table: template <typename Key> struct Hash { @@ -213,40 +492,22 @@ template <typename Key> struct Equal class Vector2 { public: - typedef Vector2 const &Arg; - Vector2() {} explicit Vector2(float f) : x(f), y(f) {} Vector2(float x, float y): x(x), y(y) {} - Vector2(Vector2::Arg v) : x(v.x), y(v.y) {} - - const Vector2 &operator=(Vector2::Arg v) - { - x = v.x; - y = v.y; - return - *this; - } - const float *ptr() const { return &x; } - - void set(float _x, float _y) - { - x = _x; - y = _y; - } Vector2 operator-() const { return Vector2(-x, -y); } - void operator+=(Vector2::Arg v) + void operator+=(const Vector2 &v) { x += v.x; y += v.y; } - void operator-=(Vector2::Arg v) + void operator-=(const Vector2 &v) { x -= v.x; y -= v.y; @@ -258,138 +519,99 @@ public: y *= s; } - void operator*=(Vector2::Arg v) + void operator*=(const Vector2 &v) { x *= v.x; y *= v.y; } - friend bool operator==(Vector2::Arg a, Vector2::Arg b) - { - return a.x == b.x && a.y == b.y; - } - - friend bool operator!=(Vector2::Arg a, Vector2::Arg b) - { - return a.x != b.x || a.y != b.y; - } - - union - { -#ifdef _MSC_VER -#pragma warning(push) -#pragma warning(disable : 4201) -#endif - struct - { - float x, y; - }; -#ifdef _MSC_VER -#pragma warning(pop) -#endif - - float component[2]; - }; + float x, y; }; -Vector2 operator+(Vector2::Arg a, Vector2::Arg b) +static bool operator==(const Vector2 &a, const Vector2 &b) { - return Vector2(a.x + b.x, a.y + b.y); + return a.x == b.x && a.y == b.y; } -Vector2 operator-(Vector2::Arg a, Vector2::Arg b) +static bool operator!=(const Vector2 &a, const Vector2 &b) { - return Vector2(a.x - b.x, a.y - b.y); + return a.x != b.x || a.y != b.y; } -Vector2 operator*(Vector2::Arg v, float s) +static Vector2 operator+(const Vector2 &a, const Vector2 &b) { - return Vector2(v.x * s, v.y * s); -} - -Vector2 operator*(Vector2::Arg v1, Vector2::Arg v2) -{ - return Vector2(v1.x * v2.x, v1.y * v2.y); + return Vector2(a.x + b.x, a.y + b.y); } -Vector2 operator/(Vector2::Arg v, float s) +static Vector2 operator-(const Vector2 &a, const Vector2 &b) { - return Vector2(v.x / s, v.y / s); + return Vector2(a.x - b.x, a.y - b.y); } -Vector2 lerp(Vector2::Arg v1, Vector2::Arg v2, float t) +static Vector2 operator*(const Vector2 &v, float s) { - const float s = 1.0f - t; - return Vector2(v1.x * s + t * v2.x, v1.y * s + t * v2.y); + return Vector2(v.x * s, v.y * s); } -float dot(Vector2::Arg a, Vector2::Arg b) +static float dot(const Vector2 &a, const Vector2 &b) { return a.x * b.x + a.y * b.y; } -float lengthSquared(Vector2::Arg v) +static float lengthSquared(const Vector2 &v) { return v.x * v.x + v.y * v.y; } -float length(Vector2::Arg v) +static float length(const Vector2 &v) { return sqrtf(lengthSquared(v)); } -float distance(Vector2::Arg a, Vector2::Arg b) -{ - return length(a - b); -} - -bool isNormalized(Vector2::Arg v, float epsilon = NV_NORMAL_EPSILON) +#if XA_DEBUG +static bool isNormalized(const Vector2 &v, float epsilon = kNormalEpsilon) { return equal(length(v), 1, epsilon); } +#endif -Vector2 normalize(Vector2::Arg v, float epsilon = NV_EPSILON) +static Vector2 normalize(const Vector2 &v, float epsilon) { float l = length(v); - xaDebugAssert(!isZero(l, epsilon)); -#ifdef NDEBUG - epsilon = 0; // silence unused parameter warning -#endif + XA_DEBUG_ASSERT(!isZero(l, epsilon)); + XA_UNUSED(epsilon); Vector2 n = v * (1.0f / l); - xaDebugAssert(isNormalized(n)); + XA_DEBUG_ASSERT(isNormalized(n)); return n; } -Vector2 normalizeSafe(Vector2::Arg v, Vector2::Arg fallback, float epsilon = NV_EPSILON) +static bool equal(const Vector2 &v1, const Vector2 &v2, float epsilon) { - float l = length(v); - if (isZero(l, epsilon)) { - return fallback; - } - return v * (1.0f / l); + return equal(v1.x, v2.x, epsilon) && equal(v1.y, v2.y, epsilon); } -bool equal(Vector2::Arg v1, Vector2::Arg v2, float epsilon = NV_EPSILON) +static Vector2 min(const Vector2 &a, const Vector2 &b) { - return equal(v1.x, v2.x, epsilon) && equal(v1.y, v2.y, epsilon); + return Vector2(min(a.x, b.x), min(a.y, b.y)); } -Vector2 max(Vector2::Arg a, Vector2::Arg b) +static Vector2 max(const Vector2 &a, const Vector2 &b) { - return Vector2(std::max(a.x, b.x), std::max(a.y, b.y)); + return Vector2(max(a.x, b.x), max(a.y, b.y)); } -bool isFinite(Vector2::Arg v) +static bool isFinite(const Vector2 &v) { - return std::isfinite(v.x) && std::isfinite(v.y); + return isFinite(v.x) && isFinite(v.y); } // Note, this is the area scaled by 2! -float triangleArea(Vector2::Arg v0, Vector2::Arg v1) +static float triangleArea(const Vector2 &v0, const Vector2 &v1) { return (v0.x * v1.y - v0.y * v1.x); // * 0.5f; } -float triangleArea(Vector2::Arg a, Vector2::Arg b, Vector2::Arg 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; @@ -402,62 +624,54 @@ float triangleArea(Vector2::Arg a, Vector2::Arg b, Vector2::Arg c) return triangleArea(a - c, b - c); } -float triangleArea2(Vector2::Arg v1, Vector2::Arg v2, Vector2::Arg v3) +static bool linesIntersect(const Vector2 &a1, const Vector2 &a2, const Vector2 &b1, const Vector2 &b2, float epsilon) { - return 0.5f * (v3.x * v1.y + v1.x * v2.y + v2.x * v3.y - v2.x * v1.y - v3.x * v2.y - v1.x * v3.y); + const Vector2 v0 = a2 - a1; + const Vector2 v1 = b2 - b1; + const float denom = -v1.x * v0.y + v0.x * v1.y; + if (equal(denom, 0.0f, epsilon)) + 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; + return t > epsilon && t < 1.0f - epsilon; + } + return false; } -static uint32_t hash(const Vector2 &v, uint32_t h) +struct Vector2i { - return sdbmFloatHash(v.component, 2, h); -} + Vector2i(int32_t x, int32_t y) : x(x), y(y) {} + + int32_t x, y; +}; class Vector3 { public: - typedef Vector3 const &Arg; - 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(Vector2::Arg v, float z) : x(v.x), y(v.y), z(z) {} - Vector3(Vector3::Arg v) : x(v.x), y(v.y), z(v.z) {} - - const Vector3 &operator=(Vector3::Arg v) - { - x = v.x; - y = v.y; - z = v.z; - return *this; - } + Vector3(const Vector2 &v, float z) : x(v.x), y(v.y), z(z) {} Vector2 xy() const { return Vector2(x, y); } - const float *ptr() const { return &x; } - - void set(float _x, float _y, float _z) - { - x = _x; - y = _y; - z = _z; - } - Vector3 operator-() const { return Vector3(-x, -y, -z); } - void operator+=(Vector3::Arg v) + void operator+=(const Vector3 &v) { x += v.x; y += v.y; z += v.z; } - void operator-=(Vector3::Arg v) + void operator-=(const Vector3 &v) { x -= v.x; y -= v.y; @@ -479,159 +693,89 @@ public: z *= is; } - void operator*=(Vector3::Arg v) + void operator*=(const Vector3 &v) { x *= v.x; y *= v.y; z *= v.z; } - void operator/=(Vector3::Arg v) + void operator/=(const Vector3 &v) { x /= v.x; y /= v.y; z /= v.z; } - friend bool operator==(Vector3::Arg a, Vector3::Arg b) - { - return a.x == b.x && a.y == b.y && a.z == b.z; - } - - friend bool operator!=(Vector3::Arg a, Vector3::Arg b) - { - return a.x != b.x || a.y != b.y || a.z != b.z; - } - - union - { -#ifdef _MSC_VER -#pragma warning(push) -#pragma warning(disable : 4201) -#endif - struct - { - float x, y, z; - }; -#ifdef _MSC_VER -#pragma warning(pop) -#endif - - float component[3]; - }; + float x, y, z; }; -Vector3 add(Vector3::Arg a, Vector3::Arg b) -{ - return Vector3(a.x + b.x, a.y + b.y, a.z + b.z); -} -Vector3 add(Vector3::Arg a, float b) -{ - return Vector3(a.x + b, a.y + b, a.z + b); -} -Vector3 operator+(Vector3::Arg a, Vector3::Arg b) +static bool operator!=(const Vector3 &a, const Vector3 &b) { - return add(a, b); -} -Vector3 operator+(Vector3::Arg a, float b) -{ - return add(a, b); + return a.x != b.x || a.y != b.y || a.z != b.z; } -Vector3 sub(Vector3::Arg a, Vector3::Arg b) +static Vector3 operator+(const Vector3 &a, const Vector3 &b) { - return Vector3(a.x - b.x, a.y - b.y, a.z - b.z); -} - -Vector3 sub(Vector3::Arg a, float b) -{ - return Vector3(a.x - b, a.y - b, a.z - b); -} - -Vector3 operator-(Vector3::Arg a, Vector3::Arg b) -{ - return sub(a, b); + return Vector3(a.x + b.x, a.y + b.y, a.z + b.z); } -Vector3 operator-(Vector3::Arg a, float b) +static Vector3 operator-(const Vector3 &a, const Vector3 &b) { - return sub(a, b); + return Vector3(a.x - b.x, a.y - b.y, a.z - b.z); } -Vector3 cross(Vector3::Arg a, Vector3::Arg b) +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); } -Vector3 operator*(Vector3::Arg v, float s) +static Vector3 operator*(const Vector3 &v, float s) { return Vector3(v.x * s, v.y * s, v.z * s); } -Vector3 operator*(float s, Vector3::Arg v) +static Vector3 operator*(float s, const Vector3 &v) { return Vector3(v.x * s, v.y * s, v.z * s); } -Vector3 operator*(Vector3::Arg v, Vector3::Arg s) -{ - return Vector3(v.x * s.x, v.y * s.y, v.z * s.z); -} - -Vector3 operator/(Vector3::Arg v, float s) +static Vector3 operator/(const Vector3 &v, float s) { return v * (1.0f / s); } -Vector3 lerp(Vector3::Arg v1, Vector3::Arg v2, float t) -{ - const float s = 1.0f - t; - return Vector3(v1.x * s + t * v2.x, v1.y * s + t * v2.y, v1.z * s + t * v2.z); -} - -float dot(Vector3::Arg a, Vector3::Arg b) +static float dot(const Vector3 &a, const Vector3 &b) { return a.x * b.x + a.y * b.y + a.z * b.z; } -float lengthSquared(Vector3::Arg v) +static float lengthSquared(const Vector3 &v) { return v.x * v.x + v.y * v.y + v.z * v.z; } -float length(Vector3::Arg v) +static float length(const Vector3 &v) { return sqrtf(lengthSquared(v)); } -float distance(Vector3::Arg a, Vector3::Arg b) -{ - return length(a - b); -} - -float distanceSquared(Vector3::Arg a, Vector3::Arg b) -{ - return lengthSquared(a - b); -} - -bool isNormalized(Vector3::Arg v, float epsilon = NV_NORMAL_EPSILON) +static bool isNormalized(const Vector3 &v, float epsilon = kNormalEpsilon) { return equal(length(v), 1, epsilon); } -Vector3 normalize(Vector3::Arg v, float epsilon = NV_EPSILON) +static Vector3 normalize(const Vector3 &v, float epsilon) { float l = length(v); - xaDebugAssert(!isZero(l, epsilon)); -#ifdef NDEBUG - epsilon = 0; // silence unused parameter warning -#endif + XA_DEBUG_ASSERT(!isZero(l, epsilon)); + XA_UNUSED(epsilon); Vector3 n = v * (1.0f / l); - xaDebugAssert(isNormalized(n)); + XA_DEBUG_ASSERT(isNormalized(n)); return n; } -Vector3 normalizeSafe(Vector3::Arg v, Vector3::Arg fallback, float epsilon = NV_EPSILON) +static Vector3 normalizeSafe(const Vector3 &v, const Vector3 &fallback, float epsilon) { float l = length(v); if (isZero(l, epsilon)) { @@ -640,57 +784,423 @@ Vector3 normalizeSafe(Vector3::Arg v, Vector3::Arg fallback, float epsilon = NV_ return v * (1.0f / l); } -bool equal(Vector3::Arg v1, Vector3::Arg v2, float epsilon = NV_EPSILON) +static bool equal(const Vector3 &v0, const Vector3 &v1, float epsilon) { - return equal(v1.x, v2.x, epsilon) && equal(v1.y, v2.y, epsilon) && equal(v1.z, v2.z, epsilon); + return fabs(v0.x - v1.x) <= epsilon && fabs(v0.y - v1.y) <= epsilon && fabs(v0.z - v1.z) <= epsilon; } -Vector3 min(Vector3::Arg a, Vector3::Arg b) +static Vector3 min(const Vector3 &a, const Vector3 &b) { - return Vector3(std::min(a.x, b.x), std::min(a.y, b.y), std::min(a.z, b.z)); + return Vector3(min(a.x, b.x), min(a.y, b.y), min(a.z, b.z)); } -Vector3 max(Vector3::Arg a, Vector3::Arg b) +static Vector3 max(const Vector3 &a, const Vector3 &b) { - return Vector3(std::max(a.x, b.x), std::max(a.y, b.y), std::max(a.z, b.z)); + return Vector3(max(a.x, b.x), max(a.y, b.y), max(a.z, b.z)); } -Vector3 clamp(Vector3::Arg v, float min, float max) +#if XA_DEBUG +bool isFinite(const Vector3 &v) { - return Vector3(clamp(v.x, min, max), clamp(v.y, min, max), clamp(v.z, min, max)); + return isFinite(v.x) && isFinite(v.y) && isFinite(v.z); } +#endif -Vector3 saturate(Vector3::Arg v) +struct Plane { - return Vector3(saturate(v.x), saturate(v.y), saturate(v.z)); + 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; } -Vector3 floor(Vector3::Arg v) +static bool sameSide(const Vector3 &p1, const Vector3 &p2, const Vector3 &a, const Vector3 &b) { - return Vector3(floorf(v.x), floorf(v.y), floorf(v.z)); + const Vector3 &ab = b - a; + return dot(cross(ab, p1 - a), cross(ab, p2 - a)) >= 0.0f; } -bool isFinite(Vector3::Arg v) +// http://blackpawn.com/texts/pointinpoly/default.html +static bool pointInTriangle(const Vector3 &p, const Vector3 &a, const Vector3 &b, const Vector3 &c) { - return std::isfinite(v.x) && std::isfinite(v.y) && std::isfinite(v.z); + return sameSide(p, a, b, c) && sameSide(p, b, a, c) && sameSide(p, c, a, b); } -static uint32_t hash(const Vector3 &v, uint32_t h) +#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 { - return sdbmFloatHash(v.component, 3, h); + 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) + { + min = internal::min(min, p); + max = internal::max(max, p); + } + + void expandToInclude(const AABB &aabb) + { + min = internal::min(min, aabb.min); + max = internal::max(max, aabb.max); + } + + void expand(float amount) + { + min -= Vector3(amount); + max += Vector3(amount); + } + + Vector3 centroid() const + { + return min + (max - min) * 0.5f; + } + + 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) + result = 2; + return result; + } + + Vector3 min, max; +}; + +template <typename T> +static void construct_range(T * ptr, uint32_t new_size, uint32_t old_size) { + for (uint32_t i = old_size; i < new_size; i++) { + new(ptr+i) T; // placement new + } +} + +template <typename T> +static void construct_range(T * ptr, uint32_t new_size, uint32_t old_size, const T & elem) { + for (uint32_t i = old_size; i < new_size; i++) { + new(ptr+i) T(elem); // placement new + } } +template <typename T> +static void construct_range(T * ptr, uint32_t new_size, uint32_t old_size, const T * src) { + for (uint32_t i = old_size; i < new_size; i++) { + new(ptr+i) T(src[i]); // placement new + } +} + +template <typename T> +static void destroy_range(T * ptr, uint32_t new_size, uint32_t old_size) { + for (uint32_t i = new_size; i < old_size; i++) { + (ptr+i)->~T(); // Explicit call to the destructor + } +} + +/** +* Replacement for std::vector that is easier to debug and provides +* some nice foreach enumerators. +*/ +template<typename T> +class Array { +public: + typedef uint32_t size_type; + + Array(int memTag = MemTag::Default) : m_memTag(memTag), m_buffer(nullptr), m_capacity(0), m_size(0) {} + + Array(const Array &a) : m_memTag(a.m_memTag), m_buffer(nullptr), m_capacity(0), m_size(0) + { + copy(a.m_buffer, a.m_size); + } + + ~Array() + { + destroy(); + } + + const Array<T> &operator=(const Array<T> &other) + { + m_memTag = other.m_memTag; + m_buffer = other.m_buffer; + m_capacity = other.m_capacity; + m_size = other.m_size; + return *this; + } + + const T & operator[]( uint32_t index ) const + { + XA_DEBUG_ASSERT(index < m_size); + return m_buffer[index]; + } + + T & operator[] ( uint32_t index ) + { + XA_DEBUG_ASSERT(index < m_size); + return m_buffer[index]; + } + + uint32_t size() const { return m_size; } + const T * data() const { return m_buffer; } + T * data() { return m_buffer; } + T * begin() { return m_buffer; } + T * end() { return m_buffer + m_size; } + const T * begin() const { return m_buffer; } + const T * end() const { return m_buffer + m_size; } + bool isEmpty() const { return m_size == 0; } + + void push_back( const T & val ) + { + XA_DEBUG_ASSERT(&val < m_buffer || &val >= m_buffer+m_size); + uint32_t old_size = m_size; + uint32_t new_size = m_size + 1; + setArraySize(new_size); + construct_range(m_buffer, new_size, old_size, val); + } + + void pop_back() + { + XA_DEBUG_ASSERT( m_size > 0 ); + resize( m_size - 1 ); + } + + const T & back() const + { + XA_DEBUG_ASSERT( m_size > 0 ); + return m_buffer[m_size-1]; + } + + T & back() + { + XA_DEBUG_ASSERT( m_size > 0 ); + return m_buffer[m_size-1]; + } + + const T & front() const + { + XA_DEBUG_ASSERT( m_size > 0 ); + return m_buffer[0]; + } + + T & front() + { + XA_DEBUG_ASSERT( m_size > 0 ); + return m_buffer[0]; + } + + // Remove the element at the given index. This is an expensive operation! + void removeAt(uint32_t index) + { + XA_DEBUG_ASSERT(index >= 0 && index < m_size); + if (m_size == 1) { + clear(); + } + else { + m_buffer[index].~T(); + memmove(m_buffer+index, m_buffer+index+1, sizeof(T) * (m_size - 1 - index)); + m_size--; + } + } + + // Insert the given element at the given index shifting all the elements up. + void insertAt(uint32_t index, const T & val = T()) + { + XA_DEBUG_ASSERT( index >= 0 && index <= m_size ); + setArraySize(m_size + 1); + if (index < m_size - 1) { + memmove(m_buffer+index+1, m_buffer+index, sizeof(T) * (m_size - 1 - index)); + } + // Copy-construct into the newly opened slot. + new(m_buffer+index) T(val); + } + + void append(const Array<T> & other) + { + append(other.m_buffer, other.m_size); + } + + void resize(uint32_t new_size) + { + uint32_t old_size = m_size; + // Destruct old elements (if we're shrinking). + destroy_range(m_buffer, new_size, old_size); + setArraySize(new_size); + // Call default constructors + construct_range(m_buffer, new_size, old_size); + } + + void resize(uint32_t new_size, const T & elem) + { + XA_DEBUG_ASSERT(&elem < m_buffer || &elem > m_buffer+m_size); + uint32_t old_size = m_size; + // Destruct old elements (if we're shrinking). + destroy_range(m_buffer, new_size, old_size); + setArraySize(new_size); + // Call copy constructors + construct_range(m_buffer, new_size, old_size, elem); + } + + void clear() + { + // Destruct old elements + destroy_range(m_buffer, 0, m_size); + m_size = 0; + } + + void destroy() + { + clear(); + XA_FREE(m_buffer); + m_buffer = nullptr; + m_capacity = 0; + m_size = 0; + } + + void reserve(uint32_t desired_size) + { + if (desired_size > m_capacity) { + setArrayCapacity(desired_size); + } + } + + void copy(const T * data, uint32_t count) + { + destroy_range(m_buffer, 0, m_size); + setArraySize(count); + construct_range(m_buffer, count, 0, data); + } + + void moveTo(Array<T> &other) + { + other.destroy(); + swap(m_buffer, other.m_buffer); + swap(m_capacity, other.m_capacity); + swap(m_size, other.m_size); + } + +protected: + void setArraySize(uint32_t new_size) + { + m_size = new_size; + if (new_size > m_capacity) { + uint32_t new_buffer_size; + if (m_capacity == 0) { + // first allocation is exact + new_buffer_size = new_size; + } + else { + // following allocations grow array by 25% + new_buffer_size = new_size + (new_size >> 2); + } + setArrayCapacity( new_buffer_size ); + } + } + void setArrayCapacity(uint32_t new_capacity) + { + XA_DEBUG_ASSERT(new_capacity >= m_size); + if (new_capacity == 0) { + // free the buffer. + if (m_buffer != nullptr) { + XA_FREE(m_buffer); + m_buffer = nullptr; + } + } + else { + // realloc the buffer + m_buffer = XA_REALLOC(m_memTag, m_buffer, T, new_capacity); + } + m_capacity = new_capacity; + } + + int m_memTag; + T * m_buffer; + uint32_t m_capacity; + uint32_t m_size; +}; + /// Basis class to compute tangent space basis, ortogonalizations and to /// transform vectors from one space to another. -class Basis +struct Basis { -public: - /// Create a null basis. - Basis() : tangent(0, 0, 0), bitangent(0, 0, 0), normal(0, 0, 0) {} - - void buildFrameForDirection(Vector3::Arg d, float angle = 0) + void buildFrameForDirection(const Vector3 &d, float angle = 0) { - xaAssert(isNormalized(d)); + XA_ASSERT(isNormalized(d)); normal = d; // Choose minimum axis. if (fabsf(normal.x) < fabsf(normal.y) && fabsf(normal.x) < fabsf(normal.z)) { @@ -702,7 +1212,7 @@ public: } // Ortogonalize tangent -= normal * dot(normal, tangent); - tangent = normalize(tangent); + tangent = normalize(tangent, kEpsilon); bitangent = cross(normal, tangent); // Rotate frame around normal according to angle. if (angle != 0.0f) { @@ -714,9 +1224,9 @@ public: } } - Vector3 tangent; - Vector3 bitangent; - Vector3 normal; + Vector3 tangent = Vector3(0.0f); + Vector3 bitangent = Vector3(0.0f); + Vector3 normal = Vector3(0.0f); }; // Simple bit array. @@ -724,21 +1234,12 @@ class BitArray { public: BitArray() : m_size(0) {} + BitArray(uint32_t sz) { resize(sz); } - uint32_t size() const - { - return m_size; - } - - void clear() - { - resize(0); - } - void resize(uint32_t new_size) { m_size = new_size; @@ -748,162 +1249,311 @@ public: /// Get bit. bool bitAt(uint32_t b) const { - xaDebugAssert( b < m_size ); + XA_DEBUG_ASSERT( b < m_size ); return (m_wordArray[b >> 5] & (1 << (b & 31))) != 0; } // Set a bit. void setBitAt(uint32_t idx) { - xaDebugAssert(idx < m_size); + XA_DEBUG_ASSERT(idx < m_size); m_wordArray[idx >> 5] |= (1 << (idx & 31)); } - // Toggle a bit. - void toggleBitAt(uint32_t idx) - { - xaDebugAssert(idx < m_size); - m_wordArray[idx >> 5] ^= (1 << (idx & 31)); - } - - // Set a bit to the given value. @@ Rename modifyBitAt? - void setBitAt(uint32_t idx, bool b) - { - xaDebugAssert(idx < m_size); - m_wordArray[idx >> 5] = setBits(m_wordArray[idx >> 5], 1 << (idx & 31), b); - xaDebugAssert(bitAt(idx) == b); - } - // Clear all the bits. void clearAll() { - memset(m_wordArray.data(), 0, m_wordArray.size() * sizeof(uint32_t )); - } - - // Set all the bits. - void setAll() - { - memset(m_wordArray.data(), 0xFF, m_wordArray.size() * sizeof(uint32_t )); + memset(m_wordArray.data(), 0, m_wordArray.size() * sizeof(uint32_t)); } private: - // See "Conditionally set or clear bits without branching" at http://graphics.stanford.edu/~seander/bithacks.html - uint32_t setBits(uint32_t w, uint32_t m, bool b) - { - return (w & ~m) | (-int(b) & m); - } - // Number of bits stored. uint32_t m_size; // Array of bits. - std::vector<uint32_t> m_wordArray; + Array<uint32_t> m_wordArray; }; -/// Bit map. This should probably be called BitImage. -class BitMap +class BitImage { public: - BitMap() : m_width(0), m_height(0) {} - BitMap(uint32_t w, uint32_t h) : m_width(w), m_height(h), m_bitArray(w * h) {} + BitImage() : m_width(0), m_height(0), m_rowStride(0) {} - uint32_t width() const + BitImage(uint32_t w, uint32_t h) : m_width(w), m_height(h) { - return m_width; + m_rowStride = (m_width + 63) >> 6; + m_data.resize(m_rowStride * m_height); } - uint32_t height() const + + BitImage(const BitImage &other) { - return m_height; + m_width = other.m_width; + m_height = other.m_height; + m_rowStride = other.m_rowStride; + m_data.resize(m_rowStride * m_height); + memcpy(m_data.data(), other.m_data.data(), m_rowStride * m_height * sizeof(uint64_t)); } - void resize(uint32_t w, uint32_t h, bool initValue) + const BitImage &operator=(const BitImage &other) { - BitArray tmp(w * h); - if (initValue) tmp.setAll(); - else tmp.clearAll(); - // @@ Copying one bit at a time. This could be much faster. - for (uint32_t y = 0; y < m_height; y++) { - for (uint32_t x = 0; x < m_width; x++) { - //tmp.setBitAt(y*w + x, bitAt(x, y)); - if (bitAt(x, y) != initValue) tmp.toggleBitAt(y * w + x); - } + m_width = other.m_width; + m_height = other.m_height; + m_rowStride = other.m_rowStride; + m_data = other.m_data; + return *this; + } + + uint32_t width() const { return m_width; } + uint32_t height() const { return m_height; } + + void resize(uint32_t w, uint32_t h, bool discard) + { + const uint32_t rowStride = (w + 63) >> 6; + if (discard) { + m_data.resize(rowStride * h); + memset(m_data.data(), 0, m_data.size() * sizeof(uint64_t)); + } else { + Array<uint64_t> tmp; + tmp.resize(rowStride * h); + memset(tmp.data(), 0, tmp.size() * sizeof(uint64_t)); + // If only height has changed, can copy all rows at once. + if (rowStride == m_rowStride) { + memcpy(tmp.data(), m_data.data(), m_rowStride * min(m_height, h) * sizeof(uint64_t)); + } else if (m_width > 0 && m_height > 0) { + const uint32_t height = min(m_height, h); + for (uint32_t i = 0; i < height; i++) + memcpy(&tmp[i * rowStride], &m_data[i * m_rowStride], min(rowStride, m_rowStride) * sizeof(uint64_t)); + } + tmp.moveTo(m_data); } - std::swap(m_bitArray, tmp); m_width = w; m_height = h; + m_rowStride = rowStride; } bool bitAt(uint32_t x, uint32_t y) const { - xaDebugAssert(x < m_width && y < m_height); - return m_bitArray.bitAt(y * m_width + x); + 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 setBitAt(uint32_t x, uint32_t y) { - xaDebugAssert(x < m_width && y < m_height); - m_bitArray.setBitAt(y * m_width + x); + 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(bitAt(x, y)); } void clearAll() { - m_bitArray.clearAll(); + memset(m_data.data(), 0, m_data.size() * sizeof(uint64_t)); + } + + 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) + continue; + uint32_t x = 0; + for (;;) { + const uint32_t thisX = x + offsetX; + if (thisX >= m_width) + break; + const uint32_t thisBlockShift = thisX % 64; + const uint64_t thisBlock = m_data[(thisX >> 6) + thisY * m_rowStride] >> thisBlockShift; + const uint32_t blockShift = x % 64; + const uint64_t block = image.m_data[(x >> 6) + y * image.m_rowStride] >> blockShift; + if ((thisBlock & block) != 0) + return false; + x += 64 - max(thisBlockShift, blockShift); + if (x >= image.m_width) + break; + } + } + return true; + } + + void dilate(uint32_t padding) + { + BitImage tmp(m_width, m_height); + for (uint32_t p = 0; p < padding; p++) { + tmp.clearAll(); + for (uint32_t y = 0; y < m_height; y++) { + for (uint32_t x = 0; x < m_width; x++) { + bool b = bitAt(x, y); + if (!b) { + if (x > 0) { + b |= bitAt(x - 1, y); + if (y > 0) b |= bitAt(x - 1, y - 1); + if (y < m_height - 1) b |= bitAt(x - 1, y + 1); + } + if (y > 0) b |= bitAt(x, y - 1); + if (y < m_height - 1) b |= bitAt(x, y + 1); + if (x < m_width - 1) { + b |= bitAt(x + 1, y); + if (y > 0) b |= bitAt(x + 1, y - 1); + if (y < m_height - 1) b |= bitAt(x + 1, y + 1); + } + } + if (b) + tmp.setBitAt(x, y); + } + } + swap(m_data, tmp.m_data); + } } private: uint32_t m_width; uint32_t m_height; - BitArray m_bitArray; + uint32_t m_rowStride; // In uint64_t's + Array<uint64_t> m_data; }; -// Axis Aligned Bounding Box. -class Box +// From Fast-BVH +class BVH { public: - Box() {} - Box(const Box &b) : minCorner(b.minCorner), maxCorner(b.maxCorner) {} - Box(const Vector3 &mins, const Vector3 &maxs) : minCorner(mins), maxCorner(maxs) {} - - operator const float *() const - { - return reinterpret_cast<const float *>(this); - } - - // Clear the bounds. - void clearBounds() - { - minCorner.set(FLT_MAX, FLT_MAX, FLT_MAX); - maxCorner.set(-FLT_MAX, -FLT_MAX, -FLT_MAX); - } - - // Return extents of the box. - Vector3 extents() const + BVH(const Array<AABB> &objectAabbs, uint32_t leafSize = 4) { - return (maxCorner - minCorner) * 0.5f; + m_objectAabbs = &objectAabbs; + if (m_objectAabbs->isEmpty()) + return; + m_objectIds.resize(objectAabbs.size()); + for (uint32_t i = 0; i < m_objectIds.size(); i++) + m_objectIds[i] = i; + BuildEntry todo[128]; + uint32_t stackptr = 0; + const uint32_t kRoot = 0xfffffffc; + const uint32_t kUntouched = 0xffffffff; + const uint32_t kTouchedTwice = 0xfffffffd; + // Push the root + todo[stackptr].start = 0; + todo[stackptr].end = objectAabbs.size(); + todo[stackptr].parent = kRoot; + stackptr++; + Node node; + m_nodes.reserve(objectAabbs.size() * 2); + uint32_t nNodes = 0; + while(stackptr > 0) { + // Pop the next item off of the stack + const BuildEntry &bnode = todo[--stackptr]; + const uint32_t start = bnode.start; + const uint32_t end = bnode.end; + const uint32_t nPrims = end - start; + nNodes++; + node.start = start; + node.nPrims = nPrims; + node.rightOffset = kUntouched; + // 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) { + bb.expandToInclude(objectAabbs[m_objectIds[p]]); + bc.expandToInclude(objectAabbs[m_objectIds[p]].centroid()); + } + node.aabb = bb; + // If the number of primitives at this point is less than the leaf + // size, then this will become a leaf. (Signified by rightOffset == 0) + if (nPrims <= leafSize) + node.rightOffset = 0; + m_nodes.push_back(node); + // Child touches parent... + // Special case: Don't do this for the root. + if (bnode.parent != kRoot) { + 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 ) + m_nodes[bnode.parent].rightOffset = nNodes - 1 - bnode.parent; + } + // If this is a leaf, no need to subdivide. + if (node.rightOffset == 0) + continue; + // Set the split dimensions + const uint32_t split_dim = bc.maxDimension(); + // Split on the center of the longest axis + const float split_coord = 0.5f * ((&bc.min.x)[split_dim] + (&bc.max.x)[split_dim]); + // Partition the list of objects on this split + uint32_t mid = start; + for (uint32_t i = start; i < end; ++i) { + const Vector3 centroid(objectAabbs[m_objectIds[i]].centroid()); + if ((¢roid.x)[split_dim] < split_coord) { + swap(m_objectIds[i], m_objectIds[mid]); + ++mid; + } + } + // If we get a bad split, just choose the center... + if (mid == start || mid == end) + mid = start + (end - start) / 2; + // Push right child + todo[stackptr].start = mid; + todo[stackptr].end = end; + todo[stackptr].parent = nNodes - 1; + stackptr++; + // Push left child + todo[stackptr].start = start; + todo[stackptr].end = mid; + todo[stackptr].parent = nNodes - 1; + stackptr++; + } + } + + 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) { + // 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) { + const uint32_t obj = node.start + o; + if (queryAabb.intersect((*m_objectAabbs)[m_objectIds[obj]])) + result.push_back(m_objectIds[obj]); + } + } else { // Not a leaf + const uint32_t left = ni + 1; + const uint32_t right = ni + node.rightOffset; + if (queryAabb.intersect(m_nodes[left].aabb)) + todo[++stackptr] = left; + if (queryAabb.intersect(m_nodes[right].aabb)) + todo[++stackptr] = right; + } + } } - // Add a point to this box. - void addPointToBounds(const Vector3 &p) +private: + struct BuildEntry { - minCorner = min(minCorner, p); - maxCorner = max(maxCorner, p); - } + 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. + }; - // Get the volume of the box. - float volume() const + struct Node { - Vector3 d = extents(); - return 8.0f * (d.x * d.y * d.z); - } + AABB aabb; + uint32_t start, nPrims, rightOffset; + }; - Vector3 minCorner; - Vector3 maxCorner; + const Array<AABB> *m_objectAabbs; + Array<uint32_t> m_objectIds; + Array<Node> m_nodes; }; class Fit { public: - static Vector3 computeCentroid(int n, const Vector3 *__restrict points) + static Vector3 computeCentroid(int n, const Vector3 * points) { Vector3 centroid(0.0f); for (int i = 0; i < n; i++) { @@ -913,7 +1563,7 @@ public: return centroid; } - static Vector3 computeCovariance(int n, const Vector3 *__restrict points, float *__restrict covariance) + static Vector3 computeCovariance(int n, const Vector3 * points, float * covariance) { // compute the centroid Vector3 centroid = computeCentroid(n, points); @@ -933,25 +1583,12 @@ public: return centroid; } - static bool isPlanar(int n, const Vector3 *points, float epsilon = NV_EPSILON) - { - // compute the centroid and covariance - float matrix[6]; - computeCovariance(n, points, matrix); - float eigenValues[3]; - Vector3 eigenVectors[3]; - if (!eigenSolveSymmetric3(matrix, eigenValues, eigenVectors)) { - return false; - } - return eigenValues[2] < epsilon; - } - // 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]) { - xaDebugAssert(matrix != NULL && eigenValues != NULL && eigenVectors != NULL); + XA_DEBUG_ASSERT(matrix != nullptr && eigenValues != nullptr && eigenVectors != nullptr); float subd[3]; float diag[3]; float work[3][3]; @@ -975,24 +1612,24 @@ public: // eigenvectors are the columns; make them the rows : for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { - eigenVectors[j].component[i] = (float) work[i][j]; + (&eigenVectors[j].x)[i] = (float) work[i][j]; } } // shuffle to sort by singular value : if (eigenValues[2] > eigenValues[0] && eigenValues[2] > eigenValues[1]) { - std::swap(eigenValues[0], eigenValues[2]); - std::swap(eigenVectors[0], eigenVectors[2]); + swap(eigenValues[0], eigenValues[2]); + swap(eigenVectors[0], eigenVectors[2]); } if (eigenValues[1] > eigenValues[0]) { - std::swap(eigenValues[0], eigenValues[1]); - std::swap(eigenVectors[0], eigenVectors[1]); + swap(eigenValues[0], eigenValues[1]); + swap(eigenVectors[0], eigenVectors[1]); } if (eigenValues[2] > eigenValues[1]) { - std::swap(eigenValues[1], eigenValues[2]); - std::swap(eigenVectors[1], eigenVectors[2]); + swap(eigenValues[1], eigenValues[2]); + swap(eigenVectors[1], eigenVectors[2]); } - xaDebugAssert(eigenValues[0] >= eigenValues[1] && eigenValues[0] >= eigenValues[2]); - xaDebugAssert(eigenValues[1] >= eigenValues[2]); + XA_DEBUG_ASSERT(eigenValues[0] >= eigenValues[1] && eigenValues[0] >= eigenValues[2]); + XA_DEBUG_ASSERT(eigenValues[1] >= eigenValues[2]); return true; } @@ -1118,7 +1755,7 @@ public: const FullVector &operator=(const FullVector &v) { - xaAssert(dimension() == v.dimension()); + XA_ASSERT(dimension() == v.dimension()); m_array = v.m_array; return *this; } @@ -1135,1698 +1772,1695 @@ public: } } - void operator+=(const FullVector &v) - { - xaDebugAssert(dimension() == v.dimension()); - const uint32_t dim = dimension(); - for (uint32_t i = 0; i < dim; i++) { - m_array[i] += v.m_array[i]; - } - } - - void operator-=(const FullVector &v) - { - xaDebugAssert(dimension() == v.dimension()); - const uint32_t dim = dimension(); - for (uint32_t i = 0; i < dim; i++) { - m_array[i] -= v.m_array[i]; - } - } - - void operator*=(const FullVector &v) - { - xaDebugAssert(dimension() == v.dimension()); - const uint32_t dim = dimension(); - for (uint32_t i = 0; i < dim; i++) { - m_array[i] *= v.m_array[i]; - } - } - - void operator+=(float f) - { - const uint32_t dim = dimension(); - for (uint32_t i = 0; i < dim; i++) { - m_array[i] += f; - } - } - - void operator-=(float f) - { - const uint32_t dim = dimension(); - for (uint32_t i = 0; i < dim; i++) { - m_array[i] -= f; - } - } - - void operator*=(float f) - { - const uint32_t dim = dimension(); - for (uint32_t i = 0; i < dim; i++) { - m_array[i] *= f; - } - } - private: - std::vector<float> m_array; + Array<float> m_array; }; -namespace halfedge { -class Face; -class Vertex; - -class Edge +template<typename Key, typename Value, typename H = Hash<Key>, typename E = Equal<Key> > +class HashMap { public: - uint32_t id; - Edge *next; - Edge *prev; // This is not strictly half-edge, but makes algorithms easier and faster. - Edge *pair; - Vertex *vertex; - Face *face; - - // Default constructor. - Edge(uint32_t id) : id(id), next(NULL), prev(NULL), pair(NULL), vertex(NULL), face(NULL) {} - - // Vertex queries. - const Vertex *from() const + HashMap(int memTag, uint32_t size) : m_memTag(memTag), m_size(size), m_numSlots(0), m_slots(nullptr), m_keys(memTag), m_values(memTag), m_next(memTag) { - return vertex; } - Vertex *from() + ~HashMap() { - return vertex; + if (m_slots) + XA_FREE(m_slots); } - const Vertex *to() const - { - return pair->vertex; // This used to be 'next->vertex', but that changed often when the connectivity of the mesh changes. - } + const Value &value(uint32_t index) const { return m_values[index]; } - Vertex *to() + void add(const Key &key, const Value &value) { - return pair->vertex; + if (!m_slots) + alloc(); + const uint32_t hash = computeHash(key); + m_keys.push_back(key); + m_values.push_back(value); + m_next.push_back(m_slots[hash]); + m_slots[hash] = m_next.size() - 1; } - // Edge queries. - void setNext(Edge *e) - { - next = e; - if (e != NULL) e->prev = this; - } - void setPrev(Edge *e) + uint32_t get(const Key &key) const { - prev = e; - if (e != NULL) e->next = this; + 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; } - // @@ It would be more simple to only check m_pair == NULL - // Face queries. - bool isBoundary() const + uint32_t getNext(uint32_t current) const { - return !(face && pair->face); + 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; } - // @@ This is not exactly accurate, we should compare the texture coordinates... - bool isSeam() const +private: + void alloc() { - return vertex != pair->next->vertex || next->vertex != pair->vertex; + XA_DEBUG_ASSERT(m_size > 0); + m_numSlots = (uint32_t)(m_size * 1.3); + m_slots = XA_ALLOC_ARRAY(m_memTag, uint32_t, m_numSlots); + for (uint32_t i = 0; i < m_numSlots; i++) + m_slots[i] = UINT32_MAX; + m_keys.reserve(m_size); + m_values.reserve(m_size); + m_next.reserve(m_size); } - bool isNormalSeam() const; - bool isTextureSeam() const; - - bool isValid() const + uint32_t computeHash(const Key &key) const { - // null face is OK. - if (next == NULL || prev == NULL || pair == NULL || vertex == NULL) return false; - if (next->prev != this) return false; - if (prev->next != this) return false; - if (pair->pair != this) return false; - return true; + H hash; + return hash(key) % m_numSlots; } - float length() const; - - // Return angle between this edge and the previous one. - float angle() const; + int m_memTag; + uint32_t m_size; + uint32_t m_numSlots; + uint32_t *m_slots; + Array<Key> m_keys; + Array<Value> m_values; + Array<uint32_t> m_next; }; -class Vertex +template<typename T> +static void insertionSort(T *data, uint32_t length) { -public: - uint32_t id; - // -- GODOT start -- - uint32_t original_id; - // -- GODOT end -- - Edge *edge; - Vertex *next; - Vertex *prev; - Vector3 pos; - Vector3 nor; - Vector2 tex; - - // -- GODOT start -- - //Vertex(uint32_t id) : id(id), edge(NULL), pos(0.0f), nor(0.0f), tex(0.0f) - Vertex(uint32_t id) : id(id), original_id(id), edge(NULL), pos(0.0f), nor(0.0f), tex(0.0f) - // -- GODOT end -- - { - next = this; - prev = this; - } - - // Set first edge of all colocals. - void setEdge(Edge *e) - { - for (VertexIterator it(colocals()); !it.isDone(); it.advance()) { - it.current()->edge = e; + for (int32_t i = 1; i < (int32_t)length; i++) { + T x = data[i]; + int32_t j = i - 1; + while (j >= 0 && x < data[j]) { + data[j + 1] = data[j]; + j--; } + data[j + 1] = x; } +} - // Update position of all colocals. - void setPos(const Vector3 &p) +class KISSRng +{ +public: + uint32_t getRange(uint32_t range) { - for (VertexIterator it(colocals()); !it.isDone(); it.advance()) { - it.current()->pos = p; - } + if (range == 0) + return 0; + x = 69069 * x + 12345; + y ^= (y << 13); + y ^= (y >> 17); + y ^= (y << 5); + uint64_t t = 698769069ULL * z + c; + c = (t >> 32); + return (x + y + (z = (uint32_t)t)) % range; } - bool isFirstColocal() const - { - return firstColocal() == this; - } +private: + uint32_t x = 123456789, y = 362436000, z = 521288629, c = 7654321; +}; + +// 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 +{ +public: + RadixSort() : m_size(0), m_ranks(nullptr), m_ranks2(nullptr), m_validRanks(false) {} - const Vertex *firstColocal() const + ~RadixSort() { - uint32_t firstId = id; - const Vertex *vertex = this; - for (ConstVertexIterator it(colocals()); !it.isDone(); it.advance()) { - if (it.current()->id < firstId) { - firstId = vertex->id; - vertex = it.current(); - } - } - return vertex; + // Release everything + XA_FREE(m_ranks2); + XA_FREE(m_ranks); } - Vertex *firstColocal() + RadixSort &sort(const float *input, uint32_t count) { - Vertex *vertex = this; - uint32_t firstId = id; - for (VertexIterator it(colocals()); !it.isDone(); it.advance()) { - if (it.current()->id < firstId) { - firstId = vertex->id; - vertex = it.current(); + if (input == nullptr || count == 0) return *this; + // Resize lists if needed + if (count != m_size) { + if (count > m_size) { + m_ranks2 = XA_REALLOC(MemTag::Default, m_ranks2, uint32_t, count); + m_ranks = XA_REALLOC(MemTag::Default, m_ranks, uint32_t, count); } + m_size = count; + m_validRanks = false; } - return vertex; - } - - bool isColocal(const Vertex *v) const - { - if (this == v) return true; - if (pos != v->pos) return false; - for (ConstVertexIterator it(colocals()); !it.isDone(); it.advance()) { - if (v == it.current()) { - return true; + if (count < 32) { + insertionSort(input, count); + } else { + // @@ Avoid touching the input multiple times. + for (uint32_t i = 0; i < count; i++) { + FloatFlip((uint32_t &)input[i]); + } + radixSort<uint32_t>((const uint32_t *)input, count); + for (uint32_t i = 0; i < count; i++) { + IFloatFlip((uint32_t &)input[i]); } } - return false; + return *this; } - void linkColocal(Vertex *v) - { - next->prev = v; - v->next = next; - next = v; - v->prev = this; - } - void unlinkColocal() + RadixSort &sort(const Array<float> &input) { - next->prev = prev; - prev->next = next; - next = this; - prev = this; + return sort(input.data(), input.size()); } - // @@ Note: This only works if linkBoundary has been called. - bool isBoundary() const + // 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 { - return (edge && !edge->face); + XA_DEBUG_ASSERT(m_validRanks); + return m_ranks; } - // Iterator that visits the edges around this vertex in counterclockwise order. - class EdgeIterator //: public Iterator<Edge *> + uint32_t *ranks() { - public: - EdgeIterator(Edge *e) : m_end(NULL), m_current(e) { } - - virtual void advance() - { - if (m_end == NULL) m_end = m_current; - m_current = m_current->pair->next; - //m_current = m_current->prev->pair; - } - - virtual bool isDone() const - { - return m_end == m_current; - } - virtual Edge *current() const - { - return m_current; - } - Vertex *vertex() const - { - return m_current->vertex; - } + XA_DEBUG_ASSERT(m_validRanks); + return m_ranks; + } - private: - Edge *m_end; - Edge *m_current; - }; +private: + uint32_t m_size; + uint32_t *m_ranks; + uint32_t *m_ranks2; + bool m_validRanks; - EdgeIterator edges() + void FloatFlip(uint32_t &f) { - return EdgeIterator(edge); + int32_t mask = (int32_t(f) >> 31) | 0x80000000; // Warren Hunt, Manchor Ko. + f ^= mask; } - EdgeIterator edges(Edge *e) + + void IFloatFlip(uint32_t &f) { - return EdgeIterator(e); + uint32_t mask = ((f >> 31) - 1) | 0x80000000; // Michael Herf. + f ^= mask; } - // Iterator that visits the edges around this vertex in counterclockwise order. - class ConstEdgeIterator //: public Iterator<Edge *> + template<typename T> + void createHistograms(const T *buffer, uint32_t count, uint32_t *histogram) { - public: - ConstEdgeIterator(const Edge *e) : m_end(NULL), m_current(e) { } - ConstEdgeIterator(EdgeIterator it) : m_end(NULL), m_current(it.current()) { } - - virtual void advance() - { - if (m_end == NULL) m_end = m_current; - m_current = m_current->pair->next; - //m_current = m_current->prev->pair; - } - - virtual bool isDone() const - { - return m_end == m_current; - } - virtual const Edge *current() const - { - return m_current; + const uint32_t bucketCount = sizeof(T); // (8 * sizeof(T)) / log2(radix) + // Init bucket pointers. + uint32_t *h[bucketCount]; + for (uint32_t i = 0; i < bucketCount; i++) { + h[i] = histogram + 256 * i; } - const Vertex *vertex() const - { - return m_current->to(); + // Clear histograms. + 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); + 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 } - - private: - const Edge *m_end; - const Edge *m_current; - }; - - ConstEdgeIterator edges() const - { - return ConstEdgeIterator(edge); - } - ConstEdgeIterator edges(const Edge *e) const - { - return ConstEdgeIterator(e); } - // Iterator that visits all the colocal vertices. - class VertexIterator //: public Iterator<Edge *> + template <typename T> void insertionSort(const T *input, uint32_t count) { - public: - VertexIterator(Vertex *v) : m_end(NULL), m_current(v) { } - - virtual void advance() - { - if (m_end == NULL) m_end = m_current; - m_current = m_current->next; - } - - virtual bool isDone() const - { - return m_end == m_current; - } - virtual Vertex *current() const - { - return m_current; + if (!m_validRanks) { + m_ranks[0] = 0; + for (uint32_t i = 1; i != count; ++i) { + int rank = m_ranks[i] = i; + uint32_t j = i; + while (j != 0 && input[rank] < input[m_ranks[j - 1]]) { + m_ranks[j] = m_ranks[j - 1]; + --j; + } + if (i != j) { + m_ranks[j] = rank; + } + } + m_validRanks = true; + } else { + for (uint32_t i = 1; i != count; ++i) { + int rank = m_ranks[i]; + uint32_t j = i; + while (j != 0 && input[rank] < input[m_ranks[j - 1]]) { + m_ranks[j] = m_ranks[j - 1]; + --j; + } + if (i != j) { + m_ranks[j] = rank; + } + } } - - private: - Vertex *m_end; - Vertex *m_current; - }; - - VertexIterator colocals() - { - return VertexIterator(this); } - // Iterator that visits all the colocal vertices. - class ConstVertexIterator //: public Iterator<Edge *> + template <typename T> void radixSort(const T *input, uint32_t count) { - public: - ConstVertexIterator(const Vertex *v) : m_end(NULL), m_current(v) { } - - virtual void advance() - { - if (m_end == NULL) m_end = m_current; - m_current = m_current->next; - } - - virtual bool isDone() const - { - return m_end == m_current; + const uint32_t P = sizeof(T); // pass count + // Allocate histograms & offsets on the stack + uint32_t histogram[256 * P]; + uint32_t *link[256]; + createHistograms(input, count, 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? + inputBytes += j; + if (h[inputBytes[0]] == count) { + // 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]; + // Perform Radix Sort + if (!m_validRanks) { + for (uint32_t i = 0; i < count; i++) { + *link[inputBytes[i * P]]++ = i; + } + m_validRanks = true; + } else { + for (uint32_t i = 0; i < count; i++) { + const uint32_t idx = m_ranks[i]; + *link[inputBytes[idx * P]]++ = idx; + } + } + // Swap pointers for next pass. Valid indices - the most recent ones - are in m_ranks after the swap. + swap(m_ranks, m_ranks2); } - virtual const Vertex *current() const - { - return m_current; + // All values were equal, generate linear ranks. + if (!m_validRanks) { + for (uint32_t i = 0; i < count; i++) { + m_ranks[i] = i; + } + m_validRanks = true; } - - private: - const Vertex *m_end; - const Vertex *m_current; - }; - - ConstVertexIterator colocals() const - { - return ConstVertexIterator(this); } }; -bool Edge::isNormalSeam() const -{ - return (vertex->nor != pair->next->vertex->nor || next->vertex->nor != pair->vertex->nor); -} - -bool Edge::isTextureSeam() const -{ - return (vertex->tex != pair->next->vertex->tex || next->vertex->tex != pair->vertex->tex); -} - -float Edge::length() const -{ - return internal::length(to()->pos - from()->pos); -} - -float Edge::angle() const -{ - Vector3 p = vertex->pos; - Vector3 a = prev->vertex->pos; - Vector3 b = next->vertex->pos; - Vector3 v0 = a - p; - Vector3 v1 = b - p; - return acosf(dot(v0, v1) / (internal::length(v0) * internal::length(v1))); -} - -class Face +// Wrapping this in a class allows temporary arrays to be re-used. +class BoundingBox2D { public: - uint32_t id; - uint16_t group; - uint16_t material; - Edge *edge; - - Face(uint32_t id) : id(id), group(uint16_t(~0)), material(uint16_t(~0)), edge(NULL) {} + Vector2 majorAxis() const { return m_majorAxis; } + Vector2 minorAxis() const { return m_minorAxis; } + Vector2 minCorner() const { return m_minCorner; } + Vector2 maxCorner() const { return m_maxCorner; } - float area() const - { - float area = 0; - const Vector3 &v0 = edge->from()->pos; - for (ConstEdgeIterator it(edges(edge->next)); it.current() != edge->prev; it.advance()) { - const Edge *e = it.current(); - const Vector3 &v1 = e->vertex->pos; - const Vector3 &v2 = e->next->vertex->pos; - area += length(cross(v1 - v0, v2 - v0)); - } - return area * 0.5f; - } - - float parametricArea() const - { - float area = 0; - const Vector2 &v0 = edge->from()->tex; - for (ConstEdgeIterator it(edges(edge->next)); it.current() != edge->prev; it.advance()) { - const Edge *e = it.current(); - const Vector2 &v1 = e->vertex->tex; - const Vector2 &v2 = e->next->vertex->tex; - area += triangleArea(v0, v1, v2); - } - return area * 0.5f; - } - - Vector3 normal() const + // This should compute convex hull and use rotating calipers to find the best box. Currently it uses a brute force method. + void compute(const Vector2 *boundaryVertices, uint32_t boundaryVertexCount, const Vector2 *vertices, uint32_t vertexCount) { - Vector3 n(0); - const Vertex *vertex0 = NULL; - for (ConstEdgeIterator it(edges()); !it.isDone(); it.advance()) { - const Edge *e = it.current(); - xaAssert(e != NULL); - if (vertex0 == NULL) { - vertex0 = e->vertex; - } else if (e->next->vertex != vertex0) { - const halfedge::Vertex *vertex1 = e->from(); - const halfedge::Vertex *vertex2 = e->to(); - const Vector3 &p0 = vertex0->pos; - const Vector3 &p1 = vertex1->pos; - const Vector3 &p2 = vertex2->pos; - Vector3 v10 = p1 - p0; - Vector3 v20 = p2 - p0; - n += cross(v10, v20); + convexHull(boundaryVertices, boundaryVertexCount, 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); + Vector2 best_max(0); + Vector2 best_axis(0); + const uint32_t hullCount = m_hull.size(); + 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); + 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++) { + const Vector2 &point = vertices[v]; + const float x = dot(axis, point); + const float y = dot(Vector2(-axis.y, axis.x), point); + box_min.x = min(box_min.x, x); + box_max.x = max(box_max.x, x); + box_min.y = min(box_min.y, y); + box_max.y = max(box_max.y, y); + } + // Compute box area. + const float area = (box_max.x - box_min.x) * (box_max.y - box_min.y); + if (area < best_area) { + best_area = area; + best_min = box_min; + best_max = box_max; + best_axis = axis; } } - return normalizeSafe(n, Vector3(0, 0, 1), 0.0f); - } - - Vector3 centroid() const - { - Vector3 sum(0.0f); - uint32_t count = 0; - for (ConstEdgeIterator it(edges()); !it.isDone(); it.advance()) { - const Edge *e = it.current(); - sum += e->from()->pos; - count++; - } - return sum / float(count); - } - - // Unnormalized face normal assuming it's a triangle. - Vector3 triangleNormal() const - { - Vector3 p0 = edge->vertex->pos; - Vector3 p1 = edge->next->vertex->pos; - Vector3 p2 = edge->next->next->vertex->pos; - Vector3 e0 = p2 - p0; - Vector3 e1 = p1 - p0; - return normalizeSafe(cross(e0, e1), Vector3(0), 0.0f); - } - - Vector3 triangleNormalAreaScaled() const - { - Vector3 p0 = edge->vertex->pos; - Vector3 p1 = edge->next->vertex->pos; - Vector3 p2 = edge->next->next->vertex->pos; - Vector3 e0 = p2 - p0; - Vector3 e1 = p1 - p0; - return cross(e0, e1); - } - - // Average of the edge midpoints weighted by the edge length. - // I want a point inside the triangle, but closer to the cirumcenter. - Vector3 triangleCenter() const - { - Vector3 p0 = edge->vertex->pos; - Vector3 p1 = edge->next->vertex->pos; - Vector3 p2 = edge->next->next->vertex->pos; - float l0 = length(p1 - p0); - float l1 = length(p2 - p1); - float l2 = length(p0 - p2); - Vector3 m0 = (p0 + p1) * l0 / (l0 + l1 + l2); - Vector3 m1 = (p1 + p2) * l1 / (l0 + l1 + l2); - Vector3 m2 = (p2 + p0) * l2 / (l0 + l1 + l2); - return m0 + m1 + m2; - } - - bool isValid() const - { - uint32_t count = 0; - for (ConstEdgeIterator it(edges()); !it.isDone(); it.advance()) { - const Edge *e = it.current(); - if (e->face != this) return false; - if (!e->isValid()) return false; - if (!e->pair->isValid()) return false; - count++; - } - if (count < 3) return false; - return true; - } - - bool contains(const Edge *e) const - { - for (ConstEdgeIterator it(edges()); !it.isDone(); it.advance()) { - if (it.current() == e) return true; - } - return false; - } - - uint32_t edgeCount() const - { - uint32_t count = 0; - for (ConstEdgeIterator it(edges()); !it.isDone(); it.advance()) { - ++count; - } - return count; + m_majorAxis = best_axis; + m_minorAxis = Vector2(-best_axis.y, best_axis.x); + m_minCorner = best_min; + m_maxCorner = best_max; + XA_ASSERT(isFinite(m_majorAxis) && isFinite(m_minorAxis) && isFinite(m_minCorner)); } - // The iterator that visits the edges of this face in clockwise order. - class EdgeIterator //: public Iterator<Edge *> +private: + // Compute the convex hull using Graham Scan. + void convexHull(const Vector2 *input, uint32_t inputCount, Array<Vector2> &output, float epsilon) { - public: - EdgeIterator(Edge *e) : m_end(NULL), m_current(e) { } - - virtual void advance() - { - if (m_end == NULL) m_end = m_current; - m_current = m_current->next; + m_coords.resize(inputCount); + for (uint32_t i = 0; i < inputCount; i++) + m_coords[i] = input[i].x; + RadixSort radix; + radix.sort(m_coords); + const uint32_t *ranks = radix.ranks(); + m_top.clear(); + m_bottom.clear(); + m_top.reserve(inputCount); + m_bottom.reserve(inputCount); + Vector2 P = input[ranks[0]]; + Vector2 Q = input[ranks[inputCount - 1]]; + float topy = max(P.y, Q.y); + float boty = min(P.y, Q.y); + for (uint32_t i = 0; i < inputCount; i++) { + Vector2 p = input[ranks[i]]; + if (p.y >= boty) + m_top.push_back(p); } - - virtual bool isDone() const - { - return m_end == m_current; + for (uint32_t i = 0; i < inputCount; i++) { + Vector2 p = input[ranks[inputCount - 1 - i]]; + if (p.y <= topy) + m_bottom.push_back(p); } - virtual Edge *current() const - { - return m_current; + // Filter top list. + output.clear(); + output.push_back(m_top[0]); + output.push_back(m_top[1]); + 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]; + float area = triangleArea(a, b, c); + if (area >= -epsilon) + output.pop_back(); + if (area < -epsilon || output.size() == 1) { + output.push_back(c); + i++; + } } - Vertex *vertex() const - { - return m_current->vertex; + uint32_t top_count = output.size(); + output.push_back(m_bottom[1]); + // Filter bottom list. + 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]; + float area = triangleArea(a, b, c); + if (area >= -epsilon) + output.pop_back(); + if (area < -epsilon || output.size() == top_count) { + output.push_back(c); + i++; + } } - - private: - Edge *m_end; - Edge *m_current; - }; - - EdgeIterator edges() - { - return EdgeIterator(edge); - } - EdgeIterator edges(Edge *e) - { - xaDebugAssert(contains(e)); - return EdgeIterator(e); + // Remove duplicate element. + XA_DEBUG_ASSERT(output.front() == output.back()); + output.pop_back(); } - // The iterator that visits the edges of this face in clockwise order. - class ConstEdgeIterator //: public Iterator<const Edge *> - { - public: - ConstEdgeIterator(const Edge *e) : m_end(NULL), m_current(e) { } - ConstEdgeIterator(const EdgeIterator &it) : m_end(NULL), m_current(it.current()) { } + Array<float> m_coords; + Array<Vector2> m_top, m_bottom, m_hull; + Vector2 m_majorAxis, m_minorAxis, m_minCorner, m_maxCorner; +}; - virtual void advance() - { - if (m_end == NULL) m_end = m_current; - m_current = m_current->next; - } +static uint32_t meshEdgeFace(uint32_t edge) { return edge / 3; } +static uint32_t meshEdgeIndex0(uint32_t edge) { return edge; } - virtual bool isDone() const - { - return m_end == m_current; - } - virtual const Edge *current() const - { - return m_current; - } - const Vertex *vertex() const - { - return m_current->vertex; - } - - private: - const Edge *m_end; - const Edge *m_current; - }; +static uint32_t meshEdgeIndex1(uint32_t edge) +{ + const uint32_t faceFirstEdge = edge / 3 * 3; + return faceFirstEdge + (edge - faceFirstEdge + 1) % 3; +} - ConstEdgeIterator edges() const - { - return ConstEdgeIterator(edge); - } - ConstEdgeIterator edges(const Edge *e) const +struct MeshFlags +{ + enum { - xaDebugAssert(contains(e)); - return ConstEdgeIterator(e); - } + HasFaceGroups = 1<<0, + HasIgnoredFaces = 1<<1, + HasNormals = 1<<2 + }; }; -/// Simple half edge mesh designed for dynamic mesh manipulation. +class Mesh; +static void meshGetBoundaryLoops(const Mesh &mesh, Array<uint32_t> &boundaryLoops); + class Mesh { public: - Mesh() : m_colocalVertexCount(0) {} - - Mesh(const Mesh *mesh) - { - // Copy mesh vertices. - const uint32_t vertexCount = mesh->vertexCount(); - m_vertexArray.resize(vertexCount); - for (uint32_t v = 0; v < vertexCount; v++) { - const Vertex *vertex = mesh->vertexAt(v); - xaDebugAssert(vertex->id == v); - m_vertexArray[v] = new Vertex(v); - m_vertexArray[v]->pos = vertex->pos; - m_vertexArray[v]->nor = vertex->nor; - m_vertexArray[v]->tex = vertex->tex; - } - m_colocalVertexCount = vertexCount; - // Copy mesh faces. - const uint32_t faceCount = mesh->faceCount(); - std::vector<uint32_t> indexArray; - indexArray.reserve(3); - for (uint32_t f = 0; f < faceCount; f++) { - const Face *face = mesh->faceAt(f); - for (Face::ConstEdgeIterator it(face->edges()); !it.isDone(); it.advance()) { - const Vertex *vertex = it.current()->from(); - indexArray.push_back(vertex->id); - } - addFace(indexArray); - indexArray.clear(); - } - } - - ~Mesh() + 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_faceGroups(MemTag::Mesh), m_indices(MemTag::MeshIndices), m_positions(MemTag::MeshPositions), m_normals(MemTag::MeshNormals), m_texcoords(MemTag::MeshTexcoords), m_colocalVertexCount(0), m_nextColocalVertex(MemTag::MeshColocals), m_boundaryVertices(MemTag::MeshBoundaries), m_oppositeEdges(MemTag::MeshBoundaries), m_nextBoundaryEdges(MemTag::MeshBoundaries), m_edgeMap(MemTag::MeshEdgeMap, approxFaceCount * 3) { - clear(); + m_indices.reserve(approxFaceCount * 3); + m_positions.reserve(approxVertexCount); + m_texcoords.reserve(approxVertexCount); + if (m_flags & MeshFlags::HasFaceGroups) + m_faceGroups.reserve(approxFaceCount); + if (m_flags & MeshFlags::HasIgnoredFaces) + m_faceIgnore.reserve(approxFaceCount); + if (m_flags & MeshFlags::HasNormals) + m_normals.reserve(approxVertexCount); } - void clear() - { - for (size_t i = 0; i < m_vertexArray.size(); i++) - delete m_vertexArray[i]; - m_vertexArray.clear(); - for (auto it = m_edgeMap.begin(); it != m_edgeMap.end(); it++) - delete it->second; - m_edgeArray.clear(); - m_edgeMap.clear(); - for (size_t i = 0; i < m_faceArray.size(); i++) - delete m_faceArray[i]; - m_faceArray.clear(); - } + uint32_t flags() const { return m_flags; } + uint32_t id() const { return m_id; } - Vertex *addVertex(const Vector3 &pos) + void addVertex(const Vector3 &pos, const Vector3 &normal = Vector3(0.0f), const Vector2 &texcoord = Vector2(0.0f)) { - xaDebugAssert(isFinite(pos)); - Vertex *v = new Vertex(m_vertexArray.size()); - v->pos = pos; - m_vertexArray.push_back(v); - return v; - } - - /// Link colocal vertices based on geometric location only. - void linkColocals() - { - xaPrint("--- Linking colocals:\n"); - const uint32_t vertexCount = this->vertexCount(); - std::unordered_map<Vector3, Vertex *, Hash<Vector3>, Equal<Vector3> > vertexMap; - vertexMap.reserve(vertexCount); - for (uint32_t v = 0; v < vertexCount; v++) { - Vertex *vertex = vertexAt(v); - Vertex *colocal = vertexMap[vertex->pos]; - if (colocal) { - colocal->linkColocal(vertex); - } else { - vertexMap[vertex->pos] = vertex; - } - } - m_colocalVertexCount = vertexMap.size(); - xaPrint("--- %d vertex positions.\n", m_colocalVertexCount); - // @@ Remove duplicated vertices? or just leave them as colocals? + XA_DEBUG_ASSERT(isFinite(pos)); + m_positions.push_back(pos); + if (m_flags & MeshFlags::HasNormals) + m_normals.push_back(normal); + m_texcoords.push_back(texcoord); } - void linkColocalsWithCanonicalMap(const std::vector<uint32_t> &canonicalMap) + struct AddFaceResult { - xaPrint("--- Linking colocals:\n"); - uint32_t vertexMapSize = 0; - for (uint32_t i = 0; i < canonicalMap.size(); i++) { - vertexMapSize = std::max(vertexMapSize, canonicalMap[i] + 1); - } - std::vector<Vertex *> vertexMap; - vertexMap.resize(vertexMapSize, NULL); - m_colocalVertexCount = 0; - const uint32_t vertexCount = this->vertexCount(); - for (uint32_t v = 0; v < vertexCount; v++) { - Vertex *vertex = vertexAt(v); - Vertex *colocal = vertexMap[canonicalMap[v]]; - if (colocal != NULL) { - xaDebugAssert(vertex->pos == colocal->pos); - colocal->linkColocal(vertex); - } else { - vertexMap[canonicalMap[v]] = vertex; - m_colocalVertexCount++; - } - } - xaPrint("--- %d vertex positions.\n", m_colocalVertexCount); - } - - Face *addFace() - { - Face *f = new Face(m_faceArray.size()); - m_faceArray.push_back(f); - return f; - } + enum Enum + { + OK, + DuplicateEdge = 1 + }; + }; - Face *addFace(uint32_t v0, uint32_t v1, uint32_t v2) + AddFaceResult::Enum addFace(uint32_t v0, uint32_t v1, uint32_t v2, bool ignore = false, bool hashEdge = true) { uint32_t indexArray[3]; indexArray[0] = v0; indexArray[1] = v1; indexArray[2] = v2; - return addFace(indexArray, 3, 0, 3); - } - - Face *addFace(uint32_t v0, uint32_t v1, uint32_t v2, uint32_t v3) - { - uint32_t indexArray[4]; - indexArray[0] = v0; - indexArray[1] = v1; - indexArray[2] = v2; - indexArray[3] = v3; - return addFace(indexArray, 4, 0, 4); + return addFace(indexArray, ignore, hashEdge); } - Face *addFace(const std::vector<uint32_t> &indexArray) + AddFaceResult::Enum addFace(const uint32_t *indices, bool ignore = false, bool hashEdge = true) { - return addFace(indexArray, 0, indexArray.size()); + AddFaceResult::Enum result = AddFaceResult::OK; + if (m_flags & MeshFlags::HasFaceGroups) + m_faceGroups.push_back(UINT32_MAX); + if (m_flags & MeshFlags::HasIgnoredFaces) + m_faceIgnore.push_back(ignore); + const uint32_t firstIndex = m_indices.size(); + for (uint32_t i = 0; i < 3; i++) + m_indices.push_back(indices[i]); + if (hashEdge) { + 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, firstIndex + i); + } + } + return result; } - Face *addFace(const std::vector<uint32_t> &indexArray, uint32_t first, uint32_t num) + void createColocals() { - return addFace(indexArray.data(), (uint32_t)indexArray.size(), first, num); + const uint32_t vertexCount = m_positions.size(); + Array<AABB> aabbs; + aabbs.resize(vertexCount); + for (uint32_t i = 0; i < m_positions.size(); i++) + aabbs[i] = AABB(m_positions[i], m_epsilon); + BVH bvh(aabbs); + Array<uint32_t> colocals; + Array<uint32_t> potential; + m_colocalVertexCount = 0; + m_nextColocalVertex.resize(vertexCount, UINT32_MAX); + 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. + bvh.query(AABB(m_positions[i], m_epsilon), potential); + for (uint32_t j = 0; j < potential.size(); j++) { + const uint32_t otherVertex = potential[j]; + if (otherVertex != i && equal(m_positions[i], m_positions[otherVertex], m_epsilon) && m_nextColocalVertex[otherVertex] == UINT32_MAX) + colocals.push_back(otherVertex); + } + if (colocals.size() == 1) { + // No colocals for this vertex. + m_nextColocalVertex[i] = i; + continue; + } + m_colocalVertexCount += colocals.size(); + // 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()]; + XA_DEBUG_ASSERT(m_nextColocalVertex[i] != UINT32_MAX); + } } - Face *addFace(const uint32_t *indexArray, uint32_t indexCount, uint32_t first, uint32_t num) + // Check if the face duplicates any edges of any face already in the group. + bool faceDuplicatesGroupEdge(uint32_t group, uint32_t face) const { - xaDebugAssert(first < indexCount); - xaDebugAssert(num <= indexCount - first); - xaDebugAssert(num > 2); - if (!canAddFace(indexArray, first, num)) { - return NULL; - } - Face *f = new Face(m_faceArray.size()); - Edge *firstEdge = NULL; - Edge *last = NULL; - Edge *current = NULL; - for (uint32_t i = 0; i < num - 1; i++) { - current = addEdge(indexArray[first + i], indexArray[first + i + 1]); - xaAssert(current != NULL && current->face == NULL); - current->face = f; - if (last != NULL) last->setNext(current); - else firstEdge = current; - last = current; + for (FaceEdgeIterator edgeIt(this, face); !edgeIt.isDone(); edgeIt.advance()) { + for (ColocalEdgeIterator colocalEdgeIt(this, edgeIt.vertex0(), edgeIt.vertex1()); !colocalEdgeIt.isDone(); colocalEdgeIt.advance()) { + if (m_faceGroups[meshEdgeFace(colocalEdgeIt.edge())] == group) + return true; + } } - current = addEdge(indexArray[first + num - 1], indexArray[first]); - xaAssert(current != NULL && current->face == NULL); - current->face = f; - last->setNext(current); - current->setNext(firstEdge); - f->edge = firstEdge; - m_faceArray.push_back(f); - return f; + return false; } - // -- GODOT start -- - Face *addUniqueFace(uint32_t v0, uint32_t v1, uint32_t v2) { - - int base_vertex = m_vertexArray.size(); - - uint32_t ids[3] = { v0, v1, v2 }; - - Vector3 base[3] = { - m_vertexArray[v0]->pos, - m_vertexArray[v1]->pos, - m_vertexArray[v2]->pos, - }; - - //make sure its not a degenerate - bool degenerate = distanceSquared(base[0], base[1]) < NV_EPSILON || distanceSquared(base[0], base[2]) < NV_EPSILON || distanceSquared(base[1], base[2]) < NV_EPSILON; - xaDebugAssert(!degenerate); - - float min_x = 0; - - for (int i = 0; i < 3; i++) { - if (i == 0 || m_vertexArray[v0]->pos.x < min_x) { - min_x = m_vertexArray[v0]->pos.x; + // Check if the face mirrors any face already in the group. + // i.e. don't want two-sided faces in the same group. + // A face mirrors another face if all edges match with opposite winding. + bool faceMirrorsGroupFace(uint32_t group, uint32_t face) const + { + FaceEdgeIterator edgeIt(this, face); + for (ColocalEdgeIterator colocalEdgeIt(this, edgeIt.vertex1(), edgeIt.vertex0()); !colocalEdgeIt.isDone(); colocalEdgeIt.advance()) { + const uint32_t candidateFace = meshEdgeFace(colocalEdgeIt.edge()); + if (m_faceGroups[candidateFace] == group) { + // Found a match for mirrored first edge, try the other edges. + bool match = false; + for (; !edgeIt.isDone(); edgeIt.advance()) { + match = false; + for (ColocalEdgeIterator colocalEdgeIt2(this, edgeIt.vertex1(), edgeIt.vertex0()); !colocalEdgeIt2.isDone(); colocalEdgeIt2.advance()) { + if (meshEdgeFace(colocalEdgeIt2.edge()) == candidateFace) { + match = true; + break; + } + } + if (!match) + break; + } + if (match) + return true; // All edges are mirrored in this face. + // Try the next face. + edgeIt = FaceEdgeIterator(this, candidateFace); } } + return false; + } - float max_x = 0; - - for (int j = 0; j < m_vertexArray.size(); j++) { - if (j == 0 || m_vertexArray[j]->pos.x > max_x) { //vertex already exists - max_x = m_vertexArray[j]->pos.x; + void createFaceGroups() + { + uint32_t group = 0; + Array<uint32_t> growFaces; + for (;;) { + // Find an unassigned face. + uint32_t face = UINT32_MAX; + for (uint32_t f = 0; f < faceCount(); f++) { + if (m_faceGroups[f] == UINT32_MAX && !isFaceIgnored(f)) { + face = f; + break; + } } + if (face == UINT32_MAX) + break; // All faces assigned to a group (except ignored faces). + m_faceGroups[face] = group; + growFaces.clear(); + growFaces.push_back(face); + // Find faces connected to the face and assign them to the same group as the face, unless they are already assigned to another group. + for (;;) { + if (growFaces.isEmpty()) + break; + const uint32_t f = growFaces.back(); + growFaces.pop_back(); + for (FaceEdgeIterator edgeIt(this, 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 (ColocalEdgeIterator oppositeEdgeIt(this, edgeIt.vertex1(), edgeIt.vertex0()); !oppositeEdgeIt.isDone(); oppositeEdgeIt.advance()) { + const uint32_t oppositeEdge = oppositeEdgeIt.edge(); + const uint32_t oppositeFace = meshEdgeFace(oppositeEdge); + if (isFaceIgnored(oppositeFace)) + continue; // Don't add ignored faces to group. + if (m_faceGroups[oppositeFace] == group) { + alreadyAssignedToThisGroup = true; + break; + } + if (m_faceGroups[oppositeFace] != UINT32_MAX) + continue; // Connected face is already assigned to another group. + if (faceDuplicatesGroupEdge(group, oppositeFace)) + continue; // Don't want duplicate edges in a group. + if (faceMirrorsGroupFace(group, oppositeFace)) + continue; // Don't want two-sided faces in a group. + const uint32_t oppositeVertex0 = m_indices[meshEdgeIndex0(oppositeEdge)]; + const uint32_t oppositeVertex1 = m_indices[meshEdgeIndex1(oppositeEdge)]; + if (bestConnectedFace == UINT32_MAX || (oppositeVertex0 == edgeIt.vertex1() && oppositeVertex1 == edgeIt.vertex0())) + bestConnectedFace = oppositeFace; + } + if (!alreadyAssignedToThisGroup && bestConnectedFace != UINT32_MAX) { + m_faceGroups[bestConnectedFace] = group; + growFaces.push_back(bestConnectedFace); + } + } + } + group++; } - - //separate from everything else, in x axis - for (int i = 0; i < 3; i++) { - - base[i].x -= min_x; - base[i].x += max_x + 10.0; - } - - for (int i = 0; i < 3; i++) { - Vertex *v = new Vertex(m_vertexArray.size()); - v->pos = base[i]; - v->nor = m_vertexArray[ids[i]]->nor, - v->tex = m_vertexArray[ids[i]]->tex, - - v->original_id = ids[i]; - m_vertexArray.push_back(v); - } - - uint32_t indexArray[3]; - indexArray[0] = base_vertex + 0; - indexArray[1] = base_vertex + 1; - indexArray[2] = base_vertex + 2; - return addFace(indexArray, 3, 0, 3); } - // -- GODOT end -- - - // These functions disconnect the given element from the mesh and delete it. - // @@ We must always disconnect edge pairs simultaneously. - void disconnect(Edge *edge) + void createBoundaries() { - xaDebugAssert(edge != NULL); - // Remove from edge list. - if ((edge->id & 1) == 0) { - xaDebugAssert(m_edgeArray[edge->id / 2] == edge); - m_edgeArray[edge->id / 2] = NULL; - } - // Remove edge from map. @@ Store map key inside edge? - xaDebugAssert(edge->from() != NULL && edge->to() != NULL); - size_t removed = m_edgeMap.erase(Key(edge->from()->id, edge->to()->id)); - xaDebugAssert(removed == 1); -#ifdef NDEBUG - removed = 0; // silence unused parameter warning + const uint32_t edgeCount = m_indices.size(); + const uint32_t vertexCount = m_positions.size(); + m_oppositeEdges.resize(edgeCount); + m_boundaryVertices.resize(vertexCount); + for (uint32_t i = 0; i < edgeCount; i++) + m_oppositeEdges[i] = UINT32_MAX; + for (uint32_t i = 0; i < vertexCount; i++) + m_boundaryVertices[i] = false; + const bool hasFaceGroups = m_flags & MeshFlags::HasFaceGroups; + for (uint32_t i = 0; i < faceCount(); i++) { + if (isFaceIgnored(i)) + continue; + for (uint32_t j = 0; j < 3; j++) { + const uint32_t vertex0 = m_indices[i * 3 + j]; + const uint32_t vertex1 = m_indices[i * 3 + (j + 1) % 3]; + // If there is an edge with opposite winding to this one, the edge isn't on a boundary. + const uint32_t oppositeEdge = findEdge(hasFaceGroups ? m_faceGroups[i] : UINT32_MAX, vertex1, vertex0); + if (oppositeEdge != UINT32_MAX) { +#if XA_DEBUG + if (hasFaceGroups) + XA_DEBUG_ASSERT(m_faceGroups[meshEdgeFace(oppositeEdge)] == m_faceGroups[i]); #endif - // Disconnect from vertex. - if (edge->vertex != NULL) { - if (edge->vertex->edge == edge) { - if (edge->prev && edge->prev->pair) { - edge->vertex->edge = edge->prev->pair; - } else if (edge->pair && edge->pair->next) { - edge->vertex->edge = edge->pair->next; + XA_DEBUG_ASSERT(!isFaceIgnored(meshEdgeFace(oppositeEdge))); + m_oppositeEdges[i * 3 + j] = oppositeEdge; } else { - edge->vertex->edge = NULL; - // @@ Remove disconnected vertex? + m_boundaryVertices[vertex0] = m_boundaryVertices[vertex1] = true; } } } - // Disconnect from face. - if (edge->face != NULL) { - if (edge->face->edge == edge) { - if (edge->next != NULL && edge->next != edge) { - edge->face->edge = edge->next; - } else if (edge->prev != NULL && edge->prev != edge) { - edge->face->edge = edge->prev; - } else { - edge->face->edge = NULL; - // @@ Remove disconnected face? + } + + void linkBoundaries() + { + const uint32_t edgeCount = m_indices.size(); + HashMap<uint32_t, uint32_t> vertexToEdgeMap(MemTag::Mesh, edgeCount); + for (uint32_t i = 0; i < edgeCount; i++) { + const uint32_t vertex0 = m_indices[meshEdgeIndex0(i)]; + const uint32_t vertex1 = m_indices[meshEdgeIndex1(i)]; + vertexToEdgeMap.add(vertex0, i); + vertexToEdgeMap.add(vertex1, 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.clearAll(); + 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.bitAt(i)) { + firstEdge = i; + break; } } - } - // Disconnect from previous. - if (edge->prev) { - if (edge->prev->next == edge) { - edge->prev->setNext(NULL); + 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 mapOtherEdgeIndex = vertexToEdgeMap.get(it.vertex()); + while (mapOtherEdgeIndex != UINT32_MAX) { + const uint32_t otherEdge = vertexToEdgeMap.value(mapOtherEdgeIndex); + if (m_oppositeEdges[otherEdge] != UINT32_MAX) + goto next; // Not a boundary edge. + if (linkedEdges.bitAt(otherEdge)) + goto next; // Already linked. + if (m_flags & MeshFlags::HasFaceGroups && m_faceGroups[meshEdgeFace(currentEdge)] != m_faceGroups[meshEdgeFace(otherEdge)]) + goto next; // Don't cross face groups. + if (isFaceIgnored(meshEdgeFace(otherEdge))) + goto next; // Face is ignored. + 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: + mapOtherEdgeIndex = vertexToEdgeMap.getNext(mapOtherEdgeIndex); + } + } + if (bestNextEdge == UINT32_MAX) { + numUnclosedBoundaries++; + if (currentEdge == firstEdge) + linkedEdges.setBitAt(firstEdge); // Only 1 edge in this boundary "loop". + break; // Can't find a next edge. + } + m_nextBoundaryEdges[currentEdge] = bestNextEdge; + linkedEdges.setBitAt(bestNextEdge); + currentEdge = bestNextEdge; + if (currentEdge == firstEdge) { + numBoundaryLoops++; + break; // Closed the boundary loop. + } } - //edge->setPrev(NULL); } - // Disconnect from next. - if (edge->next) { - if (edge->next->prev == edge) { - edge->next->setPrev(NULL); + // 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.clearAll(); + for (Mesh::BoundaryEdgeIterator it1(this, boundaryLoops[loop]); !it1.isDone(); it1.advance()) { + const uint32_t e1 = it1.edge(); + if (linkedEdges.bitAt(e1)) + continue; + for (Mesh::BoundaryEdgeIterator it2(this, boundaryLoops[loop]); !it2.isDone(); it2.advance()) { + const uint32_t e2 = it2.edge(); + if (e1 == e2 || !isBoundaryEdge(e2) || linkedEdges.bitAt(e2)) + continue; + if (!areColocal(m_indices[meshEdgeIndex1(e1)], m_indices[meshEdgeIndex1(e2)])) + continue; + swap(m_nextBoundaryEdges[e1], m_nextBoundaryEdges[e2]); + linkedEdges.setBitAt(e1); + linkedEdges.setBitAt(e2); + goto fixInternalBoundary; // start over + } } - //edge->setNext(NULL); } } - void remove(Edge *edge) - { - xaDebugAssert(edge != NULL); - disconnect(edge); - delete edge; + /// Find edge, test all colocals. + uint32_t findEdge(uint32_t faceGroup, uint32_t vertex0, uint32_t vertex1) const + { + uint32_t result = UINT32_MAX; + if (m_nextColocalVertex.isEmpty()) { + EdgeKey key(vertex0, vertex1); + uint32_t mapEdgeIndex = m_edgeMap.get(key); + while (mapEdgeIndex != UINT32_MAX) { + const uint32_t edge = m_edgeMap.value(mapEdgeIndex); + // Don't find edges of ignored faces. + if ((faceGroup == UINT32_MAX || m_faceGroups[meshEdgeFace(edge)] == faceGroup) && !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 + } + mapEdgeIndex = m_edgeMap.getNext(mapEdgeIndex); + } + } 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()); + uint32_t mapEdgeIndex = m_edgeMap.get(key); + while (mapEdgeIndex != UINT32_MAX) { + const uint32_t edge = m_edgeMap.value(mapEdgeIndex); + // Don't find edges of ignored faces. + if ((faceGroup == UINT32_MAX || m_faceGroups[meshEdgeFace(edge)] == faceGroup) && !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 + } + mapEdgeIndex = m_edgeMap.getNext(mapEdgeIndex); + } + } + } + } + return result; } - void remove(Vertex *vertex) +#if XA_DEBUG_EXPORT_OBJ + void writeObjVertices(FILE *file) const { - xaDebugAssert(vertex != NULL); - // Remove from vertex list. - m_vertexArray[vertex->id] = NULL; - // Disconnect from colocals. - vertex->unlinkColocal(); - // Disconnect from edges. - if (vertex->edge != NULL) { - // @@ Removing a connected vertex is asking for trouble... - if (vertex->edge->vertex == vertex) { - // @@ Connect edge to a colocal? - vertex->edge->vertex = NULL; - } - vertex->setEdge(NULL); + 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) { + for (uint32_t i = 0; i < m_normals.size(); i++) + fprintf(file, "vn %g %g %g\n", m_normals[i].x, m_normals[i].y, m_normals[i].z); } - delete vertex; + for (uint32_t i = 0; i < m_texcoords.size(); i++) + fprintf(file, "vt %g %g\n", m_texcoords[i].x, m_texcoords[i].y); } - void remove(Face *face) + void writeObjFace(FILE *file, uint32_t face) const { - xaDebugAssert(face != NULL); - // Remove from face list. - m_faceArray[face->id] = NULL; - // Disconnect from edges. - if (face->edge != NULL) { - xaDebugAssert(face->edge->face == face); - face->edge->face = NULL; - face->edge = NULL; + fprintf(file, "f "); + for (uint32_t j = 0; j < 3; j++) { + const uint32_t index = m_indices[face * 3 + j] + 1; // 1-indexed + fprintf(file, "%d/%d/%d%c", index, index, index, j == 2 ? '\n' : ' '); } - delete face; } - // Triangulate in place. - void triangulate() + void writeObjBoundaryEges(FILE *file) const { - bool all_triangles = true; - const uint32_t faceCount = m_faceArray.size(); - for (uint32_t f = 0; f < faceCount; f++) { - Face *face = m_faceArray[f]; - if (face->edgeCount() != 3) { - all_triangles = false; - break; - } - } - if (all_triangles) { - return; - } - // Do not touch vertices, but rebuild edges and faces. - std::vector<Edge *> edgeArray; - std::vector<Face *> faceArray; - std::swap(edgeArray, m_edgeArray); - std::swap(faceArray, m_faceArray); - m_edgeMap.clear(); - for (uint32_t f = 0; f < faceCount; f++) { - Face *face = faceArray[f]; - // Trivial fan-like triangulation. - const uint32_t v0 = face->edge->vertex->id; - uint32_t v2, v1 = (uint32_t)-1; - for (Face::EdgeIterator it(face->edges()); !it.isDone(); it.advance()) { - Edge *edge = it.current(); - v2 = edge->to()->id; - if (v2 == v0) break; - if (v1 != -1) addFace(v0, v1, v2); - v1 = v2; - } - } - xaDebugAssert(m_faceArray.size() > faceCount); // triangle count > face count - linkBoundary(); - for (size_t i = 0; i < edgeArray.size(); i++) - delete edgeArray[i]; - for (size_t i = 0; i < faceArray.size(); i++) - delete faceArray[i]; - } - - /// Link boundary edges once the mesh has been created. - void linkBoundary() - { - xaPrint("--- Linking boundaries:\n"); - int num = 0; - // Create boundary edges. - uint32_t edgeCount = this->edgeCount(); - for (uint32_t e = 0; e < edgeCount; e++) { - Edge *edge = edgeAt(e); - if (edge != NULL && edge->pair == NULL) { - Edge *pair = new Edge(edge->id + 1); - uint32_t i = edge->from()->id; - uint32_t j = edge->next->from()->id; - Key key(j, i); - xaAssert(m_edgeMap.find(key) == m_edgeMap.end()); - pair->vertex = m_vertexArray[j]; - m_edgeMap[key] = pair; - edge->pair = pair; - pair->pair = edge; - num++; - } - } - // Link boundary edges. - for (uint32_t e = 0; e < edgeCount; e++) { - Edge *edge = edgeAt(e); - if (edge != NULL && edge->pair->face == NULL) { - linkBoundaryEdge(edge->pair); + if (m_oppositeEdges.isEmpty()) + return; // Boundaries haven't been created. + fprintf(file, "o boundary_edges\n"); + for (uint32_t i = 0; i < edgeCount(); i++) { + if (m_oppositeEdges[i] != UINT32_MAX) + continue; + fprintf(file, "l %d %d\n", m_indices[meshEdgeIndex0(i)] + 1, m_indices[meshEdgeIndex1(i)] + 1); // 1-indexed + } + } + + 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; + } } } - xaPrint("--- %d boundary edges.\n", num); } - /* - Fixing T-junctions. + void writeObjFile(const char *filename) const + { + FILE *file; + XA_FOPEN(file, filename, "w"); + if (!file) + return; + writeObjVertices(file); + fprintf(file, "s off\n"); + fprintf(file, "o object\n"); + for (uint32_t i = 0; i < faceCount(); i++) + writeObjFace(file, i); + writeObjBoundaryEges(file); + writeObjLinkedBoundaries(file); + fclose(file); + } +#endif - - 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. + float computeSurfaceArea() const + { + float area = 0; + for (uint32_t f = 0; f < faceCount(); f++) + area += faceArea(f); + XA_DEBUG_ASSERT(area >= 0); + return area; + } - */ - bool splitBoundaryEdges() // Returns true if any split was made. - { - std::vector<Vertex *> boundaryVertices; - for (uint32_t i = 0; i < m_vertexArray.size(); i++) { - Vertex *v = m_vertexArray[i]; - if (v->isBoundary()) { - boundaryVertices.push_back(v); - } - } - xaPrint("Fixing T-junctions:\n"); - int splitCount = 0; - for (uint32_t v = 0; v < boundaryVertices.size(); v++) { - Vertex *vertex = boundaryVertices[v]; - Vector3 x0 = vertex->pos; - // Find edges that this vertex overlaps with. - for (uint32_t e = 0; e < m_edgeArray.size(); e++) { - Edge *edge = m_edgeArray[e]; - if (edge != NULL && edge->isBoundary()) { - if (edge->from() == vertex || edge->to() == vertex) { - continue; - } - Vector3 x1 = edge->from()->pos; - Vector3 x2 = edge->to()->pos; - Vector3 v01 = x0 - x1; - Vector3 v21 = x2 - x1; - float l = length(v21); - float d = length(cross(v01, v21)) / l; - if (isZero(d)) { - float t = dot(v01, v21) / (l * l); - if (t > 0.0f + NV_EPSILON && t < 1.0f - NV_EPSILON) { - xaDebugAssert(equal(lerp(x1, x2, t), x0)); - Vertex *splitVertex = splitBoundaryEdge(edge, t, x0); - vertex->linkColocal(splitVertex); // @@ Should we do this here? - splitCount++; - } - } - } - } - } - xaPrint(" - %d edges split.\n", splitCount); - xaDebugAssert(isValid()); - return splitCount != 0; + float computeParametricArea() const + { + float area = 0; + for (uint32_t f = 0; f < faceCount(); f++) + area += faceParametricArea(f); + return fabsf(area); // May be negative, depends on texcoord winding. } - // Vertices - uint32_t vertexCount() const + float faceArea(uint32_t face) const { - return m_vertexArray.size(); + 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; } - const Vertex *vertexAt(int i) const + + Vector3 faceCentroid(uint32_t face) const { - return m_vertexArray[i]; + Vector3 sum(0.0f); + for (uint32_t i = 0; i < 3; i++) + sum += m_positions[m_indices[face * 3 + i]]; + return sum / 3.0f; } - Vertex *vertexAt(int i) + + Vector3 calculateFaceNormal(uint32_t face) const { - return m_vertexArray[i]; + return normalizeSafe(triangleNormalAreaScaled(face), Vector3(0, 0, 1), 0.0f); } - uint32_t colocalVertexCount() const + float faceParametricArea(uint32_t face) const { - return m_colocalVertexCount; + 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) * 0.5f; + } + + // Average of the edge midpoints weighted by the edge length. + // I want a point inside the triangle, but closer to the cirumcenter. + Vector3 triangleCenter(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 float l0 = length(p1 - p0); + const float l1 = length(p2 - p1); + const float l2 = length(p0 - p2); + const Vector3 m0 = (p0 + p1) * l0 / (l0 + l1 + l2); + const Vector3 m1 = (p1 + p2) * l1 / (l0 + l1 + l2); + const Vector3 m2 = (p2 + p0) * l2 / (l0 + l1 + l2); + return m0 + m1 + m2; } - // Faces - uint32_t faceCount() const + // Unnormalized face normal assuming it's a triangle. + Vector3 triangleNormal(uint32_t face) const { - return m_faceArray.size(); + return normalizeSafe(triangleNormalAreaScaled(face), Vector3(0), 0.0f); } - const Face *faceAt(int i) const + + Vector3 triangleNormalAreaScaled(uint32_t face) const { - return m_faceArray[i]; + 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; + return cross(e0, e1); } - Face *faceAt(int i) + + // @@ This is not exactly accurate, we should compare the texture coordinates... + bool isSeam(uint32_t edge) const { - return m_faceArray[i]; + const uint32_t oppositeEdge = m_oppositeEdges[edge]; + if (oppositeEdge == UINT32_MAX) + return false; // boundary edge + const uint32_t e0 = meshEdgeIndex0(edge); + const uint32_t e1 = meshEdgeIndex1(edge); + const uint32_t oe0 = meshEdgeIndex0(oppositeEdge); + const uint32_t oe1 = meshEdgeIndex1(oppositeEdge); + return m_indices[e0] != m_indices[oe1] || m_indices[e1] != m_indices[oe0]; } - // Edges - uint32_t edgeCount() const + bool isTextureSeam(uint32_t edge) const { - return m_edgeArray.size(); + const uint32_t oppositeEdge = m_oppositeEdges[edge]; + if (oppositeEdge == UINT32_MAX) + return false; // boundary edge + const uint32_t e0 = meshEdgeIndex0(edge); + const uint32_t e1 = meshEdgeIndex1(edge); + const uint32_t oe0 = meshEdgeIndex0(oppositeEdge); + const uint32_t oe1 = meshEdgeIndex1(oppositeEdge); + return m_texcoords[m_indices[e0]] != m_texcoords[m_indices[oe1]] || m_texcoords[m_indices[e1]] != m_texcoords[m_indices[oe0]]; } - const Edge *edgeAt(int i) const + + uint32_t firstColocal(uint32_t vertex) const { - return m_edgeArray[i]; + for (ColocalVertexIterator it(this, vertex); !it.isDone(); it.advance()) { + if (it.vertex() < vertex) + vertex = it.vertex(); + } + return vertex; } - Edge *edgeAt(int i) + + bool areColocal(uint32_t vertex0, uint32_t vertex1) const { - return m_edgeArray[i]; + 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; } - class ConstVertexIterator; + float epsilon() const { return m_epsilon; } + uint32_t edgeCount() const { return m_indices.size(); } + uint32_t oppositeEdge(uint32_t edge) const { return m_oppositeEdges[edge]; } + bool isBoundaryEdge(uint32_t edge) const { return m_oppositeEdges[edge] == UINT32_MAX; } + bool isBoundaryVertex(uint32_t vertex) const { return m_boundaryVertices[vertex]; } + uint32_t colocalVertexCount() const { return m_colocalVertexCount; } + uint32_t vertexCount() const { return m_positions.size(); } + uint32_t vertexAt(uint32_t i) const { return m_indices[i]; } + const Vector3 &position(uint32_t vertex) const { return m_positions[vertex]; } + const Vector3 &normal(uint32_t vertex) const { XA_DEBUG_ASSERT(m_flags & MeshFlags::HasNormals); return m_normals[vertex]; } + const Vector2 &texcoord(uint32_t vertex) const { return m_texcoords[vertex]; } + Vector2 &texcoord(uint32_t vertex) { return m_texcoords[vertex]; } + Vector2 *texcoords() { return m_texcoords.data(); } + uint32_t faceCount() const { return m_indices.size() / 3; } + uint32_t faceGroupCount() const { XA_DEBUG_ASSERT(m_flags & MeshFlags::HasFaceGroups); return m_faceGroups.size(); } + uint32_t faceGroupAt(uint32_t face) const { XA_DEBUG_ASSERT(m_flags & MeshFlags::HasFaceGroups); return m_faceGroups[face]; } + const uint32_t *indices() const { return m_indices.data(); } + uint32_t indexCount() const { return m_indices.size(); } + +private: + bool isFaceIgnored(uint32_t face) const { return (m_flags & MeshFlags::HasIgnoredFaces) && m_faceIgnore[face]; } + + float m_epsilon; + uint32_t m_flags; + uint32_t m_id; + Array<bool> m_faceIgnore; + Array<uint32_t> m_faceGroups; + Array<uint32_t> m_indices; + Array<Vector3> m_positions; + Array<Vector3> m_normals; + Array<Vector2> m_texcoords; + + // Populated by createColocals + uint32_t m_colocalVertexCount; + Array<uint32_t> m_nextColocalVertex; // In: vertex index. Out: the vertex index of the next colocal position. + + // Populated by createBoundaries + Array<bool> m_boundaryVertices; + 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. - class VertexIterator + // 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 { - friend class ConstVertexIterator; - public: - VertexIterator(Mesh *mesh) : m_mesh(mesh), m_current(0) { } + EdgeKey() {} + EdgeKey(const EdgeKey &k) : v0(k.v0), v1(k.v1) {} + EdgeKey(uint32_t v0, uint32_t v1) : v0(v0), v1(v1) {} - virtual void advance() + void operator=(const EdgeKey &k) { - m_current++; + v0 = k.v0; + v1 = k.v1; } - virtual bool isDone() const + bool operator==(const EdgeKey &k) const { - return m_current == m_mesh->vertexCount(); - } - virtual Vertex *current() const - { - return m_mesh->vertexAt(m_current); + return v0 == k.v0 && v1 == k.v1; } - private: - halfedge::Mesh *m_mesh; - uint32_t m_current; + uint32_t v0; + uint32_t v1; }; - VertexIterator vertices() - { - return VertexIterator(this); - } - class ConstVertexIterator + HashMap<EdgeKey, uint32_t> m_edgeMap; + +public: + class BoundaryEdgeIterator { public: - ConstVertexIterator(const Mesh *mesh) : m_mesh(mesh), m_current(0) { } - ConstVertexIterator(class VertexIterator &it) : m_mesh(it.m_mesh), m_current(it.m_current) { } + BoundaryEdgeIterator(const Mesh *mesh, uint32_t edge) : m_mesh(mesh), m_first(UINT32_MAX), m_current(edge) {} - virtual void advance() + void advance() { - m_current++; + if (m_first == UINT32_MAX) + m_first = m_current; + m_current = m_mesh->m_nextBoundaryEdges[m_current]; } - virtual bool isDone() const + + bool isDone() const { - return m_current == m_mesh->vertexCount(); + return m_first == m_current || m_current == UINT32_MAX; } - virtual const Vertex *current() const + + uint32_t edge() const { - return m_mesh->vertexAt(m_current); + return m_current; + } + + uint32_t nextEdge() const + { + return m_mesh->m_nextBoundaryEdges[m_current]; } private: - const halfedge::Mesh *m_mesh; + const Mesh *m_mesh; + uint32_t m_first; uint32_t m_current; }; - ConstVertexIterator vertices() const - { - return ConstVertexIterator(this); - } - class ConstFaceIterator; - - class FaceIterator + class ColocalVertexIterator { - friend class ConstFaceIterator; public: - FaceIterator(Mesh *mesh) : m_mesh(mesh), m_current(0) { } + ColocalVertexIterator(const Mesh *mesh, uint32_t v) : m_mesh(mesh), m_first(UINT32_MAX), m_current(v) {} - virtual void advance() + void advance() { - m_current++; + if (m_first == UINT32_MAX) + m_first = m_current; + if (!m_mesh->m_nextColocalVertex.isEmpty()) + m_current = m_mesh->m_nextColocalVertex[m_current]; } - virtual bool isDone() const + + bool isDone() const { - return m_current == m_mesh->faceCount(); + return m_first == m_current; } - virtual Face *current() const + + uint32_t vertex() const { - return m_mesh->faceAt(m_current); + return m_current; + } + + const Vector3 *pos() const + { + return &m_mesh->m_positions[m_current]; } private: - halfedge::Mesh *m_mesh; + const Mesh *m_mesh; + uint32_t m_first; uint32_t m_current; }; - FaceIterator faces() - { - return FaceIterator(this); - } - class ConstFaceIterator + class ColocalEdgeIterator { public: - ConstFaceIterator(const Mesh *mesh) : m_mesh(mesh), m_current(0) { } - ConstFaceIterator(const FaceIterator &it) : m_mesh(it.m_mesh), m_current(it.m_current) { } + ColocalEdgeIterator(const Mesh *mesh, uint32_t vertex0, uint32_t vertex1) : m_mesh(mesh), m_vertex0It(mesh, vertex0), m_vertex1It(mesh, vertex1), m_vertex1(vertex1) + { + resetElement(); + } - virtual void advance() + void advance() { - m_current++; + advanceElement(); } - virtual bool isDone() const + + bool isDone() const { - return m_current == m_mesh->faceCount(); + return m_vertex0It.isDone() && m_vertex1It.isDone() && m_mapEdgeIndex == UINT32_MAX; } - virtual const Face *current() const + + uint32_t edge() const { - return m_mesh->faceAt(m_current); + return m_mesh->m_edgeMap.value(m_mapEdgeIndex); } private: - const halfedge::Mesh *m_mesh; - uint32_t m_current; - }; - ConstFaceIterator faces() const - { - return ConstFaceIterator(this); - } - - class ConstEdgeIterator; + void resetElement() + { + m_mapEdgeIndex = m_mesh->m_edgeMap.get(Mesh::EdgeKey(m_vertex0It.vertex(), m_vertex1It.vertex())); + while (m_mapEdgeIndex != UINT32_MAX) { + if (!isIgnoredFace()) + break; + m_mapEdgeIndex = m_mesh->m_edgeMap.getNext(m_mapEdgeIndex); + } + if (m_mapEdgeIndex == UINT32_MAX) + advanceVertex1(); + } - class EdgeIterator - { - friend class ConstEdgeIterator; - public: - EdgeIterator(Mesh *mesh) : m_mesh(mesh), m_current(0) { } + void advanceElement() + { + for (;;) { + m_mapEdgeIndex = m_mesh->m_edgeMap.getNext(m_mapEdgeIndex); + if (m_mapEdgeIndex == UINT32_MAX) + break; + if (!isIgnoredFace()) + break; + } + if (m_mapEdgeIndex == UINT32_MAX) + advanceVertex1(); + } - virtual void advance() + void advanceVertex0() { - m_current++; + m_vertex0It.advance(); + if (m_vertex0It.isDone()) + return; + m_vertex1It = ColocalVertexIterator(m_mesh, m_vertex1); + resetElement(); } - virtual bool isDone() const + + void advanceVertex1() { - return m_current == m_mesh->edgeCount(); + m_vertex1It.advance(); + if (m_vertex1It.isDone()) + advanceVertex0(); + else + resetElement(); } - virtual Edge *current() const + + bool isIgnoredFace() const { - return m_mesh->edgeAt(m_current); + const uint32_t edge = m_mesh->m_edgeMap.value(m_mapEdgeIndex); + return m_mesh->m_faceIgnore[meshEdgeFace(edge)]; } - private: - halfedge::Mesh *m_mesh; - uint32_t m_current; + const Mesh *m_mesh; + ColocalVertexIterator m_vertex0It, m_vertex1It; + const uint32_t m_vertex1; + uint32_t m_mapEdgeIndex; }; - EdgeIterator edges() - { - return EdgeIterator(this); - } - class ConstEdgeIterator + class FaceEdgeIterator { public: - ConstEdgeIterator(const Mesh *mesh) : m_mesh(mesh), m_current(0) { } - ConstEdgeIterator(const EdgeIterator &it) : m_mesh(it.m_mesh), m_current(it.m_current) { } + FaceEdgeIterator (const Mesh *mesh, uint32_t face) : m_mesh(mesh), m_face(face), m_relativeEdge(0) + { + m_edge = m_face * 3; + } - virtual void advance() + void advance() { - m_current++; + if (m_relativeEdge < 3) { + m_edge++; + m_relativeEdge++; + } } - virtual bool isDone() const + + bool isDone() const + { + return m_relativeEdge == 3; + } + + bool isBoundary() const { return m_mesh->m_oppositeEdges[m_edge] == UINT32_MAX; } + bool isSeam() const { return m_mesh->isSeam(m_edge); } + bool isTextureSeam() const { return m_mesh->isTextureSeam(m_edge); } + uint32_t edge() const { return m_edge; } + 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 { - return m_current == m_mesh->edgeCount(); + const uint32_t oedge = m_mesh->m_oppositeEdges[m_edge]; + if (oedge == UINT32_MAX) + return UINT32_MAX; + return meshEdgeFace(oedge); } - virtual const Edge *current() const + + uint32_t vertex0() const { - return m_mesh->edgeAt(m_current); + return m_mesh->m_indices[m_face * 3 + m_relativeEdge]; } + uint32_t vertex1() const + { + return m_mesh->m_indices[m_face * 3 + (m_relativeEdge + 1) % 3]; + } + + const Vector3 &position0() const { return m_mesh->m_positions[vertex0()]; } + const Vector3 &position1() const { return m_mesh->m_positions[vertex1()]; } + const Vector3 &normal0() const { return m_mesh->m_normals[vertex0()]; } + const Vector3 &normal1() const { return m_mesh->m_normals[vertex1()]; } + const Vector2 &texcoord0() const { return m_mesh->m_texcoords[vertex0()]; } + const Vector2 &texcoord1() const { return m_mesh->m_texcoords[vertex1()]; } + private: - const halfedge::Mesh *m_mesh; - uint32_t m_current; + const Mesh *m_mesh; + uint32_t m_face; + uint32_t m_edge; + uint32_t m_relativeEdge; }; - ConstEdgeIterator edges() const - { - return ConstEdgeIterator(this); - } - - // @@ Add half-edge iterator. +}; - bool isValid() const - { - // Make sure all edges are valid. - const uint32_t edgeCount = m_edgeArray.size(); - for (uint32_t e = 0; e < edgeCount; e++) { - Edge *edge = m_edgeArray[e]; - if (edge != NULL) { - if (edge->id != 2 * e) { - return false; +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] = acosf(dot(edge1, edge2) / (length(edge1) * length(edge2))); + if (frontAngles[i] >= smallestAngle || isNan(frontAngles[i])) + continue; + // Don't duplicate edges. + if (mesh->findEdge(UINT32_MAX, frontVertices[i1], frontVertices[i2]) != UINT32_MAX) + continue; + if (mesh->findEdge(UINT32_MAX, frontVertices[i2], frontVertices[i3]) != UINT32_MAX) + continue; + if (mesh->findEdge(UINT32_MAX, 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 (!edge->isValid()) { - return false; + } + 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 (edge->pair->id != 2 * e + 1) { - return false; + } + 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 (!edge->pair->isValid()) { - return false; + } + 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; } - // @@ Make sure all faces are valid. - // @@ Make sure all vertices are valid. - return true; + // 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; +} - // Error status: - - struct ErrorCode - { - enum Enum - { - AlreadyAddedEdge, - DegenerateColocalEdge, - DegenerateEdge, - DuplicateEdge - }; - }; - - mutable ErrorCode::Enum errorCode; - mutable uint32_t errorIndex0; - mutable uint32_t errorIndex1; +static bool meshCloseHoles(Mesh *mesh, const Array<uint32_t> &boundaryLoops, const Vector3 &normal, Array<uint32_t> &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::BoundaryEdgeIterator 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::BoundaryEdgeIterator 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. + holeFaceCounts.push_back(mesh->faceCount() - oldFaceCount); + } + return result; +} -private: - // Return true if the face can be added to the manifold mesh. - bool canAddFace(const std::vector<uint32_t> &indexArray, uint32_t first, uint32_t num) const - { - return canAddFace(indexArray.data(), first, num); +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; +} - bool canAddFace(const uint32_t *indexArray, uint32_t first, uint32_t num) const - { - for (uint32_t j = num - 1, i = 0; i < num; j = i++) { - if (!canAddEdge(indexArray[first + j], indexArray[first + i])) { - errorIndex0 = indexArray[first + j]; - errorIndex1 = indexArray[first + i]; - return false; - } - } - // We also have to make sure the face does not have any duplicate edge! - for (uint32_t i = 0; i < num; i++) { - int i0 = indexArray[first + i + 0]; - int i1 = indexArray[first + (i + 1) % num]; - for (uint32_t j = i + 1; j < num; j++) { - int j0 = indexArray[first + j + 0]; - int j1 = indexArray[first + (j + 1) % num]; - if (i0 == j0 && i1 == j1) { - errorCode = ErrorCode::DuplicateEdge; - errorIndex0 = i0; - errorIndex1 = i1; - return false; - } - } - } - return true; - } +/* +Fixing T-junctions. - // Return true if the edge doesn't exist or doesn't have any adjacent face. - bool canAddEdge(uint32_t i, uint32_t j) const - { - if (i == j) { - // Skip degenerate edges. - errorCode = ErrorCode::DegenerateEdge; - return false; - } - // Same check, but taking into account colocal vertices. - const Vertex *v0 = vertexAt(i); - const Vertex *v1 = vertexAt(j); - for (Vertex::ConstVertexIterator it(v0->colocals()); !it.isDone(); it.advance()) { - if (it.current() == v1) { - // Skip degenerate edges. - errorCode = ErrorCode::DegenerateColocalEdge; - return false; - } - } - // Make sure edge has not been added yet. - Edge *edge = findEdge(i, j); - // We ignore edges that don't have an adjacent face yet, since this face could become the edge's face. - if (!(edge == NULL || edge->face == NULL)) { - errorCode = ErrorCode::AlreadyAddedEdge; - return false; - } - return true; - } +- 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; - Edge *addEdge(uint32_t i, uint32_t j) + bool operator<(const SplitEdge &other) const { - xaAssert(i != j); - Edge *edge = findEdge(i, j); - if (edge != NULL) { - // Edge may already exist, but its face must not be set. - xaDebugAssert(edge->face == NULL); - // Nothing else to do! - } else { - // Add new edge. - // Lookup pair. - Edge *pair = findEdge(j, i); - if (pair != NULL) { - // Create edge with same id. - edge = new Edge(pair->id + 1); - // Link edge pairs. - edge->pair = pair; - pair->pair = edge; - // @@ I'm not sure this is necessary! - pair->vertex->setEdge(pair); - } else { - // Create edge. - edge = new Edge(2 * m_edgeArray.size()); - // Add only unpaired edges. - m_edgeArray.push_back(edge); - } - edge->vertex = m_vertexArray[i]; - m_edgeMap[Key(i, j)] = edge; + if (edge < other.edge) + return true; + else if (edge == other.edge) { + if (t < other.t) + return true; } - // Face and Next are set by addFace. - return edge; + return false; } +}; - /// Find edge, test all colocals. - Edge *findEdge(uint32_t i, uint32_t j) const - { - Edge *edge = NULL; - const Vertex *v0 = vertexAt(i); - const Vertex *v1 = vertexAt(j); - // Test all colocal pairs. - for (Vertex::ConstVertexIterator it0(v0->colocals()); !it0.isDone(); it0.advance()) { - for (Vertex::ConstVertexIterator it1(v1->colocals()); !it1.isDone(); it1.advance()) { - Key key(it0.current()->id, it1.current()->id); - if (edge == NULL) { - auto edgeIt = m_edgeMap.find(key); - if (edgeIt != m_edgeMap.end()) - edge = (*edgeIt).second; - #if !defined(_DEBUG) - if (edge != NULL) return edge; - #endif - } else { - // Make sure that only one edge is found. - xaDebugAssert(m_edgeMap.find(key) == m_edgeMap.end()); +// 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; + const uint32_t vertexCount = inputMesh.vertexCount(); + const uint32_t edgeCount = inputMesh.edgeCount(); + for (uint32_t v = 0; v < vertexCount; v++) { + if (!inputMesh.isBoundaryVertex(v)) + continue; + // Find edges that this vertex overlaps with. + const Vector3 &pos = inputMesh.position(v); + for (uint32_t e = 0; e < edgeCount; e++) { + if (!inputMesh.isBoundaryEdge(e)) + continue; + 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 (splitEdges.isEmpty()) + return nullptr; + const uint32_t faceCount = inputMesh.faceCount(); + Mesh *mesh = XA_NEW(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); } } + 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; + } } - return edge; - } - - /// Link this boundary edge. - void linkBoundaryEdge(Edge *edge) - { - xaAssert(edge->face == NULL); - // Make sure next pointer has not been set. @@ We want to be able to relink boundary edges after mesh changes. - Edge *next = edge; - while (next->pair->face != NULL) { - // Get pair prev - Edge *e = next->pair->next; - while (e->next != next->pair) { - e = e->next; - } - next = e; - } - edge->setNext(next->pair); - // Adjust vertex edge, so that it's the boundary edge. (required for isBoundary()) - if (edge->vertex->edge != edge) { - // Multiple boundaries in the same edge. - edge->vertex->edge = edge; - } - } - - Vertex *splitBoundaryEdge(Edge *edge, float t, const Vector3 &pos) - { - /* - We want to go from this configuration: - - + + - | ^ - edge |<->| pair - v | - + + - - To this one: - - + + - | ^ - e0 |<->| p0 - v | - vertex + + - | ^ - e1 |<->| p1 - v | - + + - - */ - Edge *pair = edge->pair; - // Make sure boundaries are linked. - xaDebugAssert(pair != NULL); - // Make sure edge is a boundary edge. - xaDebugAssert(pair->face == NULL); - // Add new vertex. - Vertex *vertex = addVertex(pos); - vertex->nor = lerp(edge->from()->nor, edge->to()->nor, t); - vertex->tex = lerp(edge->from()->tex, edge->to()->tex, t); - disconnect(edge); - disconnect(pair); - // Add edges. - Edge *e0 = addEdge(edge->from()->id, vertex->id); - Edge *p0 = addEdge(vertex->id, pair->to()->id); - Edge *e1 = addEdge(vertex->id, edge->to()->id); - Edge *p1 = addEdge(pair->from()->id, vertex->id); - // Link edges. - e0->setNext(e1); - p1->setNext(p0); - e0->setPrev(edge->prev); - e1->setNext(edge->next); - p1->setPrev(pair->prev); - p0->setNext(pair->next); - xaDebugAssert(e0->next == e1); - xaDebugAssert(e1->prev == e0); - xaDebugAssert(p1->next == p0); - xaDebugAssert(p0->prev == p1); - xaDebugAssert(p0->pair == e0); - xaDebugAssert(e0->pair == p0); - xaDebugAssert(p1->pair == e1); - xaDebugAssert(e1->pair == p1); - // Link faces. - e0->face = edge->face; - e1->face = edge->face; - // Link vertices. - edge->from()->setEdge(e0); - vertex->setEdge(e1); - delete edge; - delete pair; - return vertex; } + if (fixedTJunctionsCount) + *fixedTJunctionsCount = splitEdges.size(); + return mesh; +} -private: - std::vector<Vertex *> m_vertexArray; - std::vector<Edge *> m_edgeArray; - std::vector<Face *> m_faceArray; - - struct Key - { - Key() {} - Key(const Key &k) : p0(k.p0), p1(k.p1) {} - Key(uint32_t v0, uint32_t v1) : p0(v0), p1(v1) {} - void operator=(const Key &k) - { - p0 = k.p0; - p1 = k.p1; - } - bool operator==(const Key &k) const - { - return p0 == k.p0 && p1 == k.p1; - } - - uint32_t p0; - uint32_t p1; - }; - - friend struct Hash<Mesh::Key>; - std::unordered_map<Key, Edge *, Hash<Key>, Equal<Key> > m_edgeMap; - uint32_t m_colocalVertexCount; -}; +// 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.clearAll(); + 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.bitAt(e) || !mesh.isBoundaryEdge(e)) + continue; + for (Mesh::BoundaryEdgeIterator it(&mesh, e); !it.isDone(); it.advance()) + bitFlags.setBitAt(it.edge()); + boundaryLoops.push_back(e); + } +} class MeshTopology { public: MeshTopology(const Mesh *mesh) { - buildTopologyInfo(mesh); - } - - /// Determine if the mesh is connected. - bool isConnected() const - { - return m_connectedCount == 1; - } - - /// Determine if the mesh is closed. (Each edge is shared by two faces) - bool isClosed() const - { - return m_boundaryCount == 0; - } - - /// Return true if the mesh has the topology of a disk. - bool isDisk() const - { - return isConnected() && m_boundaryCount == 1/* && m_eulerNumber == 1*/; - } - -private: - void buildTopologyInfo(const Mesh *mesh) - { const uint32_t vertexCount = mesh->colocalVertexCount(); const uint32_t faceCount = mesh->faceCount(); const uint32_t edgeCount = mesh->edgeCount(); - xaPrint( "--- Building mesh topology:\n" ); - std::vector<uint32_t> stack(faceCount); + Array<uint32_t> stack(MemTag::Default); + stack.reserve(faceCount); BitArray bitFlags(faceCount); bitFlags.clearAll(); // Compute connectivity. - xaPrint( "--- Computing connectivity.\n" ); m_connectedCount = 0; for (uint32_t f = 0; f < faceCount; f++ ) { - if ( bitFlags.bitAt(f) == false ) { + if (bitFlags.bitAt(f) == false) { m_connectedCount++; - stack.push_back( f ); - while ( !stack.empty() ) { + stack.push_back(f); + while (!stack.isEmpty()) { const uint32_t top = stack.back(); - xaAssert(top != uint32_t(~0)); + XA_ASSERT(top != uint32_t(~0)); stack.pop_back(); - if ( bitFlags.bitAt(top) == false ) { + if (bitFlags.bitAt(top) == false) { bitFlags.setBitAt(top); - const Face *face = mesh->faceAt(top); - const Edge *firstEdge = face->edge; - const Edge *edge = firstEdge; - do { - const Face *neighborFace = edge->pair->face; - if (neighborFace != NULL) { - stack.push_back(neighborFace->id); - } - edge = edge->next; - } while (edge != firstEdge); + for (Mesh::FaceEdgeIterator it(mesh, top); !it.isDone(); it.advance()) { + const uint32_t oppositeFace = it.oppositeFace(); + if (oppositeFace != UINT32_MAX) + stack.push_back(oppositeFace); + } } } } } - xaAssert(stack.empty()); - xaPrint( "--- %d connected components.\n", m_connectedCount ); + XA_ASSERT(stack.isEmpty()); // Count boundary loops. - xaPrint( "--- Counting boundary loops.\n" ); m_boundaryCount = 0; bitFlags.resize(edgeCount); bitFlags.clearAll(); // Don't forget to link the boundary otherwise this won't work. for (uint32_t e = 0; e < edgeCount; e++) { - const Edge *startEdge = mesh->edgeAt(e); - if (startEdge != NULL && startEdge->isBoundary() && bitFlags.bitAt(e) == false) { - xaDebugAssert(startEdge->face != NULL); - xaDebugAssert(startEdge->pair->face == NULL); - startEdge = startEdge->pair; - m_boundaryCount++; - const Edge *edge = startEdge; - do { - bitFlags.setBitAt(edge->id / 2); - edge = edge->next; - } while (startEdge != edge); - } - } - xaPrint("--- %d boundary loops found.\n", m_boundaryCount ); + if (bitFlags.bitAt(e) || !mesh->isBoundaryEdge(e)) + continue; + m_boundaryCount++; + for (Mesh::BoundaryEdgeIterator it(mesh, e); !it.isDone(); it.advance()) + bitFlags.setBitAt(it.edge()); + } // Compute euler number. m_eulerNumber = vertexCount - edgeCount + faceCount; - xaPrint("--- Euler number: %d.\n", m_eulerNumber); // Compute genus. (only valid on closed connected surfaces) m_genus = -1; - if ( isClosed() && isConnected() ) { + if (isClosed() && isConnected()) m_genus = (2 - m_eulerNumber) / 2; - xaPrint("--- Genus: %d.\n", m_genus); - } + } + + /// Determine if the mesh is connected. + bool isConnected() const + { + return m_connectedCount == 1; + } + + /// Determine if the mesh is closed. (Each edge is shared by two faces) + bool isClosed() const + { + return m_boundaryCount == 0; + } + + /// Return true if the mesh has the topology of a disk. + bool isDisk() const + { + return isConnected() && m_boundaryCount == 1/* && m_eulerNumber == 1*/; } private: @@ -2843,702 +3477,313 @@ private: int m_genus; }; -float computeSurfaceArea(const halfedge::Mesh *mesh) -{ - float area = 0; - for (halfedge::Mesh::ConstFaceIterator it(mesh->faces()); !it.isDone(); it.advance()) { - const halfedge::Face *face = it.current(); - area += face->area(); - } - xaDebugAssert(area >= 0); - return area; -} - -float computeParametricArea(const halfedge::Mesh *mesh) -{ - float area = 0; - for (halfedge::Mesh::ConstFaceIterator it(mesh->faces()); !it.isDone(); it.advance()) { - const halfedge::Face *face = it.current(); - area += face->parametricArea(); - } - return area; -} - -uint32_t countMeshTriangles(const Mesh *mesh) -{ - const uint32_t faceCount = mesh->faceCount(); - uint32_t triangleCount = 0; - for (uint32_t f = 0; f < faceCount; f++) { - const Face *face = mesh->faceAt(f); - uint32_t edgeCount = face->edgeCount(); - xaDebugAssert(edgeCount > 2); - triangleCount += edgeCount - 2; - } - return triangleCount; -} - -Mesh *unifyVertices(const Mesh *inputMesh) -{ - Mesh *mesh = new Mesh; - // Only add the first colocal. - const uint32_t vertexCount = inputMesh->vertexCount(); - for (uint32_t v = 0; v < vertexCount; v++) { - const Vertex *vertex = inputMesh->vertexAt(v); - if (vertex->isFirstColocal()) { - mesh->addVertex(vertex->pos); - } - } - std::vector<uint32_t> indexArray; - // Add new faces pointing to first colocals. - uint32_t faceCount = inputMesh->faceCount(); - for (uint32_t f = 0; f < faceCount; f++) { - const Face *face = inputMesh->faceAt(f); - indexArray.clear(); - for (Face::ConstEdgeIterator it(face->edges()); !it.isDone(); it.advance()) { - const Edge *edge = it.current(); - const Vertex *vertex = edge->vertex->firstColocal(); - indexArray.push_back(vertex->id); - } - mesh->addFace(indexArray); - } - mesh->linkBoundary(); - return mesh; -} - -static bool pointInTriangle(const Vector2 &p, const Vector2 &a, const Vector2 &b, const Vector2 &c) +struct Progress { - return triangleArea(a, b, p) >= 0.00001f && - triangleArea(b, c, p) >= 0.00001f && - triangleArea(c, a, p) >= 0.00001f; -} - -// 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. -Mesh *triangulate(const Mesh *inputMesh) -{ - Mesh *mesh = new Mesh; - // Add all vertices. - const uint32_t vertexCount = inputMesh->vertexCount(); - for (uint32_t v = 0; v < vertexCount; v++) { - const Vertex *vertex = inputMesh->vertexAt(v); - mesh->addVertex(vertex->pos); - } - std::vector<int> polygonVertices; - std::vector<float> polygonAngles; - std::vector<Vector2> polygonPoints; - const uint32_t faceCount = inputMesh->faceCount(); - for (uint32_t f = 0; f < faceCount; f++) { - const Face *face = inputMesh->faceAt(f); - xaDebugAssert(face != NULL); - const uint32_t edgeCount = face->edgeCount(); - xaDebugAssert(edgeCount >= 3); - polygonVertices.clear(); - polygonVertices.reserve(edgeCount); - if (edgeCount == 3) { - // Simple case for triangles. - for (Face::ConstEdgeIterator it(face->edges()); !it.isDone(); it.advance()) { - const Edge *edge = it.current(); - const Vertex *vertex = edge->vertex; - polygonVertices.push_back(vertex->id); - } - int v0 = polygonVertices[0]; - int v1 = polygonVertices[1]; - int v2 = polygonVertices[2]; - mesh->addFace(v0, v1, v2); - } 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. - const Vector3 fn = face->normal(); - Basis basis; - basis.buildFrameForDirection(fn); - polygonPoints.clear(); - polygonPoints.reserve(edgeCount); - polygonAngles.clear(); - polygonAngles.reserve(edgeCount); - for (Face::ConstEdgeIterator it(face->edges()); !it.isDone(); it.advance()) { - const Edge *edge = it.current(); - const Vertex *vertex = edge->vertex; - polygonVertices.push_back(vertex->id); - Vector2 p; - p.x = dot(basis.tangent, vertex->pos); - p.y = dot(basis.bitangent, vertex->pos); - polygonPoints.push_back(p); - } - polygonAngles.resize(edgeCount); - while (polygonVertices.size() > 2) { - uint32_t size = polygonVertices.size(); - // Update polygon angles. @@ Update only those that have changed. - float minAngle = 2 * PI; - 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 = polygonPoints[i0]; - Vector2 p1 = polygonPoints[i1]; - Vector2 p2 = polygonPoints[i2]; - - // -- GODOT start -- - bool degenerate = distance(p0, p1) < NV_EPSILON || distance(p0, p2) < NV_EPSILON || distance(p1, p2) < NV_EPSILON; - if (degenerate) { - continue; - } - // -- GODOT end -- - - 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 = 2.0f * PI - angle; - 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 = polygonPoints[j]; - if (pointInTriangle(p, p0, p1, p2)) { - valid = false; - break; - } - } - if (valid || !bestIsValid) { - minAngle = angle; - bestEar = i1; - bestIsValid = valid; - } - } - } - // -- GODOT start -- - if (!bestIsValid) - break; - // -- GODOT end -- - - xaDebugAssert(minAngle <= 2 * PI); - // Clip best ear: - uint32_t i0 = (bestEar + size - 1) % size; - uint32_t i1 = (bestEar + 0) % size; - uint32_t i2 = (bestEar + 1) % size; - int v0 = polygonVertices[i0]; - int v1 = polygonVertices[i1]; - int v2 = polygonVertices[i2]; - mesh->addFace(v0, v1, v2); - polygonVertices.erase(polygonVertices.begin() + i1); - polygonPoints.erase(polygonPoints.begin() + i1); - polygonAngles.erase(polygonAngles.begin() + i1); - } - } - } - mesh->linkBoundary(); - return mesh; -} - -} // namespace halfedge - -/// Mersenne twister random number generator. -class MTRand -{ -public: - enum time_e { Time }; - enum { N = 624 }; // length of state vector - enum { M = 397 }; - - /// Constructor that uses the current time as the seed. - MTRand( time_e ) + 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) { - seed((uint32_t )time(NULL)); - } - - /// Constructor that uses the given seed. - MTRand( uint32_t s = 0 ) - { - seed(s); - } - - /// Provide a new seed. - void seed( uint32_t s ) - { - initialize(s); - reload(); + if (m_func) { + if (!m_func(category, 0, userData)) + cancel = true; + } } - /// Get a random number between 0 - 65536. - uint32_t get() + ~Progress() { - // Pull a 32-bit integer from the generator state - // Every other access function simply transforms the numbers extracted here - if ( left == 0 ) { - reload(); + if (m_func) { + if (!m_func(m_category, 100, m_userData)) + cancel = true; } - left--; - uint32_t s1; - s1 = *next++; - s1 ^= (s1 >> 11); - s1 ^= (s1 << 7) & 0x9d2c5680U; - s1 ^= (s1 << 15) & 0xefc60000U; - return ( s1 ^ (s1 >> 18) ); - }; - - /// Get a random number on [0, max] interval. - uint32_t getRange( uint32_t max ) - { - if (max == 0) return 0; - if (max == NV_UINT32_MAX) return get(); - const uint32_t np2 = nextPowerOfTwo( max + 1 ); // @@ This fails if max == NV_UINT32_MAX - const uint32_t mask = np2 - 1; - uint32_t n; - do { - n = get() & mask; - } while ( n > max ); - return n; } -private: - void initialize( uint32_t seed ) + void update() { - // Initialize generator state with seed - // See Knuth TAOCP Vol 2, 3rd Ed, p.106 for multiplier. - // In previous versions, most significant bits (MSBs) of the seed affect - // only MSBs of the state array. Modified 9 Jan 2002 by Makoto Matsumoto. - uint32_t *s = state; - uint32_t *r = state; - int i = 1; - *s++ = seed & 0xffffffffUL; - for ( ; i < N; ++i ) { - *s++ = ( 1812433253UL * ( *r ^ (*r >> 30) ) + i ) & 0xffffffffUL; - r++; + 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 reload() + void setMaxValue(uint32_t maxValue) { - // Generate N new values in state - // Made clearer and faster by Matthew Bellew (matthew.bellew@home.com) - uint32_t *p = state; - int i; - for ( i = N - M; i--; ++p ) - *p = twist( p[M], p[0], p[1] ); - for ( i = M; --i; ++p ) - *p = twist( p[M - N], p[0], p[1] ); - *p = twist( p[M - N], p[0], state[0] ); - left = N, next = state; + m_mutex.lock(); + m_maxValue = maxValue; + m_mutex.unlock(); } - uint32_t hiBit( uint32_t u ) const - { - return u & 0x80000000U; - } - uint32_t loBit( uint32_t u ) const - { - return u & 0x00000001U; - } - uint32_t loBits( uint32_t u ) const - { - return u & 0x7fffffffU; - } - uint32_t mixBits( uint32_t u, uint32_t v ) const - { - return hiBit(u) | loBits(v); - } - uint32_t twist( uint32_t m, uint32_t s0, uint32_t s1 ) const - { - return m ^ (mixBits(s0, s1) >> 1) ^ ((~loBit(s1) + 1) & 0x9908b0dfU); - } + std::atomic<uint32_t> value; + std::atomic<bool> cancel; - uint32_t state[N]; // internal state - uint32_t *next; // next value to get from state - int left; // number of values left before reload needed +private: + ProgressCategory::Enum m_category; + ProgressFunc m_func; + void *m_userData; + uint32_t m_maxValue; + uint32_t m_progress; + std::mutex m_mutex; }; -namespace morton { -// Code from ryg: -// http://fgiesen.wordpress.com/2009/12/13/decoding-morton-codes/ - -// Inverse of part1By1 - "delete" all odd-indexed bits -uint32_t compact1By1(uint32_t x) -{ - x &= 0x55555555; // x = -f-e -d-c -b-a -9-8 -7-6 -5-4 -3-2 -1-0 - x = (x ^ (x >> 1)) & 0x33333333; // x = --fe --dc --ba --98 --76 --54 --32 --10 - x = (x ^ (x >> 2)) & 0x0f0f0f0f; // x = ---- fedc ---- ba98 ---- 7654 ---- 3210 - x = (x ^ (x >> 4)) & 0x00ff00ff; // x = ---- ---- fedc ba98 ---- ---- 7654 3210 - x = (x ^ (x >> 8)) & 0x0000ffff; // x = ---- ---- ---- ---- fedc ba98 7654 3210 - return x; -} - -// Inverse of part1By2 - "delete" all bits not at positions divisible by 3 -uint32_t compact1By2(uint32_t x) -{ - x &= 0x09249249; // x = ---- 9--8 --7- -6-- 5--4 --3- -2-- 1--0 - x = (x ^ (x >> 2)) & 0x030c30c3; // x = ---- --98 ---- 76-- --54 ---- 32-- --10 - x = (x ^ (x >> 4)) & 0x0300f00f; // x = ---- --98 ---- ---- 7654 ---- ---- 3210 - x = (x ^ (x >> 8)) & 0xff0000ff; // x = ---- --98 ---- ---- ---- ---- 7654 3210 - x = (x ^ (x >> 16)) & 0x000003ff; // x = ---- ---- ---- ---- ---- --98 7654 3210 - return x; -} - -uint32_t decodeMorton2X(uint32_t code) -{ - return compact1By1(code >> 0); -} - -uint32_t decodeMorton2Y(uint32_t code) -{ - return compact1By1(code >> 1); -} - -uint32_t decodeMorton3X(uint32_t code) +struct TaskGroupHandle { - return compact1By2(code >> 0); -} + uint32_t value = UINT32_MAX; +}; -uint32_t decodeMorton3Y(uint32_t code) +struct Task { - return compact1By2(code >> 1); -} + void (*func)(void *userData); + void *userData; +}; -uint32_t decodeMorton3Z(uint32_t code) +#if XA_MULTITHREADED +class TaskScheduler { - return compact1By2(code >> 2); -} -} // namespace morton - -// A simple, dynamic proximity grid based on Jon's code. -// Instead of storing pointers here I store indices. -struct ProximityGrid -{ - void init(const Box &box, uint32_t count) - { - cellArray.clear(); - // Determine grid size. - float cellWidth; - Vector3 diagonal = box.extents() * 2.f; - float volume = box.volume(); - if (equal(volume, 0)) { - // Degenerate box, treat like a quad. - Vector2 quad; - if (diagonal.x < diagonal.y && diagonal.x < diagonal.z) { - quad.x = diagonal.y; - quad.y = diagonal.z; - } else if (diagonal.y < diagonal.x && diagonal.y < diagonal.z) { - quad.x = diagonal.x; - quad.y = diagonal.z; - } else { - quad.x = diagonal.x; - quad.y = diagonal.y; - } - float cellArea = quad.x * quad.y / count; - cellWidth = sqrtf(cellArea); // pow(cellArea, 1.0f / 2.0f); - } else { - // Ideally we want one cell per point. - float cellVolume = volume / count; - cellWidth = powf(cellVolume, 1.0f / 3.0f); +public: + TaskScheduler() : m_shutdown(false) + { + m_workers.resize(std::thread::hardware_concurrency() <= 1 ? 1 : std::thread::hardware_concurrency() - 1); + for (uint32_t i = 0; i < m_workers.size(); i++) { + m_workers[i].wakeup = false; + m_workers[i].thread = XA_NEW(MemTag::Default, std::thread, workerThread, this, &m_workers[i]); } - xaDebugAssert(cellWidth != 0); - sx = std::max(1, ftoi_ceil(diagonal.x / cellWidth)); - sy = std::max(1, ftoi_ceil(diagonal.y / cellWidth)); - sz = std::max(1, ftoi_ceil(diagonal.z / cellWidth)); - invCellSize.x = float(sx) / diagonal.x; - invCellSize.y = float(sy) / diagonal.y; - invCellSize.z = float(sz) / diagonal.z; - cellArray.resize(sx * sy * sz); - corner = box.minCorner; // @@ Align grid better? } - int index_x(float x) const + ~TaskScheduler() { - return clamp(ftoi_floor((x - corner.x) * invCellSize.x), 0, sx - 1); + m_shutdown = true; + for (uint32_t i = 0; i < m_workers.size(); i++) { + Worker &worker = m_workers[i]; + XA_DEBUG_ASSERT(worker.thread); + worker.wakeup = true; + worker.cv.notify_one(); + if (worker.thread->joinable()) + worker.thread->join(); + worker.thread->~thread(); + XA_FREE(worker.thread); + } + for (uint32_t i = 0; i < m_groups.size(); i++) + destroyGroup(i); } - int index_y(float y) const + void run(TaskGroupHandle *handle, Task task) { - return clamp(ftoi_floor((y - corner.y) * invCellSize.y), 0, sy - 1); + // Allocate a task group if this is the first time using this handle. + TaskGroup *group; + if (handle->value == UINT32_MAX) { + group = XA_NEW(MemTag::Default, TaskGroup); + group->ref = 0; + std::lock_guard<std::mutex> lock(m_groupsMutex); + for (uint32_t i = 0; i < m_groups.size(); i++) { + if (!m_groups[i]) { + m_groups[i] = group; + handle->value = i; + break; + } + } + if (handle->value == UINT32_MAX) { + m_groups.push_back(group); + handle->value = m_groups.size() - 1; + } + } + group = m_groups[handle->value]; + { + std::lock_guard<std::mutex> lock(group->queueMutex); + group->queue.push_back(task); + } + group->ref++; + // Wake up a worker to run this task. + for (uint32_t i = 0; i < m_workers.size(); i++) { + m_workers[i].wakeup = true; + m_workers[i].cv.notify_one(); + } } - int index_z(float z) const + void wait(TaskGroupHandle *handle) { - return clamp(ftoi_floor((z - corner.z) * invCellSize.z), 0, sz - 1); + if (handle->value == UINT32_MAX) { + XA_DEBUG_ASSERT(false); + return; + } + // Run tasks from the group queue until empty. + TaskGroup *group = m_groups[handle->value]; + for (;;) { + Task *task = nullptr; + { + std::lock_guard<std::mutex> lock(group->queueMutex); + if (group->queueHead < group->queue.size()) + task = &group->queue[group->queueHead++]; + } + if (!task) + break; + task->func(task->userData); + group->ref--; + } + // Even though the task queue is empty, workers can still be running tasks. + while (group->ref > 0) + std::this_thread::yield(); + std::lock_guard<std::mutex> lock(m_groupsMutex); + destroyGroup(handle->value); + handle->value = UINT32_MAX; } - int index(int x, int y, int z) const +private: + struct TaskGroup { - xaDebugAssert(x >= 0 && x < sx); - xaDebugAssert(y >= 0 && y < sy); - xaDebugAssert(z >= 0 && z < sz); - int idx = (z * sy + y) * sx + x; - xaDebugAssert(idx >= 0 && uint32_t(idx) < cellArray.size()); - return idx; - } + Array<Task> queue; // Items are never removed. queueHead is incremented to pop items. + uint32_t queueHead = 0; + std::mutex queueMutex; + std::atomic<uint32_t> ref; // Increment when a task is enqueued, decrement when a task finishes. + }; - uint32_t mortonCount() const + struct Worker { - uint64_t s = uint64_t(max3(sx, sy, sz)); - s = nextPowerOfTwo(s); - if (s > 1024) { - return uint32_t(s * s * min3(sx, sy, sz)); - } - return uint32_t(s * s * s); - } + std::thread *thread = nullptr; + std::mutex mutex; + std::condition_variable cv; + std::atomic<bool> wakeup; + }; - int mortonIndex(uint32_t code) const - { - uint32_t x, y, z; - uint32_t s = uint32_t(max3(sx, sy, sz)); - if (s > 1024) { - // Use layered two-dimensional morton order. - s = nextPowerOfTwo(s); - uint32_t layer = code / (s * s); - code = code % (s * s); - uint32_t layer_count = uint32_t(min3(sx, sy, sz)); - if (sx == (int)layer_count) { - x = layer; - y = morton::decodeMorton2X(code); - z = morton::decodeMorton2Y(code); - } else if (sy == (int)layer_count) { - x = morton::decodeMorton2Y(code); - y = layer; - z = morton::decodeMorton2X(code); - } else { /*if (sz == layer_count)*/ - x = morton::decodeMorton2X(code); - y = morton::decodeMorton2Y(code); - z = layer; - } - } else { - x = morton::decodeMorton3X(code); - y = morton::decodeMorton3Y(code); - z = morton::decodeMorton3Z(code); - } - if (x >= uint32_t(sx) || y >= uint32_t(sy) || z >= uint32_t(sz)) { - return -1; - } - return index(x, y, z); - } + Array<TaskGroup *> m_groups; + std::mutex m_groupsMutex; + Array<Worker> m_workers; + std::atomic<bool> m_shutdown; - void add(const Vector3 &pos, uint32_t key) + void destroyGroup(uint32_t index) { - int x = index_x(pos.x); - int y = index_y(pos.y); - int z = index_z(pos.z); - uint32_t idx = index(x, y, z); - cellArray[idx].indexArray.push_back(key); + TaskGroup *group = m_groups[index]; + m_groups[index] = nullptr; + if (group) { + group->~TaskGroup(); + XA_FREE(group); + } } - // Gather all points inside the given sphere. - // Radius is assumed to be small, so we don't bother culling the cells. - void gather(const Vector3 &position, float radius, std::vector<uint32_t> &indexArray) + static void workerThread(TaskScheduler *scheduler, Worker *worker) { - int x0 = index_x(position.x - radius); - int x1 = index_x(position.x + radius); - int y0 = index_y(position.y - radius); - int y1 = index_y(position.y + radius); - int z0 = index_z(position.z - radius); - int z1 = index_z(position.z + radius); - for (int z = z0; z <= z1; z++) { - for (int y = y0; y <= y1; y++) { - for (int x = x0; x <= x1; x++) { - int idx = index(x, y, z); - indexArray.insert(indexArray.begin(), cellArray[idx].indexArray.begin(), cellArray[idx].indexArray.end()); + std::unique_lock<std::mutex> lock(worker->mutex); + for (;;) { + worker->cv.wait(lock, [=]{ return worker->wakeup.load(); }); + worker->wakeup = false; + for (;;) { + if (scheduler->m_shutdown) + return; + // Look for a task in any of the groups and run it. + TaskGroup *group = nullptr; + Task *task = nullptr; + { + std::lock_guard<std::mutex> groupsLock(scheduler->m_groupsMutex); + for (uint32_t i = 0; i < scheduler->m_groups.size(); i++) { + group = scheduler->m_groups[i]; + if (!group) + continue; + std::lock_guard<std::mutex> queueLock(group->queueMutex); + if (group->queueHead < group->queue.size()) { + task = &group->queue[group->queueHead++]; + break; + } + } } + if (!task) + break; + task->func(task->userData); + group->ref--; } } } - - struct Cell { - std::vector<uint32_t> indexArray; - }; - - std::vector<Cell> cellArray; - - Vector3 corner; - Vector3 invCellSize; - int sx, sy, sz; }; - -// 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 +#else +class TaskScheduler { public: - RadixSort() : m_size(0), m_ranks(NULL), m_ranks2(NULL), m_validRanks(false) {} - ~RadixSort() + ~TaskScheduler() { - // Release everything - free(m_ranks2); - free(m_ranks); + for (uint32_t i = 0; i < m_groups.size(); i++) + destroyGroup({ i }); } - RadixSort &sort(const float *input, uint32_t count) + void run(TaskGroupHandle *handle, Task task) { - if (input == NULL || count == 0) return *this; - // Resize lists if needed - if (count != m_size) { - if (count > m_size) { - m_ranks2 = (uint32_t *)realloc(m_ranks2, sizeof(uint32_t ) * count); - m_ranks = (uint32_t *)realloc(m_ranks, sizeof(uint32_t ) * count); - } - m_size = count; - m_validRanks = false; - } - if (count < 32) { - insertionSort(input, count); - } else { - // @@ Avoid touching the input multiple times. - for (uint32_t i = 0; i < count; i++) { - FloatFlip((uint32_t &)input[i]); + if (handle->value == UINT32_MAX) { + TaskGroup *group = XA_NEW(MemTag::Default, TaskGroup); + for (uint32_t i = 0; i < m_groups.size(); i++) { + if (!m_groups[i]) { + m_groups[i] = group; + handle->value = i; + break; + } } - radixSort<uint32_t>((const uint32_t *)input, count); - for (uint32_t i = 0; i < count; i++) { - IFloatFlip((uint32_t &)input[i]); + if (handle->value == UINT32_MAX) { + m_groups.push_back(group); + handle->value = m_groups.size() - 1; } } - return *this; + m_groups[handle->value]->queue.push_back(task); } - RadixSort &sort(const std::vector<float> &input) + void wait(TaskGroupHandle *handle) { - return 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 - { - xaDebugAssert(m_validRanks); - return m_ranks; - } - uint32_t *ranks() - { - xaDebugAssert(m_validRanks); - return m_ranks; + 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.clear(); + destroyGroup(*handle); + handle->value = UINT32_MAX; } private: - uint32_t m_size; - uint32_t *m_ranks; - uint32_t *m_ranks2; - bool m_validRanks; - - void FloatFlip(uint32_t &f) + void destroyGroup(TaskGroupHandle handle) { - int32_t mask = (int32_t(f) >> 31) | 0x80000000; // Warren Hunt, Manchor Ko. - f ^= mask; + TaskGroup *group = m_groups[handle.value]; + if (group) { + group->~TaskGroup(); + XA_FREE(group); + m_groups[handle.value] = nullptr; + } } - void IFloatFlip(uint32_t &f) + struct TaskGroup { - uint32_t mask = ((f >> 31) - 1) | 0x80000000; // Michael Herf. - f ^= mask; - } + Array<Task> queue; + }; - 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) - // 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 )); - // @@ 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); - 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) + Array<TaskGroup *> m_groups; +}; #endif - } - } - template <typename T> void insertionSort(const T *input, uint32_t count) - { - if (!m_validRanks) { - m_ranks[0] = 0; - for (uint32_t i = 1; i != count; ++i) { - int rank = m_ranks[i] = i; - uint32_t j = i; - while (j != 0 && input[rank] < input[m_ranks[j - 1]]) { - m_ranks[j] = m_ranks[j - 1]; - --j; - } - if (i != j) { - m_ranks[j] = rank; - } - } - m_validRanks = true; - } else { - for (uint32_t i = 1; i != count; ++i) { - int rank = m_ranks[i]; - uint32_t j = i; - while (j != 0 && input[rank] < input[m_ranks[j - 1]]) { - m_ranks[j] = m_ranks[j - 1]; - --j; - } - if (i != j) { - m_ranks[j] = rank; - } - } - } - } +struct UvMeshChart +{ + Array<uint32_t> indices; + uint32_t material; +}; - template <typename T> void radixSort(const T *input, uint32_t count) - { - const uint32_t P = sizeof(T); // pass count - // Allocate histograms & offsets on the stack - uint32_t histogram[256 * P]; - uint32_t *link[256]; - createHistograms(input, count, 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? - inputBytes += j; - if (h[inputBytes[0]] == count) { - // 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]; - // Perform Radix Sort - if (!m_validRanks) { - for (uint32_t i = 0; i < count; i++) { - *link[inputBytes[i * P]]++ = i; - } - m_validRanks = true; - } else { - for (uint32_t i = 0; i < count; i++) { - const uint32_t idx = m_ranks[i]; - *link[inputBytes[idx * P]]++ = idx; - } - } - // Swap pointers for next pass. Valid indices - the most recent ones - are in m_ranks after the swap. - std::swap(m_ranks, m_ranks2); - } - // All values were equal, generate linear ranks. - if (!m_validRanks) { - for (uint32_t i = 0; i < count; i++) { - m_ranks[i] = i; - } - m_validRanks = true; - } - } +struct UvMesh +{ + UvMeshDecl decl; + Array<uint32_t> indices; + Array<UvMeshChart *> charts; + Array<uint32_t> vertexToChartMap; +}; + +struct UvMeshInstance +{ + UvMesh *mesh; + Array<Vector2> texcoords; + bool rotateCharts; }; namespace raster { class ClippedTriangle { public: - ClippedTriangle(Vector2::Arg a, Vector2::Arg b, Vector2::Arg c) + ClippedTriangle(const Vector2 &a, const Vector2 &b, const Vector2 &c) { m_numVertices = 3; m_activeVertexBuffer = 0; @@ -3549,16 +3794,6 @@ public: m_vertexBuffers[1] = m_verticesB; } - uint32_t vertexCount() - { - return m_numVertices; - } - - const Vector2 *vertices() - { - return m_vertexBuffers[m_activeVertexBuffer]; - } - void clipHorizontalPlane(float offset, float clipdirection) { Vector2 *v = m_vertexBuffers[m_activeVertexBuffer]; @@ -3581,7 +3816,6 @@ public: dy1in = dy2in; } m_numVertices = p; - //for (uint32_t k=0; k<m_numVertices; k++) printf("(%f, %f)\n", v2[k].x, v2[k].y); printf("\n"); } void clipVerticalPlane(float offset, float clipdirection ) @@ -3608,7 +3842,7 @@ public: m_numVertices = p; } - void computeAreaCentroid() + void computeArea() { Vector2 *v = m_vertexBuffers[m_activeVertexBuffer]; v[m_numVertices] = v[0]; @@ -3622,25 +3856,15 @@ public: centroidy += f * (v[k].y + v[k + 1].y); } m_area = 0.5f * fabsf(m_area); - if (m_area == 0) { - m_centroid = Vector2(0.0f); - } else { - m_centroid = Vector2(centroidx / (6 * m_area), centroidy / (6 * m_area)); - } } void clipAABox(float x0, float y0, float x1, float y1) { - clipVerticalPlane ( x0, -1); - clipHorizontalPlane( y0, -1); - clipVerticalPlane ( x1, 1); - clipHorizontalPlane( y1, 1); - computeAreaCentroid(); - } - - Vector2 centroid() - { - return m_centroid; + clipVerticalPlane(x0, -1); + clipHorizontalPlane(y0, -1); + clipVerticalPlane(x1, 1); + clipHorizontalPlane(y1, 1); + computeArea(); } float area() @@ -3652,212 +3876,50 @@ private: Vector2 m_verticesA[7 + 1]; Vector2 m_verticesB[7 + 1]; Vector2 *m_vertexBuffers[2]; - uint32_t m_numVertices; - uint32_t m_activeVertexBuffer; - float m_area; - Vector2 m_centroid; + uint32_t m_numVertices; + uint32_t m_activeVertexBuffer; + float m_area; }; /// A callback to sample the environment. Return false to terminate rasterization. -typedef bool (* SamplingCallback)(void *param, int x, int y, Vector3::Arg bar, Vector3::Arg dx, Vector3::Arg dy, float coverage); +typedef bool (*SamplingCallback)(void *param, int x, int y); /// A triangle for rasterization. struct Triangle { - Triangle(Vector2::Arg v0, Vector2::Arg v1, Vector2::Arg v2, Vector3::Arg t0, Vector3::Arg t1, Vector3::Arg t2) + Triangle(const Vector2 &v0, const Vector2 &v1, const Vector2 &v2) { // Init vertices. this->v1 = v0; this->v2 = v2; this->v3 = v1; - // Set barycentric coordinates. - this->t1 = t0; - this->t2 = t2; - this->t3 = t1; // make sure every triangle is front facing. flipBackface(); // Compute deltas. - valid = computeDeltas(); computeUnitInwardNormals(); } - /// Compute texture space deltas. - /// This method takes two edge vectors that form a basis, determines the - /// coordinates of the canonic vectors in that basis, and computes the - /// texture gradient that corresponds to those vectors. - bool computeDeltas() + bool isValid() { - Vector2 e0 = v3 - v1; - Vector2 e1 = v2 - v1; - Vector3 de0 = t3 - t1; - Vector3 de1 = t2 - t1; - float denom = 1.0f / (e0.y * e1.x - e1.y * e0.x); - if (!std::isfinite(denom)) { - return false; - } - float lambda1 = - e1.y * denom; - float lambda2 = e0.y * denom; - float lambda3 = e1.x * denom; - float lambda4 = - e0.x * denom; - dx = de0 * lambda1 + de1 * lambda2; - dy = de0 * lambda3 + de1 * lambda4; - return true; - } - - bool draw(const Vector2 &extents, bool enableScissors, SamplingCallback cb, void *param) - { - // 28.4 fixed-point coordinates - const int Y1 = ftoi_round(16.0f * v1.y); - const int Y2 = ftoi_round(16.0f * v2.y); - const int Y3 = ftoi_round(16.0f * v3.y); - const int X1 = ftoi_round(16.0f * v1.x); - const int X2 = ftoi_round(16.0f * v2.x); - const int X3 = ftoi_round(16.0f * v3.x); - // Deltas - const int DX12 = X1 - X2; - const int DX23 = X2 - X3; - const int DX31 = X3 - X1; - const int DY12 = Y1 - Y2; - const int DY23 = Y2 - Y3; - const int DY31 = Y3 - Y1; - // Fixed-point deltas - const int FDX12 = DX12 << 4; - const int FDX23 = DX23 << 4; - const int FDX31 = DX31 << 4; - const int FDY12 = DY12 << 4; - const int FDY23 = DY23 << 4; - const int FDY31 = DY31 << 4; - int minx, miny, maxx, maxy; - if (enableScissors) { - int frustumX0 = 0 << 4; - int frustumY0 = 0 << 4; - int frustumX1 = (int)extents.x << 4; - int frustumY1 = (int)extents.y << 4; - // Bounding rectangle - minx = (std::max(min3(X1, X2, X3), frustumX0) + 0xF) >> 4; - miny = (std::max(min3(Y1, Y2, Y3), frustumY0) + 0xF) >> 4; - maxx = (std::min(max3(X1, X2, X3), frustumX1) + 0xF) >> 4; - maxy = (std::min(max3(Y1, Y2, Y3), frustumY1) + 0xF) >> 4; - } else { - // Bounding rectangle - minx = (min3(X1, X2, X3) + 0xF) >> 4; - miny = (min3(Y1, Y2, Y3) + 0xF) >> 4; - maxx = (max3(X1, X2, X3) + 0xF) >> 4; - maxy = (max3(Y1, Y2, Y3) + 0xF) >> 4; - } - // Block size, standard 8x8 (must be power of two) - const int q = 8; - // @@ This won't work when minx,miny are negative. This code path is not used. Leaving as is for now. - xaAssert(minx >= 0); - xaAssert(miny >= 0); - // Start in corner of 8x8 block - minx &= ~(q - 1); - miny &= ~(q - 1); - // Half-edge constants - int C1 = DY12 * X1 - DX12 * Y1; - int C2 = DY23 * X2 - DX23 * Y2; - int C3 = DY31 * X3 - DX31 * Y3; - // Correct for fill convention - if (DY12 < 0 || (DY12 == 0 && DX12 > 0)) C1++; - if (DY23 < 0 || (DY23 == 0 && DX23 > 0)) C2++; - if (DY31 < 0 || (DY31 == 0 && DX31 > 0)) C3++; - // Loop through blocks - for (int y = miny; y < maxy; y += q) { - for (int x = minx; x < maxx; x += q) { - // Corners of block - int x0 = x << 4; - int x1 = (x + q - 1) << 4; - int y0 = y << 4; - int y1 = (y + q - 1) << 4; - // Evaluate half-space functions - bool a00 = C1 + DX12 * y0 - DY12 * x0 > 0; - bool a10 = C1 + DX12 * y0 - DY12 * x1 > 0; - bool a01 = C1 + DX12 * y1 - DY12 * x0 > 0; - bool a11 = C1 + DX12 * y1 - DY12 * x1 > 0; - int a = (a00 << 0) | (a10 << 1) | (a01 << 2) | (a11 << 3); - bool b00 = C2 + DX23 * y0 - DY23 * x0 > 0; - bool b10 = C2 + DX23 * y0 - DY23 * x1 > 0; - bool b01 = C2 + DX23 * y1 - DY23 * x0 > 0; - bool b11 = C2 + DX23 * y1 - DY23 * x1 > 0; - int b = (b00 << 0) | (b10 << 1) | (b01 << 2) | (b11 << 3); - bool c00 = C3 + DX31 * y0 - DY31 * x0 > 0; - bool c10 = C3 + DX31 * y0 - DY31 * x1 > 0; - bool c01 = C3 + DX31 * y1 - DY31 * x0 > 0; - bool c11 = C3 + DX31 * y1 - DY31 * x1 > 0; - int c = (c00 << 0) | (c10 << 1) | (c01 << 2) | (c11 << 3); - // Skip block when outside an edge - if (a == 0x0 || b == 0x0 || c == 0x0) continue; - // Accept whole block when totally covered - if (a == 0xF && b == 0xF && c == 0xF) { - Vector3 texRow = t1 + dy * (y0 - v1.y) + dx * (x0 - v1.x); - for (int iy = y; iy < y + q; iy++) { - Vector3 tex = texRow; - for (int ix = x; ix < x + q; ix++) { - //Vector3 tex = t1 + dx * (ix - v1.x) + dy * (iy - v1.y); - if (!cb(param, ix, iy, tex, dx, dy, 1.0)) { - // early out. - return false; - } - tex += dx; - } - texRow += dy; - } - } else { // Partially covered block - int CY1 = C1 + DX12 * y0 - DY12 * x0; - int CY2 = C2 + DX23 * y0 - DY23 * x0; - int CY3 = C3 + DX31 * y0 - DY31 * x0; - Vector3 texRow = t1 + dy * (y0 - v1.y) + dx * (x0 - v1.x); - for (int iy = y; iy < y + q; iy++) { - int CX1 = CY1; - int CX2 = CY2; - int CX3 = CY3; - Vector3 tex = texRow; - for (int ix = x; ix < x + q; ix++) { - if (CX1 > 0 && CX2 > 0 && CX3 > 0) { - if (!cb(param, ix, iy, tex, dx, dy, 1.0)) { - // early out. - return false; - } - } - CX1 -= FDY12; - CX2 -= FDY23; - CX3 -= FDY31; - tex += dx; - } - CY1 += FDX12; - CY2 += FDX23; - CY3 += FDX31; - texRow += dy; - } - } - } - } - return true; + const Vector2 e0 = v3 - v1; + const Vector2 e1 = v2 - v1; + const float denom = 1.0f / (e0.y * e1.x - e1.y * e0.x); + return isFinite(denom); } // extents has to be multiple of BK_SIZE!! - bool drawAA(const Vector2 &extents, bool enableScissors, SamplingCallback cb, void *param) + bool drawAA(const Vector2 &extents, SamplingCallback cb, void *param) { - const float PX_INSIDE = 1.0f/sqrt(2.0f); - const float PX_OUTSIDE = -1.0f/sqrt(2.0f); + 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 = sqrt(BK_SIZE*BK_SIZE/2.0f); - const float BK_OUTSIDE = -sqrt(BK_SIZE*BK_SIZE/2.0f); - - float minx, miny, maxx, maxy; - if (enableScissors) { - // Bounding rectangle - minx = floorf(std::max(min3(v1.x, v2.x, v3.x), 0.0f)); - miny = floorf(std::max(min3(v1.y, v2.y, v3.y), 0.0f)); - maxx = ceilf( std::min(max3(v1.x, v2.x, v3.x), extents.x - 1.0f)); - maxy = ceilf( std::min(max3(v1.y, v2.y, v3.y), extents.y - 1.0f)); - } else { - // Bounding rectangle - minx = floorf(min3(v1.x, v2.x, v3.x)); - miny = floorf(min3(v1.y, v2.y, v3.y)); - maxx = ceilf( max3(v1.x, v2.x, v3.x)); - maxy = ceilf( max3(v1.y, v2.y, v3.y)); - } + 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)); // 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); @@ -3885,47 +3947,32 @@ struct Triangle 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) ) { - Vector3 texRow = t1 + dy * (y0 - v1.y) + dx * (x0 - v1.x); for (float y = y0; y < y0 + BK_SIZE; y++) { - Vector3 tex = texRow; for (float x = x0; x < x0 + BK_SIZE; x++) { - if (!cb(param, (int)x, (int)y, tex, dx, dy, 1.0f)) { + if (!cb(param, (int)x, (int)y)) { return false; } - tex += dx; } - texRow += dy; } } else { // Partially covered block float CY1 = C1 + n1.x * x0 + n1.y * y0; float CY2 = C2 + n2.x * x0 + n2.y * y0; float CY3 = C3 + n3.x * x0 + n3.y * y0; - Vector3 texRow = t1 + dy * (y0 - v1.y) + dx * (x0 - v1.x); for (float y = y0; y < y0 + BK_SIZE; y++) { // @@ This is not clipping to scissor rectangle correctly. float CX1 = CY1; float CX2 = CY2; float CX3 = CY3; - Vector3 tex = texRow; for (float x = x0; x < x0 + BK_SIZE; x++) { // @@ This is not clipping to scissor rectangle correctly. if (CX1 >= PX_INSIDE && CX2 >= PX_INSIDE && CX3 >= PX_INSIDE) { - // pixel completely covered - Vector3 tex2 = t1 + dx * (x - v1.x) + dy * (y - v1.y); - if (!cb(param, (int)x, (int)y, tex2, dx, dy, 1.0f)) { + if (!cb(param, (int)x, (int)y)) { return false; } } else if ((CX1 >= PX_OUTSIDE) && (CX2 >= PX_OUTSIDE) && (CX3 >= PX_OUTSIDE)) { // triangle partially covers pixel. do clipping. ClippedTriangle ct(v1 - Vector2(x, y), v2 - Vector2(x, y), v3 - Vector2(x, y)); ct.clipAABox(-0.5, -0.5, 0.5, 0.5); - Vector2 centroid = ct.centroid(); - float area = ct.area(); - if (area > 0.0f) { - Vector3 texCent = tex - dx * centroid.x - dy * centroid.y; - //xaAssert(texCent.x >= -0.1f && texCent.x <= 1.1f); // @@ Centroid is not very exact... - //xaAssert(texCent.y >= -0.1f && texCent.y <= 1.1f); - //xaAssert(texCent.z >= -0.1f && texCent.z <= 1.1f); - //Vector3 texCent2 = t1 + dx * (x - v1.x) + dy * (y - v1.y); - if (!cb(param, (int)x, (int)y, texCent, dx, dy, area)) { + if (ct.area() > 0.0f) { + if (!cb(param, (int)x, (int)y)) { return false; } } @@ -3933,12 +3980,10 @@ struct Triangle CX1 += n1.x; CX2 += n2.x; CX3 += n3.x; - tex += dx; } CY1 += n1.y; CY2 += n2.y; CY3 += n3.y; - texRow += dy; } } } @@ -3953,9 +3998,6 @@ struct Triangle Vector2 hv = v1; v1 = v2; v2 = hv; // swap pos - Vector3 ht = t1; - t1 = t2; - t2 = ht; // swap tex } } @@ -3976,77 +4018,24 @@ struct Triangle // Vertices. Vector2 v1, v2, v3; Vector2 n1, n2, n3; // unit inward normals - Vector3 t1, t2, t3; - - // Deltas. - Vector3 dx, dy; - - float sign; - bool valid; -}; - -enum Mode -{ - Mode_Nearest, - Mode_Antialiased }; // Process the given triangle. Returns false if rasterization was interrupted by the callback. -static bool drawTriangle(Mode mode, Vector2::Arg extents, bool enableScissors, 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], Vector3(1, 0, 0), Vector3(0, 1, 0), Vector3(0, 0, 1)); + 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. - if (tri.valid) { - if (mode == Mode_Antialiased) { - return tri.drawAA(extents, enableScissors, cb, param); - } - if (mode == Mode_Nearest) { - return tri.draw(extents, enableScissors, cb, param); - } - } + if (tri.isValid()) + return tri.drawAA(extents, cb, param); return true; } -// Process the given quad. Returns false if rasterization was interrupted by the callback. -static bool drawQuad(Mode mode, Vector2::Arg extents, bool enableScissors, const Vector2 v[4], SamplingCallback cb, void *param) -{ - bool sign0 = triangleArea2(v[0], v[1], v[2]) > 0.0f; - bool sign1 = triangleArea2(v[0], v[2], v[3]) > 0.0f; - // Divide the quad into two non overlapping triangles. - if (sign0 == sign1) { - Triangle tri0(v[0], v[1], v[2], Vector3(0, 0, 0), Vector3(1, 0, 0), Vector3(1, 1, 0)); - Triangle tri1(v[0], v[2], v[3], Vector3(0, 0, 0), Vector3(1, 1, 0), Vector3(0, 1, 0)); - if (tri0.valid && tri1.valid) { - if (mode == Mode_Antialiased) { - return tri0.drawAA(extents, enableScissors, cb, param) && tri1.drawAA(extents, enableScissors, cb, param); - } else { - return tri0.draw(extents, enableScissors, cb, param) && tri1.draw(extents, enableScissors, cb, param); - } - } - } else { - Triangle tri0(v[0], v[1], v[3], Vector3(0, 0, 0), Vector3(1, 0, 0), Vector3(0, 1, 0)); - Triangle tri1(v[1], v[2], v[3], Vector3(1, 0, 0), Vector3(1, 1, 0), Vector3(0, 1, 0)); - if (tri0.valid && tri1.valid) { - if (mode == Mode_Antialiased) { - return tri0.drawAA(extents, enableScissors, cb, param) && tri1.drawAA(extents, enableScissors, cb, param); - } else { - return tri0.draw(extents, enableScissors, cb, param) && tri1.draw(extents, enableScissors, cb, param); - } - } - } - return true; -} } // namespace raster // Full and sparse vector and matrix classes. BLAS subset. // Pseudo-BLAS interface. namespace sparse { -enum Transpose -{ - NoTransposed = 0, - Transposed = 1 -}; /** * Sparse matrix class. The matrix is assumed to be sparse and to have @@ -4072,8 +4061,8 @@ public: const Matrix &operator=(const Matrix &m) { - xaAssert(width() == m.width()); - xaAssert(height() == m.height()); + XA_ASSERT(width() == m.width()); + XA_ASSERT(height() == m.height()); m_array = m.m_array; return *this; } @@ -4085,8 +4074,8 @@ public: // x is column, y is row float getCoefficient(uint32_t x, uint32_t y) const { - xaDebugAssert( x < width() ); - xaDebugAssert( y < height() ); + XA_DEBUG_ASSERT( x < width() ); + XA_DEBUG_ASSERT( y < height() ); const uint32_t count = m_array[y].size(); for (uint32_t i = 0; i < count; i++) { if (m_array[y][i].x == x) return m_array[y][i].v; @@ -4096,8 +4085,8 @@ public: void setCoefficient(uint32_t x, uint32_t y, float f) { - xaDebugAssert( x < width() ); - xaDebugAssert( y < height() ); + XA_DEBUG_ASSERT( x < width() ); + XA_DEBUG_ASSERT( y < height() ); const uint32_t count = m_array[y].size(); for (uint32_t i = 0; i < count; i++) { if (m_array[y][i].x == x) { @@ -4113,7 +4102,7 @@ public: float dotRow(uint32_t y, const FullVector &v) const { - xaDebugAssert( y < height() ); + XA_DEBUG_ASSERT( y < height() ); const uint32_t count = m_array[y].size(); float sum = 0; for (uint32_t i = 0; i < count; i++) { @@ -4124,7 +4113,7 @@ public: void madRow(uint32_t y, float alpha, FullVector &v) const { - xaDebugAssert(y < height()); + XA_DEBUG_ASSERT(y < height()); const uint32_t count = m_array[y].size(); for (uint32_t i = 0; i < count; i++) { v[m_array[y][i].x] += alpha * m_array[y][i].v; @@ -4133,33 +4122,24 @@ public: void clearRow(uint32_t y) { - xaDebugAssert( y < height() ); + XA_DEBUG_ASSERT( y < height() ); m_array[y].clear(); } - void scaleRow(uint32_t y, float f) - { - xaDebugAssert( y < height() ); - const uint32_t count = m_array[y].size(); - for (uint32_t i = 0; i < count; i++) { - m_array[y][i].v *= f; - } - } - - const std::vector<Coefficient> &getRow(uint32_t y) const { return m_array[y]; } + const Array<Coefficient> &getRow(uint32_t y) const { return m_array[y]; } private: /// Number of columns. const uint32_t m_width; /// Array of matrix elements. - std::vector< std::vector<Coefficient> > m_array; + Array< Array<Coefficient> > m_array; }; // y = a * x + y static void saxpy(float a, const FullVector &x, FullVector &y) { - xaDebugAssert(x.dimension() == y.dimension()); + XA_DEBUG_ASSERT(x.dimension() == y.dimension()); const uint32_t dim = x.dimension(); for (uint32_t i = 0; i < dim; i++) { y[i] += a * x[i]; @@ -4168,7 +4148,7 @@ static void saxpy(float a, const FullVector &x, FullVector &y) static void copy(const FullVector &x, FullVector &y) { - xaDebugAssert(x.dimension() == y.dimension()); + XA_DEBUG_ASSERT(x.dimension() == y.dimension()); const uint32_t dim = x.dimension(); for (uint32_t i = 0; i < dim; i++) { y[i] = x[i]; @@ -4185,7 +4165,7 @@ static void scal(float a, FullVector &x) static float dot(const FullVector &x, const FullVector &y) { - xaDebugAssert(x.dimension() == y.dimension()); + XA_DEBUG_ASSERT(x.dimension() == y.dimension()); const uint32_t dim = x.dimension(); float sum = 0; for (uint32_t i = 0; i < dim; i++) { @@ -4194,61 +4174,35 @@ static float dot(const FullVector &x, const FullVector &y) return sum; } -static void mult(Transpose TM, const Matrix &M, const FullVector &x, FullVector &y) -{ - const uint32_t w = M.width(); - const uint32_t h = M.height(); - if (TM == Transposed) { - xaDebugAssert( h == x.dimension() ); - xaDebugAssert( w == y.dimension() ); - y.fill(0.0f); - for (uint32_t i = 0; i < h; i++) { - M.madRow(i, x[i], y); - } - } else { - xaDebugAssert( w == x.dimension() ); - xaDebugAssert( h == y.dimension() ); - for (uint32_t i = 0; i < h; i++) { - y[i] = M.dotRow(i, x); - } - } -} - // y = M * x static void mult(const Matrix &M, const FullVector &x, FullVector &y) { - mult(NoTransposed, M, x, y); -} - -static void sgemv(float alpha, Transpose TA, const Matrix &A, const FullVector &x, float beta, FullVector &y) -{ - const uint32_t w = A.width(); - const uint32_t h = A.height(); - if (TA == Transposed) { - xaDebugAssert( h == x.dimension() ); - xaDebugAssert( w == y.dimension() ); - for (uint32_t i = 0; i < h; i++) { - A.madRow(i, alpha * x[i], y); - } - } else { - xaDebugAssert( w == x.dimension() ); - xaDebugAssert( h == y.dimension() ); - for (uint32_t i = 0; i < h; i++) { - y[i] = alpha * A.dotRow(i, x) + beta * y[i]; - } - } + uint32_t w = M.width(); + uint32_t h = M.height(); + XA_DEBUG_ASSERT( w == x.dimension() ); + XA_UNUSED(w); + XA_DEBUG_ASSERT( h == y.dimension() ); + for (uint32_t i = 0; i < h; i++) + y[i] = M.dotRow(i, x); } // y = alpha*A*x + beta*y static void sgemv(float alpha, const Matrix &A, const FullVector &x, float beta, FullVector &y) { - sgemv(alpha, NoTransposed, A, x, beta, y); + const uint32_t w = A.width(); + const uint32_t h = A.height(); + XA_DEBUG_ASSERT( w == x.dimension() ); + XA_DEBUG_ASSERT( h == y.dimension() ); + XA_UNUSED(w); + XA_UNUSED(h); + for (uint32_t i = 0; i < h; i++) + y[i] = alpha * A.dotRow(i, x) + beta * y[i]; } // dot y-row of A by x-column of B static float dotRowColumn(int y, const Matrix &A, int x, const Matrix &B) { - const std::vector<Matrix::Coefficient> &row = A.getRow(y); + const Array<Matrix::Coefficient> &row = A.getRow(y); const uint32_t count = row.size(); float sum = 0.0f; for (uint32_t i = 0; i < count; i++) { @@ -4258,96 +4212,54 @@ static float dotRowColumn(int y, const Matrix &A, int x, const Matrix &B) return sum; } -// dot y-row of A by x-row of B -static float dotRowRow(int y, const Matrix &A, int x, const Matrix &B) -{ - const std::vector<Matrix::Coefficient> &row = A.getRow(y); - const uint32_t count = row.size(); - float sum = 0.0f; - for (uint32_t i = 0; i < count; i++) { - const Matrix::Coefficient &c = row[i]; - sum += c.v * B.getCoefficient(c.x, x); - } - return sum; -} - -// dot y-column of A by x-column of B -static float dotColumnColumn(int y, const Matrix &A, int x, const Matrix &B) -{ - xaDebugAssert(A.height() == B.height()); - const uint32_t h = A.height(); - float sum = 0.0f; - for (uint32_t i = 0; i < h; i++) { - sum += A.getCoefficient(y, i) * B.getCoefficient(x, i); - } - return sum; -} - static void transpose(const Matrix &A, Matrix &B) { - xaDebugAssert(A.width() == B.height()); - xaDebugAssert(B.width() == A.height()); + XA_DEBUG_ASSERT(A.width() == B.height()); + XA_DEBUG_ASSERT(B.width() == A.height()); const uint32_t w = A.width(); for (uint32_t x = 0; x < w; x++) { B.clearRow(x); } const uint32_t h = A.height(); for (uint32_t y = 0; y < h; y++) { - const std::vector<Matrix::Coefficient> &row = A.getRow(y); + const Array<Matrix::Coefficient> &row = A.getRow(y); const uint32_t count = row.size(); for (uint32_t i = 0; i < count; i++) { const Matrix::Coefficient &c = row[i]; - xaDebugAssert(c.x < w); + XA_DEBUG_ASSERT(c.x < w); B.setCoefficient(y, c.x, c.v); } } } -static void sgemm(float alpha, Transpose TA, const Matrix &A, Transpose TB, const Matrix &B, float beta, Matrix &C) +static void sgemm(float alpha, const Matrix &A, const Matrix &B, float beta, Matrix &C) { const uint32_t w = C.width(); const uint32_t h = C.height(); - uint32_t aw = (TA == NoTransposed) ? A.width() : A.height(); - uint32_t ah = (TA == NoTransposed) ? A.height() : A.width(); - uint32_t bw = (TB == NoTransposed) ? B.width() : B.height(); - uint32_t bh = (TB == NoTransposed) ? B.height() : B.width(); - xaDebugAssert(aw == bh); - xaDebugAssert(bw == ah); - xaDebugAssert(w == bw); - xaDebugAssert(h == ah); -#ifdef NDEBUG - aw = ah = bw = bh = 0; // silence unused parameter warning +#if XA_DEBUG + const uint32_t aw = A.width(); + const uint32_t ah = A.height(); + const uint32_t bw = B.width(); + const uint32_t bh = B.height(); + XA_DEBUG_ASSERT(aw == bh); + XA_DEBUG_ASSERT(bw == ah); + XA_DEBUG_ASSERT(w == bw); + XA_DEBUG_ASSERT(h == ah); #endif for (uint32_t y = 0; y < h; y++) { for (uint32_t x = 0; x < w; x++) { float c = beta * C.getCoefficient(x, y); - if (TA == NoTransposed && TB == NoTransposed) { - // dot y-row of A by x-column of B. - c += alpha * dotRowColumn(y, A, x, B); - } else if (TA == Transposed && TB == Transposed) { - // dot y-column of A by x-row of B. - c += alpha * dotRowColumn(x, B, y, A); - } else if (TA == Transposed && TB == NoTransposed) { - // dot y-column of A by x-column of B. - c += alpha * dotColumnColumn(y, A, x, B); - } else if (TA == NoTransposed && TB == Transposed) { - // dot y-row of A by x-row of B. - c += alpha * dotRowRow(y, A, x, B); - } + // dot y-row of A by x-column of B. + c += alpha * dotRowColumn(y, A, x, B); C.setCoefficient(x, y, c); } } } -static void mult(Transpose TA, const Matrix &A, Transpose TB, const Matrix &B, Matrix &C) -{ - sgemm(1.0f, TA, A, TB, B, 0.0f, C); -} - // C = A * B static void mult(const Matrix &A, const Matrix &B, Matrix &C) { - mult(NoTransposed, A, NoTransposed, B, C); + sgemm(1.0f, A, B, 0.0f, C); } } // namespace sparse @@ -4357,10 +4269,10 @@ class JacobiPreconditioner public: JacobiPreconditioner(const sparse::Matrix &M, bool symmetric) : m_inverseDiagonal(M.width()) { - xaAssert(M.isSquare()); + XA_ASSERT(M.isSquare()); for (uint32_t x = 0; x < M.width(); x++) { float elem = M.getCoefficient(x, x); - //xaDebugAssert( elem != 0.0f ); // This can be zero in the presence of zero area triangles. + //XA_DEBUG_ASSERT( elem != 0.0f ); // This can be zero in the presence of zero area triangles. if (symmetric) { m_inverseDiagonal[x] = (elem != 0) ? 1.0f / sqrtf(fabsf(elem)) : 1.0f; } else { @@ -4371,8 +4283,8 @@ public: void apply(const FullVector &x, FullVector &y) const { - xaDebugAssert(x.dimension() == m_inverseDiagonal.dimension()); - xaDebugAssert(y.dimension() == m_inverseDiagonal.dimension()); + XA_DEBUG_ASSERT(x.dimension() == m_inverseDiagonal.dimension()); + XA_DEBUG_ASSERT(y.dimension() == m_inverseDiagonal.dimension()); // @@ Wrap vector component-wise product into a separate function. const uint32_t D = x.dimension(); for (uint32_t i = 0; i < D; i++) { @@ -4391,9 +4303,9 @@ public: // Solve the symmetric system: At·A·x = At·b static bool LeastSquaresSolver(const sparse::Matrix &A, const FullVector &b, FullVector &x, float epsilon = 1e-5f) { - xaDebugAssert(A.width() == x.dimension()); - xaDebugAssert(A.height() == b.dimension()); - xaDebugAssert(A.height() >= A.width()); // @@ If height == width we could solve it directly... + XA_DEBUG_ASSERT(A.width() == x.dimension()); + XA_DEBUG_ASSERT(A.height() == b.dimension()); + XA_DEBUG_ASSERT(A.height() >= A.width()); // @@ If height == width we could solve it directly... const uint32_t D = A.width(); sparse::Matrix At(A.height(), A.width()); sparse::transpose(A, At); @@ -4407,12 +4319,12 @@ public: // See section 10.4.3 in: Mesh Parameterization: Theory and Practice, Siggraph Course Notes, August 2007 static bool LeastSquaresSolver(const sparse::Matrix &A, const FullVector &b, FullVector &x, const uint32_t *lockedParameters, uint32_t lockedCount, float epsilon = 1e-5f) { - xaDebugAssert(A.width() == x.dimension()); - xaDebugAssert(A.height() == b.dimension()); - xaDebugAssert(A.height() >= A.width() - lockedCount); + XA_DEBUG_ASSERT(A.width() == x.dimension()); + XA_DEBUG_ASSERT(A.height() == b.dimension()); + XA_DEBUG_ASSERT(A.height() >= A.width() - lockedCount); // @@ This is not the most efficient way of building a system with reduced degrees of freedom. It would be faster to do it on the fly. const uint32_t D = A.width() - lockedCount; - xaDebugAssert(D > 0); + XA_DEBUG_ASSERT(D > 0); // Compute: b - Al * xl FullVector b_Alxl(b); for (uint32_t y = 0; y < A.height(); y++) { @@ -4504,62 +4416,12 @@ private: * Jonhathan Richard Shewchuk. * **/ - static bool ConjugateGradientSolver(const sparse::Matrix &A, const FullVector &b, FullVector &x, float epsilon) - { - xaDebugAssert( A.isSquare() ); - xaDebugAssert( A.width() == b.dimension() ); - xaDebugAssert( A.width() == x.dimension() ); - int i = 0; - const int D = A.width(); - const int i_max = 4 * D; // Convergence should be linear, but in some cases, it's not. - FullVector r(D); // residual - FullVector p(D); // search direction - FullVector q(D); // - float delta_0; - float delta_old; - float delta_new; - float alpha; - float beta; - // r = b - A·x; - sparse::copy(b, r); - sparse::sgemv(-1, A, x, 1, r); - // p = r; - sparse::copy(r, p); - delta_new = sparse::dot( r, r ); - delta_0 = delta_new; - while (i < i_max && delta_new > epsilon * epsilon * delta_0) { - i++; - // q = A·p - mult(A, p, q); - // alpha = delta_new / p·q - alpha = delta_new / sparse::dot( p, q ); - // x = alfa·p + x - sparse::saxpy(alpha, p, x); - if ((i & 31) == 0) { // recompute r after 32 steps - // r = b - A·x - sparse::copy(b, r); - sparse::sgemv(-1, A, x, 1, r); - } else { - // r = r - alpha·q - sparse::saxpy(-alpha, q, r); - } - delta_old = delta_new; - delta_new = sparse::dot( r, r ); - beta = delta_new / delta_old; - // p = beta·p + r - sparse::scal(beta, p); - sparse::saxpy(1, r, p); - } - return delta_new <= epsilon * epsilon * delta_0; - } - - // Conjugate gradient with preconditioner. static bool ConjugateGradientSolver(const JacobiPreconditioner &preconditioner, const sparse::Matrix &A, const FullVector &b, FullVector &x, float epsilon) { - xaDebugAssert( A.isSquare() ); - xaDebugAssert( A.width() == b.dimension() ); - xaDebugAssert( A.width() == x.dimension() ); + XA_DEBUG_ASSERT( A.isSquare() ); + XA_DEBUG_ASSERT( A.width() == b.dimension() ); + XA_DEBUG_ASSERT( A.width() == x.dimension() ); int i = 0; const int D = A.width(); const int i_max = 4 * D; // Convergence should be linear, but in some cases, it's not. @@ -4582,7 +4444,7 @@ private: while (i < i_max && delta_new > epsilon * epsilon * delta_0) { i++; // q = A·p - mult(A, p, q); + sparse::mult(A, p, q); // alpha = delta_new / p·q alpha = delta_new / sparse::dot(p, q); // x = alfa·p + x @@ -4609,59 +4471,59 @@ private: static bool SymmetricSolver(const sparse::Matrix &A, const FullVector &b, FullVector &x, float epsilon = 1e-5f) { - xaDebugAssert(A.height() == A.width()); - xaDebugAssert(A.height() == b.dimension()); - xaDebugAssert(b.dimension() == x.dimension()); + XA_DEBUG_ASSERT(A.height() == A.width()); + XA_DEBUG_ASSERT(A.height() == b.dimension()); + XA_DEBUG_ASSERT(b.dimension() == x.dimension()); JacobiPreconditioner jacobi(A, true); return ConjugateGradientSolver(jacobi, A, b, x, epsilon); } }; namespace param { -class Atlas; -class Chart; // Fast sweep in 3 directions -static bool findApproximateDiameterVertices(halfedge::Mesh *mesh, halfedge::Vertex **a, halfedge::Vertex **b) +static bool findApproximateDiameterVertices(Mesh *mesh, uint32_t *a, uint32_t *b) { - xaDebugAssert(mesh != NULL); - xaDebugAssert(a != NULL); - xaDebugAssert(b != NULL); + XA_DEBUG_ASSERT(a != nullptr); + XA_DEBUG_ASSERT(b != nullptr); const uint32_t vertexCount = mesh->vertexCount(); - halfedge::Vertex *minVertex[3]; - halfedge::Vertex *maxVertex[3]; - minVertex[0] = minVertex[1] = minVertex[2] = NULL; - maxVertex[0] = maxVertex[1] = maxVertex[2] = NULL; + uint32_t minVertex[3]; + uint32_t maxVertex[3]; + minVertex[0] = minVertex[1] = minVertex[2] = UINT32_MAX; + maxVertex[0] = maxVertex[1] = maxVertex[2] = UINT32_MAX; for (uint32_t v = 1; v < vertexCount; v++) { - halfedge::Vertex *vertex = mesh->vertexAt(v); - xaDebugAssert(vertex != NULL); - if (vertex->isBoundary()) { - minVertex[0] = minVertex[1] = minVertex[2] = vertex; - maxVertex[0] = maxVertex[1] = maxVertex[2] = vertex; + if (mesh->isBoundaryVertex(v)) { + minVertex[0] = minVertex[1] = minVertex[2] = v; + maxVertex[0] = maxVertex[1] = maxVertex[2] = v; break; } } - if (minVertex[0] == NULL) { + if (minVertex[0] == UINT32_MAX) { // Input mesh has not boundaries. return false; } for (uint32_t v = 1; v < vertexCount; v++) { - halfedge::Vertex *vertex = mesh->vertexAt(v); - xaDebugAssert(vertex != NULL); - if (!vertex->isBoundary()) { + if (!mesh->isBoundaryVertex(v)) { // Skip interior vertices. continue; } - if (vertex->pos.x < minVertex[0]->pos.x) minVertex[0] = vertex; - else if (vertex->pos.x > maxVertex[0]->pos.x) maxVertex[0] = vertex; - if (vertex->pos.y < minVertex[1]->pos.y) minVertex[1] = vertex; - else if (vertex->pos.y > maxVertex[1]->pos.y) maxVertex[1] = vertex; - if (vertex->pos.z < minVertex[2]->pos.z) minVertex[2] = vertex; - else if (vertex->pos.z > maxVertex[2]->pos.z) maxVertex[2] = vertex; + const Vector3 &pos = mesh->position(v); + if (pos.x < mesh->position(minVertex[0]).x) + minVertex[0] = v; + else if (pos.x > mesh->position(maxVertex[0]).x) + maxVertex[0] = v; + if (pos.y < mesh->position(minVertex[1]).y) + minVertex[1] = v; + else if (pos.y > mesh->position(maxVertex[1]).y) + maxVertex[1] = v; + if (pos.z < mesh->position(minVertex[2]).z) + minVertex[2] = v; + else if (pos.z > mesh->position(maxVertex[2]).z) + maxVertex[2] = v; } float lengths[3]; for (int i = 0; i < 3; i++) { - lengths[i] = length(minVertex[i]->pos - maxVertex[i]->pos); + lengths[i] = length(mesh->position(minVertex[i]) - mesh->position(maxVertex[i])); } if (lengths[0] > lengths[1] && lengths[0] > lengths[2]) { *a = minVertex[0]; @@ -4678,34 +4540,28 @@ static bool findApproximateDiameterVertices(halfedge::Mesh *mesh, halfedge::Vert // Conformal relations from Brecht Van Lommel (based on ABF): -static float vec_angle_cos(Vector3::Arg v1, Vector3::Arg v2, Vector3::Arg v3) +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(Vector3::Arg v1, Vector3::Arg v2, Vector3::Arg v3) +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(Vector3::Arg v1, Vector3::Arg v2, Vector3::Arg v3, float *a1, float *a2, float *a3) +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 = PI - *a2 - *a1; + *a3 = kPi - *a2 - *a1; } -static void setup_abf_relations(sparse::Matrix &A, int row, const halfedge::Vertex *v0, const halfedge::Vertex *v1, const halfedge::Vertex *v2) +static void setup_abf_relations(sparse::Matrix &A, int row, int id0, int id1, int id2, const Vector3 &p0, const Vector3 &p1, const Vector3 &p2) { - int id0 = v0->id; - int id1 = v1->id; - int id2 = v2->id; - Vector3 p0 = v0->pos; - Vector3 p1 = v1->pos; - Vector3 p2 = v2->pos; // @@ 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! @@ -4715,19 +4571,19 @@ static void setup_abf_relations(sparse::Matrix &A, int row, const halfedge::Vert float s1 = sinf(a1); float s2 = sinf(a2); if (s1 > s0 && s1 > s2) { - std::swap(s1, s2); - std::swap(s0, s1); - std::swap(a1, a2); - std::swap(a0, a1); - std::swap(id1, id2); - std::swap(id0, id1); + swap(s1, s2); + swap(s0, s1); + swap(a1, a2); + swap(a0, a1); + swap(id1, id2); + swap(id0, id1); } else if (s0 > s1 && s0 > s2) { - std::swap(s0, s2); - std::swap(s0, s1); - std::swap(a0, a2); - std::swap(a0, a1); - std::swap(id0, id2); - std::swap(id0, id1); + 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; @@ -4755,14 +4611,13 @@ static void setup_abf_relations(sparse::Matrix &A, int row, const halfedge::Vert A.setCoefficient(v2_id, 2 * row + 1, 1); } -bool computeLeastSquaresConformalMap(halfedge::Mesh *mesh) +static bool computeLeastSquaresConformalMap(Mesh *mesh) { - xaDebugAssert(mesh != NULL); // For this to work properly, mesh should not have colocals that have the same // attributes, unless you want the vertices to actually have different texcoords. const uint32_t vertexCount = mesh->vertexCount(); const uint32_t D = 2 * vertexCount; - const uint32_t N = 2 * halfedge::countMeshTriangles(mesh); + const uint32_t N = 2 * mesh->faceCount(); // N is the number of equations (one per triangle) // D is the number of variables (one per vertex; there are 2 pinned vertices). if (N < D - 4) { @@ -4774,118 +4629,64 @@ bool computeLeastSquaresConformalMap(halfedge::Mesh *mesh) // Fill b: b.fill(0.0f); // Fill x: - halfedge::Vertex *v0; - halfedge::Vertex *v1; + uint32_t v0, v1; if (!findApproximateDiameterVertices(mesh, &v0, &v1)) { // Mesh has no boundaries. return false; } - if (v0->tex == v1->tex) { + if (mesh->texcoord(v0) == mesh->texcoord(v1)) { // LSCM expects an existing parameterization. return false; } for (uint32_t v = 0; v < vertexCount; v++) { - halfedge::Vertex *vertex = mesh->vertexAt(v); - xaDebugAssert(vertex != NULL); // Initial solution. - x[2 * v + 0] = vertex->tex.x; - x[2 * v + 1] = vertex->tex.y; + x[2 * v + 0] = mesh->texcoord(v).x; + x[2 * v + 1] = mesh->texcoord(v).y; } // Fill A: const uint32_t faceCount = mesh->faceCount(); for (uint32_t f = 0, t = 0; f < faceCount; f++) { - const halfedge::Face *face = mesh->faceAt(f); - xaDebugAssert(face != NULL); - xaDebugAssert(face->edgeCount() == 3); - const halfedge::Vertex *vertex0 = NULL; - for (halfedge::Face::ConstEdgeIterator it(face->edges()); !it.isDone(); it.advance()) { - const halfedge::Edge *edge = it.current(); - xaAssert(edge != NULL); - if (vertex0 == NULL) { - vertex0 = edge->vertex; - } else if (edge->next->vertex != vertex0) { - const halfedge::Vertex *vertex1 = edge->from(); - const halfedge::Vertex *vertex2 = edge->to(); - setup_abf_relations(A, t, vertex0, vertex1, vertex2); - //setup_conformal_map_relations(A, t, vertex0, vertex1, vertex2); - t++; - } - } + const uint32_t vertex0 = mesh->vertexAt(f * 3 + 0); + const uint32_t vertex1 = mesh->vertexAt(f * 3 + 1); + const uint32_t vertex2 = mesh->vertexAt(f * 3 + 2); + setup_abf_relations(A, t, vertex0, vertex1, vertex2, mesh->position(vertex0), mesh->position(vertex1), mesh->position(vertex2)); + t++; } const uint32_t lockedParameters[] = { - 2 * v0->id + 0, - 2 * v0->id + 1, - 2 * v1->id + 0, - 2 * v1->id + 1 + 2 * v0 + 0, + 2 * v0 + 1, + 2 * v1 + 0, + 2 * v1 + 1 }; // Solve Solver::LeastSquaresSolver(A, b, x, lockedParameters, 4, 0.000001f); // Map x back to texcoords: - for (uint32_t v = 0; v < vertexCount; v++) { - halfedge::Vertex *vertex = mesh->vertexAt(v); - xaDebugAssert(vertex != NULL); - vertex->tex = Vector2(x[2 * v + 0], x[2 * v + 1]); - } + for (uint32_t v = 0; v < vertexCount; v++) + mesh->texcoord(v) = Vector2(x[2 * v + 0], x[2 * v + 1]); return true; } -bool computeOrthogonalProjectionMap(halfedge::Mesh *mesh) +static bool computeOrthogonalProjectionMap(Mesh *mesh) { - Vector3 axis[2]; uint32_t vertexCount = mesh->vertexCount(); - std::vector<Vector3> points(vertexCount); - points.resize(vertexCount); - for (uint32_t i = 0; i < vertexCount; i++) { - points[i] = mesh->vertexAt(i)->pos; - } // Avoid redundant computations. float matrix[6]; - Fit::computeCovariance(vertexCount, points.data(), matrix); - if (matrix[0] == 0 && matrix[3] == 0 && matrix[5] == 0) { + Fit::computeCovariance(vertexCount, &mesh->position(0), matrix); + if (matrix[0] == 0 && matrix[3] == 0 && matrix[5] == 0) return false; - } float eigenValues[3]; Vector3 eigenVectors[3]; - if (!Fit::eigenSolveSymmetric3(matrix, eigenValues, eigenVectors)) { + if (!Fit::eigenSolveSymmetric3(matrix, eigenValues, eigenVectors)) return false; - } - axis[0] = normalize(eigenVectors[0]); - axis[1] = normalize(eigenVectors[1]); + Vector3 axis[2]; + axis[0] = normalize(eigenVectors[0], kEpsilon); + axis[1] = normalize(eigenVectors[1], kEpsilon); // Project vertices to plane. - for (halfedge::Mesh::VertexIterator it(mesh->vertices()); !it.isDone(); it.advance()) { - halfedge::Vertex *vertex = it.current(); - vertex->tex.x = dot(axis[0], vertex->pos); - vertex->tex.y = dot(axis[1], vertex->pos); - } + for (uint32_t i = 0; i < vertexCount; i++) + mesh->texcoord(i) = Vector2(dot(axis[0], mesh->position(i)), dot(axis[1], mesh->position(i))); return true; } -void computeSingleFaceMap(halfedge::Mesh *mesh) -{ - xaDebugAssert(mesh != NULL); - xaDebugAssert(mesh->faceCount() == 1); - halfedge::Face *face = mesh->faceAt(0); - xaAssert(face != NULL); - Vector3 p0 = face->edge->from()->pos; - Vector3 p1 = face->edge->to()->pos; - Vector3 X = normalizeSafe(p1 - p0, Vector3(0.0f), 0.0f); - Vector3 Z = face->normal(); - Vector3 Y = normalizeSafe(cross(Z, X), Vector3(0.0f), 0.0f); - uint32_t i = 0; - for (halfedge::Face::EdgeIterator it(face->edges()); !it.isDone(); it.advance(), i++) { - halfedge::Vertex *vertex = it.vertex(); - xaAssert(vertex != NULL); - if (i == 0) { - vertex->tex = Vector2(0); - } else { - Vector3 pn = vertex->pos; - float xn = dot((pn - p0), X); - float yn = dot((pn - p0), Y); - vertex->tex = Vector2(xn, yn); - } - } -} - // Dummy implementation of a priority queue using sort at insertion. // - Insertion is o(n) // - Smallest element goes at the end, so that popping it is o(1). @@ -4894,7 +4695,7 @@ void computeSingleFaceMap(halfedge::Mesh *mesh) // @@ Searcing at removal would remove the need for sorting when priorities change. struct PriorityQueue { - PriorityQueue(uint32_t size = UINT_MAX) : maxSize(size) {} + PriorityQueue(uint32_t size = UINT32_MAX) : maxSize(size) {} void push(float priority, uint32_t face) { @@ -4904,10 +4705,9 @@ struct PriorityQueue if (pairs[i].priority > priority) break; } Pair p = { priority, face }; - pairs.insert(pairs.begin() + i, p); - if (pairs.size() > maxSize) { - pairs.erase(pairs.begin()); - } + pairs.insertAt(i, p); + if (pairs.size() > maxSize) + pairs.removeAt(0); } // push face out of order, to be sorted later. @@ -4949,7 +4749,7 @@ struct PriorityQueue struct Pair { - bool operator <(const Pair &p) const + bool operator<(const Pair &p) const { return priority > p.priority; // !! Sort in inverse priority order! } @@ -4958,164 +4758,411 @@ struct PriorityQueue uint32_t face; }; - std::vector<Pair> pairs; + Array<Pair> pairs; }; struct ChartBuildData { - ChartBuildData(int id) : id(id) - { - planeNormal = Vector3(0); - centroid = Vector3(0); - coneAxis = Vector3(0); - coneAngle = 0; - area = 0; - boundaryLength = 0; - normalSum = Vector3(0); - centroidSum = Vector3(0); - } - - int id; - - // Proxy info: - Vector3 planeNormal; - Vector3 centroid; - Vector3 coneAxis; - float coneAngle; - - float area; - float boundaryLength; - Vector3 normalSum; - Vector3 centroidSum; - - std::vector<uint32_t> seeds; // @@ These could be a pointers to the halfedge faces directly. - std::vector<uint32_t> faces; + int id = -1; + Vector3 averageNormal = Vector3(0.0f); + float area = 0.0f; + float boundaryLength = 0.0f; + Vector3 normalSum = Vector3(0.0f); + Vector3 centroidSum = Vector3(0.0f); // Sum of chart face centroids. + Vector3 centroid = Vector3(0.0f); // Average centroid of chart faces. + Array<uint32_t> seeds; + Array<uint32_t> faces; PriorityQueue candidates; + Basis basis; // Of first face. }; struct AtlasBuilder { - AtlasBuilder(const halfedge::Mesh *m) : mesh(m), facesLeft(m->faceCount()) + // @@ Hardcoded to 10? + AtlasBuilder(const Mesh *mesh, Array<uint32_t> *meshFaces, const ChartOptions &options) : m_mesh(mesh), m_meshFaces(meshFaces), m_facesLeft(mesh->faceCount()), m_bestTriangles(10), m_options(options) { - const uint32_t faceCount = m->faceCount(); - faceChartArray.resize(faceCount, -1); - faceCandidateArray.resize(faceCount, (uint32_t)-1); + XA_PROFILE_START(atlasBuilderInit) + const uint32_t faceCount = m_mesh->faceCount(); + if (meshFaces) { + m_ignoreFaces.resize(faceCount, true); + for (uint32_t f = 0; f < meshFaces->size(); f++) + m_ignoreFaces[(*meshFaces)[f]] = false; + m_facesLeft = meshFaces->size(); + } else { + m_ignoreFaces.resize(faceCount, false); + } + m_faceChartArray.resize(faceCount, -1); + m_faceCandidateArray.resize(faceCount, (uint32_t)-1); + m_texcoords.resize(faceCount * 3); // @@ Floyd for the whole mesh is too slow. We could compute floyd progressively per patch as the patch grows. We need a better solution to compute most central faces. //computeShortestPaths(); // Precompute edge lengths and face areas. - uint32_t edgeCount = m->edgeCount(); - edgeLengths.resize(edgeCount); - for (uint32_t i = 0; i < edgeCount; i++) { - uint32_t id = m->edgeAt(i)->id; - xaDebugAssert(id / 2 == i); -#ifdef NDEBUG - id = 0; // silence unused parameter warning -#endif - edgeLengths[i] = m->edgeAt(i)->length(); - } - faceAreas.resize(faceCount); - for (uint32_t i = 0; i < faceCount; i++) { - faceAreas[i] = m->faceAt(i)->area(); + const uint32_t edgeCount = m_mesh->edgeCount(); + m_edgeLengths.resize(edgeCount, 0.0f); + m_faceAreas.resize(m_mesh->faceCount(), 0.0f); + m_faceNormals.resize(m_mesh->faceCount()); + for (uint32_t f = 0; f < faceCount; f++) { + if (m_ignoreFaces[f]) + continue; + for (Mesh::FaceEdgeIterator it(m_mesh, f); !it.isDone(); it.advance()) { + m_edgeLengths[it.edge()] = internal::length(it.position1() - it.position0()); + XA_DEBUG_ASSERT(m_edgeLengths[it.edge()] > 0.0f); + } + m_faceAreas[f] = mesh->faceArea(f); + XA_DEBUG_ASSERT(m_faceAreas[f] > 0.0f); + m_faceNormals[f] = m_mesh->triangleNormal(f); } + XA_PROFILE_END(atlasBuilderInit) } ~AtlasBuilder() { - const uint32_t chartCount = chartArray.size(); + const uint32_t chartCount = m_chartArray.size(); for (uint32_t i = 0; i < chartCount; i++) { - delete chartArray[i]; + m_chartArray[i]->~ChartBuildData(); + XA_FREE(m_chartArray[i]); } } - void markUnchartedFaces(const std::vector<uint32_t> &unchartedFaces) + uint32_t facesLeft() const { return m_facesLeft; } + uint32_t chartCount() const { return m_chartArray.size(); } + const Array<uint32_t> &chartFaces(uint32_t i) const { return m_chartArray[i]->faces; } + const Basis &chartBasis(uint32_t chartIndex) const { return m_chartArray[chartIndex]->basis; } + + void placeSeeds(float threshold) { - const uint32_t unchartedFaceCount = unchartedFaces.size(); - for (uint32_t i = 0; i < unchartedFaceCount; i++) { - uint32_t f = unchartedFaces[i]; - faceChartArray[f] = -2; - //faceCandidateArray[f] = -2; // @@ ? - removeCandidate(f); - } - xaDebugAssert(facesLeft >= unchartedFaceCount); - facesLeft -= unchartedFaceCount; + // Instead of using a predefiened number of seeds: + // - Add seeds one by one, growing chart until a certain treshold. + // - Undo charts and restart growing process. + // @@ How can we give preference to faces far from sharp features as in the LSCM paper? + // - those points can be found using a simple flood filling algorithm. + // - how do we weight the probabilities? + while (m_facesLeft > 0) + createRandomChart(threshold); } - void computeShortestPaths() + // Returns true if any of the charts can grow more. + bool growCharts(float threshold, uint32_t faceCount) { - const uint32_t faceCount = mesh->faceCount(); - shortestPaths.resize(faceCount * faceCount, FLT_MAX); - // Fill edges: + XA_PROFILE_START(atlasBuilderGrowCharts) + // Using one global list. + faceCount = min(faceCount, m_facesLeft); + bool canAddAny = false; for (uint32_t i = 0; i < faceCount; i++) { - shortestPaths[i * faceCount + i] = 0.0f; - const halfedge::Face *face_i = mesh->faceAt(i); - Vector3 centroid_i = face_i->centroid(); - for (halfedge::Face::ConstEdgeIterator it(face_i->edges()); !it.isDone(); it.advance()) { - const halfedge::Edge *edge = it.current(); - if (!edge->isBoundary()) { - const halfedge::Face *face_j = edge->pair->face; - uint32_t j = face_j->id; - Vector3 centroid_j = face_j->centroid(); - shortestPaths[i * faceCount + j] = shortestPaths[j * faceCount + i] = length(centroid_i - centroid_j); - } + const Candidate &candidate = getBestCandidate(); + if (candidate.metric > threshold) { + XA_PROFILE_END(atlasBuilderGrowCharts) + return false; // Can't grow more. } + createFaceTexcoords(candidate.chart, candidate.face); + if (!canAddFaceToChart(candidate.chart, candidate.face)) + continue; + addFaceToChart(candidate.chart, candidate.face); + canAddAny = true; } - // Use Floyd-Warshall algorithm to compute all paths: - for (uint32_t k = 0; k < faceCount; k++) { - for (uint32_t i = 0; i < faceCount; i++) { - for (uint32_t j = 0; j < faceCount; j++) { - shortestPaths[i * faceCount + j] = std::min(shortestPaths[i * faceCount + j], shortestPaths[i * faceCount + k] + shortestPaths[k * faceCount + j]); - } + XA_PROFILE_END(atlasBuilderGrowCharts) + return canAddAny && m_facesLeft != 0; // Can continue growing. + } + + void resetCharts() + { + const uint32_t faceCount = m_mesh->faceCount(); + for (uint32_t i = 0; i < faceCount; i++) { + m_faceChartArray[i] = -1; + m_faceCandidateArray[i] = (uint32_t)-1; + } + m_facesLeft = m_meshFaces ? m_meshFaces->size() : faceCount; + m_candidateArray.clear(); + const uint32_t chartCount = m_chartArray.size(); + for (uint32_t i = 0; i < chartCount; i++) { + ChartBuildData *chart = m_chartArray[i]; + const uint32_t seed = chart->seeds.back(); + chart->area = 0.0f; + chart->boundaryLength = 0.0f; + chart->normalSum = Vector3(0.0f); + chart->centroidSum = Vector3(0.0f); + chart->centroid = Vector3(0.0f); + chart->faces.clear(); + chart->candidates.clear(); + addFaceToChart(chart, seed); + } +#if XA_GROW_CHARTS_COPLANAR + for (uint32_t i = 0; i < chartCount; i++) { + ChartBuildData *chart = m_chartArray[i]; + growChartCoplanar(chart); + } +#endif + } + + void updateCandidates(ChartBuildData *chart, uint32_t f) + { + // Traverse neighboring faces, add the ones that do not belong to any chart yet. + for (Mesh::FaceEdgeIterator it(m_mesh, f); !it.isDone(); it.advance()) { + if (!it.isBoundary() && !m_ignoreFaces[it.oppositeFace()] && m_faceChartArray[it.oppositeFace()] == -1) + chart->candidates.push(it.oppositeFace()); + } + } + + void updateProxies() + { + const uint32_t chartCount = m_chartArray.size(); + for (uint32_t i = 0; i < chartCount; i++) + updateProxy(m_chartArray[i]); + } + + bool relocateSeeds() + { + bool anySeedChanged = false; + const uint32_t chartCount = m_chartArray.size(); + for (uint32_t i = 0; i < chartCount; i++) { + if (relocateSeed(m_chartArray[i])) { + anySeedChanged = true; } } + return anySeedChanged; } - void placeSeeds(float threshold, uint32_t maxSeedCount) + void fillHoles(float threshold) { - // Instead of using a predefiened number of seeds: - // - Add seeds one by one, growing chart until a certain treshold. - // - Undo charts and restart growing process. - // @@ How can we give preference to faces far from sharp features as in the LSCM paper? - // - those points can be found using a simple flood filling algorithm. - // - how do we weight the probabilities? - for (uint32_t i = 0; i < maxSeedCount; i++) { - if (facesLeft == 0) { - // No faces left, stop creating seeds. + while (m_facesLeft > 0) + createRandomChart(threshold); + } + +#if XA_MERGE_CHARTS + void mergeCharts() + { + XA_PROFILE_START(atlasBuilderMergeCharts) + Array<float> sharedBoundaryLengths; + Array<float> sharedBoundaryLengthsNoSeams; + Array<uint32_t> sharedBoundaryEdgeCountNoSeams; + Array<Vector2> tempTexcoords; + const uint32_t chartCount = m_chartArray.size(); + // Merge charts progressively until there's none left to merge. + for (;;) { + bool merged = false; + for (int c = chartCount - 1; c >= 0; c--) { + ChartBuildData *chart = m_chartArray[c]; + if (chart == nullptr) + continue; + float externalBoundaryLength = 0.0f; + sharedBoundaryLengths.clear(); + sharedBoundaryLengths.resize(chartCount, 0.0f); + sharedBoundaryLengthsNoSeams.clear(); + sharedBoundaryLengthsNoSeams.resize(chartCount, 0.0f); + sharedBoundaryEdgeCountNoSeams.clear(); + sharedBoundaryEdgeCountNoSeams.resize(chartCount, 0u); + const uint32_t faceCount = chart->faces.size(); + for (uint32_t i = 0; i < faceCount; i++) { + const uint32_t f = chart->faces[i]; + for (Mesh::FaceEdgeIterator it(m_mesh, f); !it.isDone(); it.advance()) { + const float l = m_edgeLengths[it.edge()]; + if (it.isBoundary() || m_ignoreFaces[it.oppositeFace()]) { + externalBoundaryLength += l; + } else { + const int neighborChart = m_faceChartArray[it.oppositeFace()]; + if (m_chartArray[neighborChart] != chart) { + if ((it.isSeam() && (isNormalSeam(it.edge()) || it.isTextureSeam()))) { + externalBoundaryLength += l; + } else { + sharedBoundaryLengths[neighborChart] += l; + } + sharedBoundaryLengthsNoSeams[neighborChart] += l; + sharedBoundaryEdgeCountNoSeams[neighborChart]++; + } + } + } + } + for (int cc = chartCount - 1; cc >= 0; cc--) { + if (cc == c) + continue; + ChartBuildData *chart2 = m_chartArray[cc]; + if (chart2 == nullptr) + continue; + // Compare proxies. + if (dot(chart2->averageNormal, chart->averageNormal) < XA_MERGE_CHARTS_MIN_NORMAL_DEVIATION) + continue; + // Obey max chart area and boundary length. + if (m_options.maxChartArea > 0.0f && chart->area + chart2->area > m_options.maxChartArea) + continue; + if (m_options.maxBoundaryLength > 0.0f && chart->boundaryLength + chart2->boundaryLength - sharedBoundaryLengthsNoSeams[cc] > m_options.maxBoundaryLength) + continue; + // Merge if chart2 has a single face. + // chart1 must have more than 1 face. + // chart2 area must be <= 10% of chart1 area. + if (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 && sharedBoundaryEdgeCountNoSeams[cc] >= 2) + goto merge; + // Merge if chart2 is wholely inside chart1, ignoring seams. + if (sharedBoundaryLengthsNoSeams[cc] > 0.0f && equal(sharedBoundaryLengthsNoSeams[cc], chart2->boundaryLength, kEpsilon)) + goto merge; + if (sharedBoundaryLengths[cc] > 0.2f * max(0.0f, chart->boundaryLength - externalBoundaryLength) || + sharedBoundaryLengths[cc] > 0.75f * chart2->boundaryLength) + goto merge; + continue; + merge: + // Create texcoords for chart 2 using chart 1 basis. Backup chart 2 texcoords for restoration if charts cannot be merged. + tempTexcoords.resize(chart2->faces.size()); + for (uint32_t i = 0; i < chart2->faces.size(); i++) { + const uint32_t face = chart2->faces[i]; + tempTexcoords[i] = m_texcoords[face]; + createFaceTexcoords(chart, face); + } + if (!canMergeCharts(chart, chart2)) { + // Restore chart 2 texcoords. + for (uint32_t i = 0; i < chart2->faces.size(); i++) + m_texcoords[chart2->faces[i]] = tempTexcoords[i]; + continue; + } + mergeChart(chart, chart2, sharedBoundaryLengthsNoSeams[cc]); + merged = true; + break; + } + if (merged) + break; + } + if (!merged) break; + } + // Remove deleted charts. + for (int c = 0; c < int32_t(m_chartArray.size()); /*do not increment if removed*/) { + if (m_chartArray[c] == nullptr) { + m_chartArray.removeAt(c); + // Update m_faceChartArray. + const uint32_t faceCount = m_faceChartArray.size(); + for (uint32_t i = 0; i < faceCount; i++) { + XA_DEBUG_ASSERT(m_faceChartArray[i] != c); + XA_DEBUG_ASSERT(m_faceChartArray[i] <= int32_t(m_chartArray.size())); + if (m_faceChartArray[i] > c) { + m_faceChartArray[i]--; + } + } + } else { + m_chartArray[c]->id = c; + c++; } - createRandomChart(threshold); } + XA_PROFILE_END(atlasBuilderMergeCharts) } +#endif +private: void createRandomChart(float threshold) { - ChartBuildData *chart = new ChartBuildData(chartArray.size()); - chartArray.push_back(chart); + ChartBuildData *chart = XA_NEW(MemTag::Default, ChartBuildData); + chart->id = (int)m_chartArray.size(); + m_chartArray.push_back(chart); // Pick random face that is not used by any chart yet. - uint32_t randomFaceIdx = rand.getRange(facesLeft - 1); - uint32_t i = 0; - for (uint32_t f = 0; f != randomFaceIdx; f++, i++) { - while (faceChartArray[i] != -1) i++; - } - while (faceChartArray[i] != -1) i++; - chart->seeds.push_back(i); - addFaceToChart(chart, i, true); + uint32_t face = m_rand.getRange(m_mesh->faceCount() - 1); + while (m_ignoreFaces[face] || m_faceChartArray[face] != -1) { + if (++face >= m_mesh->faceCount()) + face = 0; + } + chart->seeds.push_back(face); + addFaceToChart(chart, face, true); +#if XA_GROW_CHARTS_COPLANAR + growChartCoplanar(chart); +#endif // Grow the chart as much as possible within the given threshold. - growChart(chart, threshold * 0.5f, facesLeft); - //growCharts(threshold - threshold * 0.75f / chartCount(), facesLeft); + growChart(chart, threshold, m_facesLeft); + } + + void createFaceTexcoords(ChartBuildData *chart, uint32_t face) + { + for (uint32_t i = 0; i < 3; i++) { + const Vector3 &pos = m_mesh->position(m_mesh->vertexAt(face * 3 + i)); + m_texcoords[face * 3 + i] = Vector2(dot(chart->basis.tangent, pos), dot(chart->basis.bitangent, pos)); + } + } + + bool isChartBoundaryEdge(ChartBuildData *chart, uint32_t edge) const + { + const uint32_t oppositeEdge = m_mesh->oppositeEdge(edge); + const uint32_t oppositeFace = meshEdgeFace(oppositeEdge); + return oppositeEdge == UINT32_MAX || m_ignoreFaces[oppositeFace] || m_faceChartArray[oppositeFace] != chart->id; + } + + bool canAddFaceToChart(ChartBuildData *chart, uint32_t face) + { + // Find face edges that are on a mesh boundary or form a boundary with another chart. + uint32_t edgesToCompare[3]; + for (uint32_t i = 0; i < 3; i++) { + const uint32_t edge = face * 3 + i; + const uint32_t oppositeEdge = m_mesh->oppositeEdge(edge); + const uint32_t oppositeFace = meshEdgeFace(oppositeEdge); + if (oppositeEdge == UINT32_MAX || m_ignoreFaces[oppositeFace] || m_faceChartArray[oppositeFace] != chart->id) + edgesToCompare[i] = edge; + else + edgesToCompare[i] = UINT32_MAX; + } + // All edges on boundary? This can happen if the face is surrounded by the chart. + if (edgesToCompare[0] == UINT32_MAX && edgesToCompare[1] == UINT32_MAX && edgesToCompare[2] == UINT32_MAX) + return true; + // Check if any valid face edge intersects the chart boundary. + for (uint32_t i = 0; i < chart->faces.size(); i++) { + const uint32_t chartFace = chart->faces[i]; + for (uint32_t j = 0; j < 3; j++) { + const uint32_t chartEdge = chartFace * 3 + j; + if (!isChartBoundaryEdge(chart, chartEdge)) + continue; + // Don't check chart boundary edges that border the face. + const uint32_t oppositeChartEdge = m_mesh->oppositeEdge(chartEdge); + if (meshEdgeFace(oppositeChartEdge) == face) + continue; + for (uint32_t k = 0; k < 3; k++) { + if (edgesToCompare[k] == UINT32_MAX) + continue; + const uint32_t e1 = chartEdge; + const uint32_t e2 = edgesToCompare[k]; + if (linesIntersect(m_texcoords[meshEdgeIndex0(e1)], m_texcoords[meshEdgeIndex1(e1)], m_texcoords[meshEdgeIndex0(e2)], m_texcoords[meshEdgeIndex1(e2)], m_mesh->epsilon())) + return false; + } + } + } + return true; + } + + bool canMergeCharts(ChartBuildData *chart1, ChartBuildData *chart2) + { + for (uint32_t f1 = 0; f1 < chart1->faces.size(); f1++) { + const uint32_t face1 = chart1->faces[f1]; + for (uint32_t i = 0; i < 3; i++) { + const uint32_t edge1 = face1 * 3 + i; + if (!isChartBoundaryEdge(chart1, edge1)) + continue; + for (uint32_t f2 = 0; f2 < chart2->faces.size(); f2++) { + const uint32_t face2 = chart2->faces[f2]; + for (uint32_t j = 0; j < 3; j++) { + const uint32_t edge2 = face2 * 3 + j; + if (!isChartBoundaryEdge(chart2, edge2)) + continue; + if (linesIntersect(m_texcoords[meshEdgeIndex0(edge1)], m_texcoords[meshEdgeIndex1(edge1)], m_texcoords[meshEdgeIndex0(edge2)], m_texcoords[meshEdgeIndex1(edge2)], m_mesh->epsilon())) + return false; + } + } + } + } + return true; } void addFaceToChart(ChartBuildData *chart, uint32_t f, bool recomputeProxy = false) { + // Use the first face normal as the chart basis. + if (chart->faces.isEmpty()) { + chart->basis.buildFrameForDirection(m_faceNormals[f]); + createFaceTexcoords(chart, f); + } // Add face to chart. chart->faces.push_back(f); - xaDebugAssert(faceChartArray[f] == -1); - faceChartArray[f] = chart->id; - facesLeft--; + XA_DEBUG_ASSERT(m_faceChartArray[f] == -1); + m_faceChartArray[f] = chart->id; + m_facesLeft--; // Update area and boundary length. chart->area = evaluateChartArea(chart, f); chart->boundaryLength = evaluateBoundaryLength(chart, f); chart->normalSum = evaluateChartNormalSum(chart, f); - chart->centroidSum = evaluateChartCentroidSum(chart, f); + chart->centroidSum += m_mesh->triangleCenter(f); if (recomputeProxy) { // Update proxy and candidate's priorities. updateProxy(chart); @@ -5126,142 +5173,93 @@ struct AtlasBuilder updatePriorities(chart); } - // Returns true if any of the charts can grow more. - bool growCharts(float threshold, uint32_t faceCount) - { - // Using one global list. - faceCount = std::min(faceCount, facesLeft); - for (uint32_t i = 0; i < faceCount; i++) { - const Candidate &candidate = getBestCandidate(); - if (candidate.metric > threshold) { - return false; // Can't grow more. - } - addFaceToChart(candidate.chart, candidate.face); - } - return facesLeft != 0; // Can continue growing. - } - bool growChart(ChartBuildData *chart, float threshold, uint32_t faceCount) { // Try to add faceCount faces within threshold to chart. for (uint32_t i = 0; i < faceCount; ) { - if (chart->candidates.count() == 0 || chart->candidates.firstPriority() > threshold) { + if (chart->candidates.count() == 0 || chart->candidates.firstPriority() > threshold) return false; - } - uint32_t f = chart->candidates.pop(); - if (faceChartArray[f] == -1) { - addFaceToChart(chart, f); - i++; - } + const uint32_t f = chart->candidates.pop(); + if (m_faceChartArray[f] != -1) + continue; + createFaceTexcoords(chart, f); + if (!canAddFaceToChart(chart, f)) + continue; + addFaceToChart(chart, f); + i++; } - if (chart->candidates.count() == 0 || chart->candidates.firstPriority() > threshold) { + if (chart->candidates.count() == 0 || chart->candidates.firstPriority() > threshold) return false; - } return true; } - void resetCharts() +#if XA_GROW_CHARTS_COPLANAR + void growChartCoplanar(ChartBuildData *chart) { - const uint32_t faceCount = mesh->faceCount(); - for (uint32_t i = 0; i < faceCount; i++) { - faceChartArray[i] = -1; - faceCandidateArray[i] = (uint32_t)-1; - } - facesLeft = faceCount; - candidateArray.clear(); - const uint32_t chartCount = chartArray.size(); - for (uint32_t i = 0; i < chartCount; i++) { - ChartBuildData *chart = chartArray[i]; - const uint32_t seed = chart->seeds.back(); - chart->area = 0.0f; - chart->boundaryLength = 0.0f; - chart->normalSum = Vector3(0); - chart->centroidSum = Vector3(0); - chart->faces.clear(); - chart->candidates.clear(); - addFaceToChart(chart, seed); - } - } - - void updateCandidates(ChartBuildData *chart, uint32_t f) - { - const halfedge::Face *face = mesh->faceAt(f); - // Traverse neighboring faces, add the ones that do not belong to any chart yet. - for (halfedge::Face::ConstEdgeIterator it(face->edges()); !it.isDone(); it.advance()) { - const halfedge::Edge *edge = it.current()->pair; - if (!edge->isBoundary()) { - uint32_t faceId = edge->face->id; - if (faceChartArray[faceId] == -1) { - chart->candidates.push(faceId); + XA_DEBUG_ASSERT(!chart->faces.isEmpty()); + const Vector3 chartNormal = m_faceNormals[chart->faces[0]]; + m_growFaces.clear(); + for (uint32_t f = 0; f < chart->faces.size(); f++) + m_growFaces.push_back(chart->faces[f]); + for (;;) { + if (m_growFaces.isEmpty()) + break; + const uint32_t face = m_growFaces.back(); + m_growFaces.pop_back(); + for (Mesh::FaceEdgeIterator it(m_mesh, face); !it.isDone(); it.advance()) { + if (it.isBoundary() || m_ignoreFaces[it.oppositeFace()] || m_faceChartArray[it.oppositeFace()] != -1) + continue; + if (equal(dot(chartNormal, m_faceNormals[it.oppositeFace()]), 1.0f, kEpsilon)) { + createFaceTexcoords(chart, it.oppositeFace()); + addFaceToChart(chart, it.oppositeFace()); + m_growFaces.push_back(it.oppositeFace()); } } } } +#endif - void updateProxies() - { - const uint32_t chartCount = chartArray.size(); - for (uint32_t i = 0; i < chartCount; i++) { - updateProxy(chartArray[i]); - } - } - - void updateProxy(ChartBuildData *chart) + void updateProxy(ChartBuildData *chart) const { //#pragma message(NV_FILE_LINE "TODO: Use best fit plane instead of average normal.") - chart->planeNormal = normalizeSafe(chart->normalSum, Vector3(0), 0.0f); + chart->averageNormal = normalizeSafe(chart->normalSum, Vector3(0), 0.0f); chart->centroid = chart->centroidSum / float(chart->faces.size()); } - bool relocateSeeds() - { - bool anySeedChanged = false; - const uint32_t chartCount = chartArray.size(); - for (uint32_t i = 0; i < chartCount; i++) { - if (relocateSeed(chartArray[i])) { - anySeedChanged = true; - } - } - return anySeedChanged; - } - bool relocateSeed(ChartBuildData *chart) { - Vector3 centroid = computeChartCentroid(chart); - const uint32_t N = 10; // @@ Hardcoded to 10? - PriorityQueue bestTriangles(N); // Find the first N triangles that fit the proxy best. const uint32_t faceCount = chart->faces.size(); + m_bestTriangles.clear(); for (uint32_t i = 0; i < faceCount; i++) { float priority = evaluateProxyFitMetric(chart, chart->faces[i]); - bestTriangles.push(priority, chart->faces[i]); + m_bestTriangles.push(priority, chart->faces[i]); } - // Of those, choose the most central triangle. - uint32_t mostCentral; + // Of those, choose the least central triangle. + uint32_t leastCentral = 0; float maxDistance = -1; - const uint32_t bestCount = bestTriangles.count(); + const uint32_t bestCount = m_bestTriangles.count(); for (uint32_t i = 0; i < bestCount; i++) { - const halfedge::Face *face = mesh->faceAt(bestTriangles.pairs[i].face); - Vector3 faceCentroid = face->triangleCenter(); - float distance = length(centroid - faceCentroid); + Vector3 faceCentroid = m_mesh->triangleCenter(m_bestTriangles.pairs[i].face); + float distance = length(chart->centroid - faceCentroid); if (distance > maxDistance) { maxDistance = distance; - mostCentral = bestTriangles.pairs[i].face; + leastCentral = m_bestTriangles.pairs[i].face; } } - xaDebugAssert(maxDistance >= 0); + XA_DEBUG_ASSERT(maxDistance >= 0); // In order to prevent k-means cyles we record all the previously chosen seeds. - uint32_t index = std::find(chart->seeds.begin(), chart->seeds.end(), mostCentral) - chart->seeds.begin(); - if (index < chart->seeds.size()) { - // Move new seed to the end of the seed array. - uint32_t last = chart->seeds.size() - 1; - std::swap(chart->seeds[index], chart->seeds[last]); - return false; - } else { - // Append new seed. - chart->seeds.push_back(mostCentral); - return true; + for (uint32_t i = 0; i < chart->seeds.size(); i++) { + if (chart->seeds[i] == leastCentral) { + // Move new seed to the end of the seed array. + uint32_t last = chart->seeds.size() - 1; + swap(chart->seeds[i], chart->seeds[last]); + return false; + } } + // Append new seed. + chart->seeds.push_back(leastCentral); + return true; } void updatePriorities(ChartBuildData *chart) @@ -5269,299 +5267,197 @@ struct AtlasBuilder // Re-evaluate candidate priorities. uint32_t candidateCount = chart->candidates.count(); for (uint32_t i = 0; i < candidateCount; i++) { - chart->candidates.pairs[i].priority = evaluatePriority(chart, chart->candidates.pairs[i].face); - if (faceChartArray[chart->candidates.pairs[i].face] == -1) { - updateCandidate(chart, chart->candidates.pairs[i].face, chart->candidates.pairs[i].priority); - } + PriorityQueue::Pair &pair = chart->candidates.pairs[i]; + pair.priority = evaluatePriority(chart, pair.face); + if (m_faceChartArray[pair.face] == -1) + updateCandidate(chart, pair.face, pair.priority); } // Sort candidates. chart->candidates.sort(); } // Evaluate combined metric. - float evaluatePriority(ChartBuildData *chart, uint32_t face) + float evaluatePriority(ChartBuildData *chart, uint32_t face) const { // Estimate boundary length and area: - float newBoundaryLength = evaluateBoundaryLength(chart, face); - float newChartArea = evaluateChartArea(chart, face); - float F = evaluateProxyFitMetric(chart, face); - float C = evaluateRoundnessMetric(chart, face, newBoundaryLength, newChartArea); - float P = evaluateStraightnessMetric(chart, face); + const float newChartArea = evaluateChartArea(chart, face); + const float newBoundaryLength = evaluateBoundaryLength(chart, face); + // Enforce limits strictly: + if (m_options.maxChartArea > 0.0f && newChartArea > m_options.maxChartArea) + return FLT_MAX; + if (m_options.maxBoundaryLength > 0.0f && newBoundaryLength > m_options.maxBoundaryLength) + return FLT_MAX; + if (dot(m_faceNormals[face], chart->averageNormal) < 0.5f) + return FLT_MAX; // Penalize faces that cross seams, reward faces that close seams or reach boundaries. - float N = evaluateNormalSeamMetric(chart, face); - float T = evaluateTextureSeamMetric(chart, face); + // Make sure normal seams are fully respected: + const float N = evaluateNormalSeamMetric(chart, face); + if (m_options.normalSeamMetricWeight >= 1000.0f && N > 0.0f) + return FLT_MAX; + float cost = m_options.normalSeamMetricWeight * N; + if (m_options.proxyFitMetricWeight > 0.0f) + cost += m_options.proxyFitMetricWeight * evaluateProxyFitMetric(chart, face); + if (m_options.roundnessMetricWeight > 0.0f) + cost += m_options.roundnessMetricWeight * evaluateRoundnessMetric(chart, face, newBoundaryLength, newChartArea); + if (m_options.straightnessMetricWeight > 0.0f) + cost += m_options.straightnessMetricWeight * evaluateStraightnessMetric(chart, face); + if (m_options.textureSeamMetricWeight > 0.0f) + cost += m_options.textureSeamMetricWeight * evaluateTextureSeamMetric(chart, face); //float R = evaluateCompletenessMetric(chart, face); //float D = evaluateDihedralAngleMetric(chart, face); // @@ Add a metric based on local dihedral angle. // @@ Tweaking the normal and texture seam metrics. // - Cause more impedance. Never cross 90 degree edges. - // - - float cost = float( - options.proxyFitMetricWeight * F + - options.roundnessMetricWeight * C + - options.straightnessMetricWeight * P + - options.normalSeamMetricWeight * N + - options.textureSeamMetricWeight * T); - // Enforce limits strictly: - if (newChartArea > options.maxChartArea) cost = FLT_MAX; - if (newBoundaryLength > options.maxBoundaryLength) cost = FLT_MAX; - // Make sure normal seams are fully respected: - if (options.normalSeamMetricWeight >= 1000 && N != 0) cost = FLT_MAX; - xaAssert(std::isfinite(cost)); + XA_DEBUG_ASSERT(isFinite(cost)); return cost; } // Returns a value in [0-1]. - float evaluateProxyFitMetric(ChartBuildData *chart, uint32_t f) + float evaluateProxyFitMetric(ChartBuildData *chart, uint32_t f) const { - const halfedge::Face *face = mesh->faceAt(f); - Vector3 faceNormal = face->triangleNormal(); + const Vector3 faceNormal = m_faceNormals[f]; // Use plane fitting metric for now: - return 1 - dot(faceNormal, chart->planeNormal); // @@ normal deviations should be weighted by face area + return 1 - dot(faceNormal, chart->averageNormal); // @@ normal deviations should be weighted by face area } - float evaluateRoundnessMetric(ChartBuildData *chart, uint32_t /*face*/, float newBoundaryLength, float newChartArea) + float evaluateRoundnessMetric(ChartBuildData *chart, uint32_t /*face*/, float newBoundaryLength, float newChartArea) const { float roundness = square(chart->boundaryLength) / chart->area; float newRoundness = square(newBoundaryLength) / newChartArea; if (newRoundness > roundness) { - return square(newBoundaryLength) / (newChartArea * 4 * PI); + return square(newBoundaryLength) / (newChartArea * 4.0f * kPi); } else { // Offer no impedance to faces that improve roundness. return 0; } } - float evaluateStraightnessMetric(ChartBuildData *chart, uint32_t f) + float evaluateStraightnessMetric(ChartBuildData *chart, uint32_t f) const { float l_out = 0.0f; float l_in = 0.0f; - const halfedge::Face *face = mesh->faceAt(f); - for (halfedge::Face::ConstEdgeIterator it(face->edges()); !it.isDone(); it.advance()) { - const halfedge::Edge *edge = it.current(); - float l = edgeLengths[edge->id / 2]; - if (edge->isBoundary()) { + for (Mesh::FaceEdgeIterator it(m_mesh, f); !it.isDone(); it.advance()) { + float l = m_edgeLengths[it.edge()]; + if (it.isBoundary() || m_ignoreFaces[it.oppositeFace()]) { l_out += l; } else { - uint32_t neighborFaceId = edge->pair->face->id; - if (faceChartArray[neighborFaceId] != chart->id) { + if (m_faceChartArray[it.oppositeFace()] != chart->id) { l_out += l; } else { l_in += l; } } } - xaDebugAssert(l_in != 0.0f); // Candidate face must be adjacent to chart. @@ This is not true if the input mesh has zero-length edges. + 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 std::min(ratio, 0.0f); // Only use the straightness metric to close gaps. + return min(ratio, 0.0f); // Only use the straightness metric to close gaps. } - float evaluateNormalSeamMetric(ChartBuildData *chart, uint32_t f) + bool isNormalSeam(uint32_t edge) const + { + const uint32_t oppositeEdge = m_mesh->oppositeEdge(edge); + if (oppositeEdge == UINT32_MAX) + return false; // boundary edge + if (m_mesh->flags() & MeshFlags::HasNormals) { + const uint32_t v0 = m_mesh->vertexAt(meshEdgeIndex0(edge)); + const uint32_t v1 = m_mesh->vertexAt(meshEdgeIndex1(edge)); + const uint32_t ov0 = m_mesh->vertexAt(meshEdgeIndex0(oppositeEdge)); + const uint32_t ov1 = m_mesh->vertexAt(meshEdgeIndex1(oppositeEdge)); + return m_mesh->normal(v0) != m_mesh->normal(ov1) || m_mesh->normal(v1) != m_mesh->normal(ov0); + } + return m_faceNormals[meshEdgeFace(edge)] != m_faceNormals[meshEdgeFace(oppositeEdge)]; + } + + float evaluateNormalSeamMetric(ChartBuildData *chart, uint32_t f) const { float seamFactor = 0.0f; float totalLength = 0.0f; - const halfedge::Face *face = mesh->faceAt(f); - for (halfedge::Face::ConstEdgeIterator it(face->edges()); !it.isDone(); it.advance()) { - const halfedge::Edge *edge = it.current(); - if (edge->isBoundary()) { + for (Mesh::FaceEdgeIterator it(m_mesh, f); !it.isDone(); it.advance()) { + if (it.isBoundary() || m_ignoreFaces[it.oppositeFace()]) continue; - } - const uint32_t neighborFaceId = edge->pair->face->id; - if (faceChartArray[neighborFaceId] != chart->id) { + if (m_faceChartArray[it.oppositeFace()] != chart->id) continue; - } - //float l = edge->length(); - float l = edgeLengths[edge->id / 2]; + float l = m_edgeLengths[it.edge()]; totalLength += l; - if (!edge->isSeam()) { + if (!it.isSeam()) continue; - } // Make sure it's a normal seam. - if (edge->isNormalSeam()) { - float d0 = clamp(dot(edge->vertex->nor, edge->pair->next->vertex->nor), 0.0f, 1.0f); - float d1 = clamp(dot(edge->next->vertex->nor, edge->pair->vertex->nor), 0.0f, 1.0f); - l *= 1 - (d0 + d1) * 0.5f; + if (isNormalSeam(it.edge())) { + float d; + if (m_mesh->flags() & MeshFlags::HasNormals) { + const Vector3 &n0 = m_mesh->normal(it.vertex0()); + const Vector3 &n1 = m_mesh->normal(it.vertex1()); + const Vector3 &on0 = m_mesh->normal(m_mesh->vertexAt(meshEdgeIndex0(it.oppositeEdge()))); + const Vector3 &on1 = m_mesh->normal(m_mesh->vertexAt(meshEdgeIndex1(it.oppositeEdge()))); + const float d0 = clamp(dot(n0, on1), 0.0f, 1.0f); + const float d1 = clamp(dot(n1, on0), 0.0f, 1.0f); + d = (d0 + d1) * 0.5f; + } else { + d = clamp(dot(m_faceNormals[f], m_faceNormals[meshEdgeFace(it.oppositeEdge())]), 0.0f, 1.0f); + } + l *= 1 - d; seamFactor += l; } } - if (seamFactor == 0) return 0.0f; + if (seamFactor <= 0.0f) + return 0.0f; return seamFactor / totalLength; } - float evaluateTextureSeamMetric(ChartBuildData *chart, uint32_t f) + float evaluateTextureSeamMetric(ChartBuildData *chart, uint32_t f) const { float seamLength = 0.0f; float totalLength = 0.0f; - const halfedge::Face *face = mesh->faceAt(f); - for (halfedge::Face::ConstEdgeIterator it(face->edges()); !it.isDone(); it.advance()) { - const halfedge::Edge *edge = it.current(); - if (edge->isBoundary()) { + for (Mesh::FaceEdgeIterator it(m_mesh, f); !it.isDone(); it.advance()) { + if (it.isBoundary() || m_ignoreFaces[it.oppositeFace()]) continue; - } - const uint32_t neighborFaceId = edge->pair->face->id; - if (faceChartArray[neighborFaceId] != chart->id) { + if (m_faceChartArray[it.oppositeFace()] != chart->id) continue; - } - //float l = edge->length(); - float l = edgeLengths[edge->id / 2]; + float l = m_edgeLengths[it.edge()]; totalLength += l; - if (!edge->isSeam()) { + if (!it.isSeam()) continue; - } // Make sure it's a texture seam. - if (edge->isTextureSeam()) { + if (it.isTextureSeam()) seamLength += l; - } } - if (seamLength == 0.0f) { + if (seamLength == 0.0f) return 0.0f; // Avoid division by zero. - } return seamLength / totalLength; } - float evaluateChartArea(ChartBuildData *chart, uint32_t f) + float evaluateChartArea(ChartBuildData *chart, uint32_t f) const { - const halfedge::Face *face = mesh->faceAt(f); - return chart->area + faceAreas[face->id]; + return chart->area + m_faceAreas[f]; } - float evaluateBoundaryLength(ChartBuildData *chart, uint32_t f) + float evaluateBoundaryLength(ChartBuildData *chart, uint32_t f) const { float boundaryLength = chart->boundaryLength; // Add new edges, subtract edges shared with the chart. - const halfedge::Face *face = mesh->faceAt(f); - for (halfedge::Face::ConstEdgeIterator it(face->edges()); !it.isDone(); it.advance()) { - const halfedge::Edge *edge = it.current(); - //float edgeLength = edge->length(); - float edgeLength = edgeLengths[edge->id / 2]; - if (edge->isBoundary()) { + for (Mesh::FaceEdgeIterator it(m_mesh, f); !it.isDone(); it.advance()) { + const float edgeLength = m_edgeLengths[it.edge()]; + if (it.isBoundary() || m_ignoreFaces[it.oppositeFace()]) { boundaryLength += edgeLength; } else { - uint32_t neighborFaceId = edge->pair->face->id; - if (faceChartArray[neighborFaceId] != chart->id) { + if (m_faceChartArray[it.oppositeFace()] != chart->id) boundaryLength += edgeLength; - } else { + else boundaryLength -= edgeLength; - } } } - return std::max(0.0f, boundaryLength); // @@ Hack! - } - - Vector3 evaluateChartNormalSum(ChartBuildData *chart, uint32_t f) - { - const halfedge::Face *face = mesh->faceAt(f); - return chart->normalSum + face->triangleNormalAreaScaled(); + return max(0.0f, boundaryLength); // @@ Hack! } - Vector3 evaluateChartCentroidSum(ChartBuildData *chart, uint32_t f) + Vector3 evaluateChartNormalSum(ChartBuildData *chart, uint32_t f) const { - const halfedge::Face *face = mesh->faceAt(f); - return chart->centroidSum + face->centroid(); - } - - Vector3 computeChartCentroid(const ChartBuildData *chart) - { - Vector3 centroid(0); - const uint32_t faceCount = chart->faces.size(); - for (uint32_t i = 0; i < faceCount; i++) { - const halfedge::Face *face = mesh->faceAt(chart->faces[i]); - centroid += face->triangleCenter(); - } - return centroid / float(faceCount); - } - - void fillHoles(float threshold) - { - while (facesLeft > 0) - createRandomChart(threshold); - } - - void mergeCharts() - { - std::vector<float> sharedBoundaryLengths; - const uint32_t chartCount = chartArray.size(); - for (int c = chartCount - 1; c >= 0; c--) { - sharedBoundaryLengths.clear(); - sharedBoundaryLengths.resize(chartCount, 0.0f); - ChartBuildData *chart = chartArray[c]; - float externalBoundary = 0.0f; - const uint32_t faceCount = chart->faces.size(); - for (uint32_t i = 0; i < faceCount; i++) { - uint32_t f = chart->faces[i]; - const halfedge::Face *face = mesh->faceAt(f); - for (halfedge::Face::ConstEdgeIterator it(face->edges()); !it.isDone(); it.advance()) { - const halfedge::Edge *edge = it.current(); - //float l = edge->length(); - float l = edgeLengths[edge->id / 2]; - if (edge->isBoundary()) { - externalBoundary += l; - } else { - uint32_t neighborFace = edge->pair->face->id; - uint32_t neighborChart = faceChartArray[neighborFace]; - if (neighborChart != (uint32_t)c) { - if ((edge->isSeam() && (edge->isNormalSeam() || edge->isTextureSeam())) || neighborChart == -2) { - externalBoundary += l; - } else { - sharedBoundaryLengths[neighborChart] += l; - } - } - } - } - } - for (int cc = chartCount - 1; cc >= 0; cc--) { - if (cc == c) - continue; - ChartBuildData *chart2 = chartArray[cc]; - if (chart2 == NULL) - continue; - if (sharedBoundaryLengths[cc] > 0.8 * std::max(0.0f, chart->boundaryLength - externalBoundary)) { - // Try to avoid degenerate configurations. - if (chart2->boundaryLength > sharedBoundaryLengths[cc]) { - if (dot(chart2->planeNormal, chart->planeNormal) > -0.25) { - mergeChart(chart2, chart, sharedBoundaryLengths[cc]); - delete chart; - chartArray[c] = NULL; - break; - } - } - } - if (sharedBoundaryLengths[cc] > 0.20 * std::max(0.0f, chart->boundaryLength - externalBoundary)) { - // Compare proxies. - if (dot(chart2->planeNormal, chart->planeNormal) > 0) { - mergeChart(chart2, chart, sharedBoundaryLengths[cc]); - delete chart; - chartArray[c] = NULL; - break; - } - } - } - } - // Remove deleted charts. - for (int c = 0; c < int32_t(chartArray.size()); /*do not increment if removed*/) { - if (chartArray[c] == NULL) { - chartArray.erase(chartArray.begin() + c); - // Update faceChartArray. - const uint32_t faceCount = faceChartArray.size(); - for (uint32_t i = 0; i < faceCount; i++) { - xaDebugAssert (faceChartArray[i] != -1); - xaDebugAssert (faceChartArray[i] != c); - xaDebugAssert (faceChartArray[i] <= int32_t(chartArray.size())); - if (faceChartArray[i] > c) { - faceChartArray[i]--; - } - } - } else { - chartArray[c]->id = c; - c++; - } - } + return chart->normalSum + m_mesh->triangleNormalAreaScaled(f); } // @@ Cleanup. struct Candidate { - uint32_t face; ChartBuildData *chart; + uint32_t face; float metric; }; @@ -5570,48 +5466,48 @@ struct AtlasBuilder { uint32_t best = 0; float bestCandidateMetric = FLT_MAX; - const uint32_t candidateCount = candidateArray.size(); - xaAssert(candidateCount > 0); + const uint32_t candidateCount = m_candidateArray.size(); + XA_ASSERT(candidateCount > 0); for (uint32_t i = 0; i < candidateCount; i++) { - const Candidate &candidate = candidateArray[i]; + const Candidate &candidate = m_candidateArray[i]; if (candidate.metric < bestCandidateMetric) { bestCandidateMetric = candidate.metric; best = i; } } - return candidateArray[best]; + return m_candidateArray[best]; } void removeCandidate(uint32_t f) { - int c = faceCandidateArray[f]; + int c = m_faceCandidateArray[f]; if (c != -1) { - faceCandidateArray[f] = (uint32_t)-1; - if (c == int(candidateArray.size() - 1)) { - candidateArray.pop_back(); + m_faceCandidateArray[f] = (uint32_t)-1; + if (c == int(m_candidateArray.size() - 1)) { + m_candidateArray.pop_back(); } else { // Replace with last. - candidateArray[c] = candidateArray[candidateArray.size() - 1]; - candidateArray.pop_back(); - faceCandidateArray[candidateArray[c].face] = c; + m_candidateArray[c] = m_candidateArray[m_candidateArray.size() - 1]; + m_candidateArray.pop_back(); + m_faceCandidateArray[m_candidateArray[c].face] = c; } } } void updateCandidate(ChartBuildData *chart, uint32_t f, float metric) { - if (faceCandidateArray[f] == -1) { - const uint32_t index = candidateArray.size(); - faceCandidateArray[f] = index; - candidateArray.resize(index + 1); - candidateArray[index].face = f; - candidateArray[index].chart = chart; - candidateArray[index].metric = metric; + if (m_faceCandidateArray[f] == (uint32_t)-1) { + const uint32_t index = m_candidateArray.size(); + m_faceCandidateArray[f] = index; + m_candidateArray.resize(index + 1); + m_candidateArray[index].face = f; + m_candidateArray[index].chart = chart; + m_candidateArray[index].metric = metric; } else { - int c = faceCandidateArray[f]; - xaDebugAssert(c != -1); - Candidate &candidate = candidateArray[c]; - xaDebugAssert(candidate.face == f); + const uint32_t c = m_faceCandidateArray[f]; + XA_DEBUG_ASSERT(c != (uint32_t)-1); + Candidate &candidate = m_candidateArray[c]; + XA_DEBUG_ASSERT(candidate.face == f); if (metric < candidate.metric || chart == candidate.chart) { candidate.metric = metric; candidate.chart = chart; @@ -5624,803 +5520,533 @@ struct AtlasBuilder const uint32_t faceCount = chart->faces.size(); for (uint32_t i = 0; i < faceCount; i++) { uint32_t f = chart->faces[i]; - xaDebugAssert(faceChartArray[f] == chart->id); - faceChartArray[f] = owner->id; + XA_DEBUG_ASSERT(m_faceChartArray[f] == chart->id); + m_faceChartArray[f] = owner->id; owner->faces.push_back(f); } // Update adjacencies? owner->area += chart->area; owner->boundaryLength += chart->boundaryLength - sharedBoundaryLength; owner->normalSum += chart->normalSum; - owner->centroidSum += chart->centroidSum; updateProxy(owner); - } + // Delete chart. + m_chartArray[chart->id] = nullptr; + chart->~ChartBuildData(); + XA_FREE(chart); + } + + const Mesh *m_mesh; + const Array<uint32_t> *m_meshFaces; + Array<bool> m_ignoreFaces; + Array<float> m_edgeLengths; + Array<float> m_faceAreas; + Array<Vector3> m_faceNormals; + Array<Vector2> m_texcoords; + Array<uint32_t> m_growFaces; + uint32_t m_facesLeft; + Array<int> m_faceChartArray; + Array<ChartBuildData *> m_chartArray; + Array<Candidate> m_candidateArray; + Array<uint32_t> m_faceCandidateArray; // Map face index to candidate index. + PriorityQueue m_bestTriangles; + KISSRng m_rand; + ChartOptions m_options; +}; +// Estimate quality of existing parameterization. +struct ParameterizationQuality +{ + uint32_t totalTriangleCount = 0; + uint32_t flippedTriangleCount = 0; + uint32_t zeroAreaTriangleCount = 0; + float parametricArea = 0.0f; + float geometricArea = 0.0f; + float stretchMetric = 0.0f; + float maxStretchMetric = 0.0f; + float conformalMetric = 0.0f; + float authalicMetric = 0.0f; + bool boundaryIntersection = false; +}; - uint32_t chartCount() const { return chartArray.size(); } - const std::vector<uint32_t> &chartFaces(uint32_t i) const { return chartArray[i]->faces; } +static ParameterizationQuality calculateParameterizationQuality(const Mesh *mesh, Array<uint32_t> *flippedFaces) +{ + XA_DEBUG_ASSERT(mesh != nullptr); + ParameterizationQuality quality; + const uint32_t faceCount = mesh->faceCount(); + uint32_t firstBoundaryEdge = UINT32_MAX; + for (uint32_t e = 0; e < mesh->edgeCount(); e++) { + if (mesh->isBoundaryEdge(e)) { + firstBoundaryEdge = e; + } + } + XA_DEBUG_ASSERT(firstBoundaryEdge != UINT32_MAX); + for (Mesh::BoundaryEdgeIterator it1(mesh, firstBoundaryEdge); !it1.isDone(); it1.advance()) { + const uint32_t edge1 = it1.edge(); + for (Mesh::BoundaryEdgeIterator it2(mesh, firstBoundaryEdge); !it2.isDone(); it2.advance()) { + const uint32_t edge2 = it2.edge(); + // Skip self and edges directly connected to edge1. + if (edge1 == edge2 || it1.nextEdge() == edge2 || it2.nextEdge() == edge1) + continue; + const Vector2 &a1 = mesh->texcoord(mesh->vertexAt(meshEdgeIndex0(edge1))); + const Vector2 &a2 = mesh->texcoord(mesh->vertexAt(meshEdgeIndex1(edge1))); + const Vector2 &b1 = mesh->texcoord(mesh->vertexAt(meshEdgeIndex0(edge2))); + const Vector2 &b2 = mesh->texcoord(mesh->vertexAt(meshEdgeIndex1(edge2))); + if (linesIntersect(a1, a2, b1, b2, mesh->epsilon())) { + quality.boundaryIntersection = true; + break; + } + } + if (quality.boundaryIntersection) + break; + } + if (flippedFaces) + flippedFaces->clear(); + for (uint32_t f = 0; f < faceCount; f++) { + Vector3 pos[3]; + Vector2 texcoord[3]; + for (int i = 0; i < 3; i++) { + const uint32_t v = mesh->vertexAt(f * 3 + i); + pos[i] = mesh->position(v); + texcoord[i] = mesh->texcoord(v); + } + quality.totalTriangleCount++; + // Evaluate texture stretch metric. See: + // - "Texture Mapping Progressive Meshes", Sander, Snyder, Gortler & Hoppe + // - "Mesh Parameterization: Theory and Practice", Siggraph'07 Course Notes, Hormann, Levy & Sheffer. + const float t1 = texcoord[0].x; + const float s1 = texcoord[0].y; + const float t2 = texcoord[1].x; + const float s2 = texcoord[1].y; + const float t3 = texcoord[2].x; + const float s3 = texcoord[2].y; + float parametricArea = ((s2 - s1) * (t3 - t1) - (s3 - s1) * (t2 - t1)) / 2; + if (isZero(parametricArea, kAreaEpsilon)) { + quality.zeroAreaTriangleCount++; + continue; + } + if (parametricArea < 0.0f) { + // Count flipped triangles. + quality.flippedTriangleCount++; + if (flippedFaces) + flippedFaces->push_back(f); + parametricArea = fabsf(parametricArea); + } + const float geometricArea = length(cross(pos[1] - pos[0], pos[2] - pos[0])) / 2; + const Vector3 Ss = (pos[0] * (t2 - t3) + pos[1] * (t3 - t1) + pos[2] * (t1 - t2)) / (2 * parametricArea); + const Vector3 St = (pos[0] * (s3 - s2) + pos[1] * (s1 - s3) + pos[2] * (s2 - s1)) / (2 * parametricArea); + 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: + 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)); + // isometric: sigma1 = sigma2 = 1 + // conformal: sigma1 / sigma2 = 1 + // authalic: sigma1 * sigma2 = 1 + const float rmsStretch = sqrtf((a + c) * 0.5f); + const float rmsStretch2 = sqrtf((square(sigma1) + square(sigma2)) * 0.5f); + XA_DEBUG_ASSERT(equal(rmsStretch, rmsStretch2, 0.01f)); + XA_UNUSED(rmsStretch2); + quality.stretchMetric += square(rmsStretch) * geometricArea; + quality.maxStretchMetric = max(quality.maxStretchMetric, sigma2); + if (!isZero(sigma1, 0.000001f)) { + // sigma1 is zero when geometricArea is zero. + quality.conformalMetric += (sigma2 / sigma1) * geometricArea; + } + quality.authalicMetric += (sigma1 * sigma2) * geometricArea; + // Accumulate total areas. + quality.geometricArea += geometricArea; + quality.parametricArea += parametricArea; + //triangleConformalEnergy(q, p); + } + if (quality.flippedTriangleCount + quality.zeroAreaTriangleCount == quality.totalTriangleCount) { + // If all triangles are flipped, then none are. + if (flippedFaces) + flippedFaces->clear(); + quality.flippedTriangleCount = 0; + } + if (quality.flippedTriangleCount > quality.totalTriangleCount / 2) + { + // If more than half the triangles are flipped, reverse the flipped / not flipped classification. + quality.flippedTriangleCount = quality.totalTriangleCount - quality.flippedTriangleCount; + if (flippedFaces) { + Array<uint32_t> temp(*flippedFaces); + flippedFaces->clear(); + for (uint32_t f = 0; f < faceCount; f++) { + bool match = false; + for (uint32_t ff = 0; ff < temp.size(); ff++) { + if (temp[ff] == f) { + match = true; + break; + } + } + if (!match) + flippedFaces->push_back(f); + } + } + } + XA_DEBUG_ASSERT(isFinite(quality.parametricArea) && quality.parametricArea >= 0); + XA_DEBUG_ASSERT(isFinite(quality.geometricArea) && quality.geometricArea >= 0); + XA_DEBUG_ASSERT(isFinite(quality.stretchMetric)); + XA_DEBUG_ASSERT(isFinite(quality.maxStretchMetric)); + XA_DEBUG_ASSERT(isFinite(quality.conformalMetric)); + XA_DEBUG_ASSERT(isFinite(quality.authalicMetric)); + if (quality.geometricArea <= 0.0f) { + quality.stretchMetric = 0.0f; + quality.maxStretchMetric = 0.0f; + quality.conformalMetric = 0.0f; + quality.authalicMetric = 0.0f; + } else { + const float normFactor = sqrtf(quality.parametricArea / quality.geometricArea); + quality.stretchMetric = sqrtf(quality.stretchMetric / quality.geometricArea) * normFactor; + quality.maxStretchMetric *= normFactor; + quality.conformalMetric = sqrtf(quality.conformalMetric / quality.geometricArea); + quality.authalicMetric = sqrtf(quality.authalicMetric / quality.geometricArea); + } + return quality; +} - const halfedge::Mesh *mesh; - uint32_t facesLeft; - std::vector<int> faceChartArray; - std::vector<ChartBuildData *> chartArray; - std::vector<float> shortestPaths; - std::vector<float> edgeLengths; - std::vector<float> faceAreas; - std::vector<Candidate> candidateArray; // - std::vector<uint32_t> faceCandidateArray; // Map face index to candidate index. - MTRand rand; - CharterOptions options; +struct ChartWarningFlags +{ + enum Enum + { + CloseHolesFailed = 1<<1, + FixTJunctionsDuplicatedEdge = 1<<2, + FixTJunctionsFailed = 1<<3, + TriangulateDuplicatedEdge = 1<<4, + }; }; /// A chart is a connected set of faces with a certain topology (usually a disk). class Chart { public: - Chart() : m_isDisk(false), m_isVertexMapped(false) {} - - void build(const halfedge::Mesh *originalMesh, const std::vector<uint32_t> &faceArray) + Chart(const Mesh *originalMesh, const Array<uint32_t> &faceArray, const Basis &basis, uint32_t meshId, uint32_t chartGroupId, uint32_t chartId) : m_basis(basis), m_mesh(nullptr), m_unifiedMesh(nullptr), m_isDisk(false), m_isOrtho(false), m_isPlanar(false), m_warningFlags(0), m_closedHolesCount(0), m_fixedTJunctionsCount(0), m_faceArray(faceArray) { + XA_UNUSED(meshId); + XA_UNUSED(chartGroupId); + XA_UNUSED(chartId); // Copy face indices. - m_faceArray = faceArray; - const uint32_t meshVertexCount = originalMesh->vertexCount(); - m_chartMesh.reset(new halfedge::Mesh()); - m_unifiedMesh.reset(new halfedge::Mesh()); - std::vector<uint32_t> chartMeshIndices(meshVertexCount, (uint32_t)~0); - std::vector<uint32_t> unifiedMeshIndices(meshVertexCount, (uint32_t)~0); + m_mesh = XA_NEW(MemTag::Mesh, Mesh, originalMesh->epsilon(), faceArray.size() * 3, faceArray.size()); + m_unifiedMesh = XA_NEW(MemTag::Mesh, Mesh, originalMesh->epsilon(), faceArray.size() * 3, faceArray.size()); + Array<uint32_t> chartMeshIndices; + chartMeshIndices.resize(originalMesh->vertexCount(), (uint32_t)~0); + Array<uint32_t> unifiedMeshIndices; + unifiedMeshIndices.resize(originalMesh->vertexCount(), (uint32_t)~0); // Add vertices. const uint32_t faceCount = faceArray.size(); for (uint32_t f = 0; f < faceCount; f++) { - const halfedge::Face *face = originalMesh->faceAt(faceArray[f]); - xaDebugAssert(face != NULL); - for (halfedge::Face::ConstEdgeIterator it(face->edges()); !it.isDone(); it.advance()) { - const halfedge::Vertex *vertex = it.current()->vertex; - const halfedge::Vertex *unifiedVertex = vertex->firstColocal(); - if (unifiedMeshIndices[unifiedVertex->id] == ~0) { - unifiedMeshIndices[unifiedVertex->id] = m_unifiedMesh->vertexCount(); - xaDebugAssert(vertex->pos == unifiedVertex->pos); - m_unifiedMesh->addVertex(vertex->pos); + for (uint32_t i = 0; i < 3; i++) { + const uint32_t vertex = originalMesh->vertexAt(faceArray[f] * 3 + i); + const uint32_t unifiedVertex = originalMesh->firstColocal(vertex); + if (unifiedMeshIndices[unifiedVertex] == (uint32_t)~0) { + unifiedMeshIndices[unifiedVertex] = m_unifiedMesh->vertexCount(); + XA_DEBUG_ASSERT(equal(originalMesh->position(vertex), originalMesh->position(unifiedVertex), originalMesh->epsilon())); + m_unifiedMesh->addVertex(originalMesh->position(vertex)); } - if (chartMeshIndices[vertex->id] == ~0) { - chartMeshIndices[vertex->id] = m_chartMesh->vertexCount(); - // -- GODOT start -- - //m_chartToOriginalMap.push_back(vertex->id); - m_chartToOriginalMap.push_back(vertex->original_id); - // -- GODOT end -- - m_chartToUnifiedMap.push_back(unifiedMeshIndices[unifiedVertex->id]); - halfedge::Vertex *v = m_chartMesh->addVertex(vertex->pos); - v->nor = vertex->nor; - v->tex = vertex->tex; + if (chartMeshIndices[vertex] == (uint32_t)~0) { + chartMeshIndices[vertex] = m_mesh->vertexCount(); + m_chartToOriginalMap.push_back(vertex); + m_chartToUnifiedMap.push_back(unifiedMeshIndices[unifiedVertex]); + m_mesh->addVertex(originalMesh->position(vertex), Vector3(0.0f), originalMesh->texcoord(vertex)); } } } - // This is ignoring the canonical map: - // - Is it really necessary to link colocals? - m_chartMesh->linkColocals(); - //m_unifiedMesh->linkColocals(); // Not strictly necessary, no colocals in the unified mesh. # Wrong. - // This check is not valid anymore, if the original mesh vertices were linked with a canonical map, then it might have - // some colocal vertices that were unlinked. So, the unified mesh might have some duplicate vertices, because firstColocal() - // is not guaranteed to return the same vertex for two colocal vertices. - //xaAssert(m_chartMesh->colocalVertexCount() == m_unifiedMesh->vertexCount()); - // Is that OK? What happens in meshes were that happens? Does anything break? Apparently not... - std::vector<uint32_t> faceIndices; - faceIndices.reserve(7); // Add faces. for (uint32_t f = 0; f < faceCount; f++) { - const halfedge::Face *face = originalMesh->faceAt(faceArray[f]); - xaDebugAssert(face != NULL); - faceIndices.clear(); - for (halfedge::Face::ConstEdgeIterator it(face->edges()); !it.isDone(); it.advance()) { - const halfedge::Vertex *vertex = it.current()->vertex; - xaDebugAssert(vertex != NULL); - faceIndices.push_back(chartMeshIndices[vertex->id]); - } - m_chartMesh->addFace(faceIndices); - faceIndices.clear(); - for (halfedge::Face::ConstEdgeIterator it(face->edges()); !it.isDone(); it.advance()) { - const halfedge::Vertex *vertex = it.current()->vertex; - xaDebugAssert(vertex != NULL); - vertex = vertex->firstColocal(); - faceIndices.push_back(unifiedMeshIndices[vertex->id]); - } - m_unifiedMesh->addFace(faceIndices); - } - m_chartMesh->linkBoundary(); - m_unifiedMesh->linkBoundary(); - //exportMesh(m_unifiedMesh.ptr(), "debug_input.obj"); - if (m_unifiedMesh->splitBoundaryEdges()) { - m_unifiedMesh.reset(halfedge::unifyVertices(m_unifiedMesh.get())); - } - //exportMesh(m_unifiedMesh.ptr(), "debug_split.obj"); - // 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. - if (!closeHoles()) { - /*static int pieceCount = 0; - StringBuilder fileName; - fileName.format("debug_hole_%d.obj", pieceCount++); - exportMesh(m_unifiedMesh.ptr(), fileName.str());*/ - } - m_unifiedMesh.reset(halfedge::triangulate(m_unifiedMesh.get())); - //exportMesh(m_unifiedMesh.ptr(), "debug_triangulated.obj"); - // Analyze chart topology. - halfedge::MeshTopology topology(m_unifiedMesh.get()); - m_isDisk = topology.isDisk(); - } - - void buildVertexMap(const halfedge::Mesh *originalMesh, const std::vector<uint32_t> &unchartedMaterialArray) - { - xaAssert(m_chartMesh.get() == NULL && m_unifiedMesh.get() == NULL); - m_isVertexMapped = true; - // Build face indices. - m_faceArray.clear(); - const uint32_t meshFaceCount = originalMesh->faceCount(); - for (uint32_t f = 0; f < meshFaceCount; f++) { - const halfedge::Face *face = originalMesh->faceAt(f); - if (std::find(unchartedMaterialArray.begin(), unchartedMaterialArray.end(), face->material) != unchartedMaterialArray.end()) { - m_faceArray.push_back(f); - } - } - const uint32_t faceCount = m_faceArray.size(); - if (faceCount == 0) { - return; - } - // @@ The chartMesh construction is basically the same as with regular charts, don't duplicate! - const uint32_t meshVertexCount = originalMesh->vertexCount(); - m_chartMesh.reset(new halfedge::Mesh()); - std::vector<uint32_t> chartMeshIndices(meshVertexCount, (uint32_t)~0); - // Vertex map mesh only has disconnected vertices. - for (uint32_t f = 0; f < faceCount; f++) { - const halfedge::Face *face = originalMesh->faceAt(m_faceArray[f]); - xaDebugAssert(face != NULL); - for (halfedge::Face::ConstEdgeIterator it(face->edges()); !it.isDone(); it.advance()) { - const halfedge::Vertex *vertex = it.current()->vertex; - if (chartMeshIndices[vertex->id] == ~0) { - chartMeshIndices[vertex->id] = m_chartMesh->vertexCount(); - // -- GODOT start -- - //m_chartToOriginalMap.push_back(vertex->id); - m_chartToOriginalMap.push_back(vertex->original_id); - // -- GODOT end -- - halfedge::Vertex *v = m_chartMesh->addVertex(vertex->pos); - v->nor = vertex->nor; - v->tex = vertex->tex; // @@ Not necessary. - } + uint32_t indices[3], unifiedIndices[3]; + for (uint32_t i = 0; i < 3; i++) { + const uint32_t vertex = originalMesh->vertexAt(faceArray[f] * 3 + i); + indices[i] = chartMeshIndices[vertex]; + unifiedIndices[i] = unifiedMeshIndices[originalMesh->firstColocal(vertex)]; + } + 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); } - } - // @@ Link colocals using the original mesh canonical map? Build canonical map on the fly? Do we need to link colocals at all for this? - //m_chartMesh->linkColocals(); - std::vector<uint32_t> faceIndices; - faceIndices.reserve(7); - // Add faces. - for (uint32_t f = 0; f < faceCount; f++) { - const halfedge::Face *face = originalMesh->faceAt(m_faceArray[f]); - xaDebugAssert(face != NULL); - faceIndices.clear(); - for (halfedge::Face::ConstEdgeIterator it(face->edges()); !it.isDone(); it.advance()) { - const halfedge::Vertex *vertex = it.current()->vertex; - xaDebugAssert(vertex != NULL); - xaDebugAssert(chartMeshIndices[vertex->id] != ~0); - faceIndices.push_back(chartMeshIndices[vertex->id]); - } - halfedge::Face *new_face = m_chartMesh->addFace(faceIndices); - xaDebugAssert(new_face != NULL); -#ifdef NDEBUG - new_face = NULL; // silence unused parameter warning #endif - } - m_chartMesh->linkBoundary(); - const uint32_t chartVertexCount = m_chartMesh->vertexCount(); - Box bounds; - bounds.clearBounds(); - for (uint32_t i = 0; i < chartVertexCount; i++) { - halfedge::Vertex *vertex = m_chartMesh->vertexAt(i); - bounds.addPointToBounds(vertex->pos); - } - ProximityGrid grid; - grid.init(bounds, chartVertexCount); - for (uint32_t i = 0; i < chartVertexCount; i++) { - halfedge::Vertex *vertex = m_chartMesh->vertexAt(i); - grid.add(vertex->pos, i); - } - uint32_t texelCount = 0; - const float positionThreshold = 0.01f; - const float normalThreshold = 0.01f; - uint32_t verticesVisited = 0; - uint32_t cellsVisited = 0; - std::vector<int> vertexIndexArray(chartVertexCount, -1); // Init all indices to -1. - // Traverse vertices in morton order. @@ It may be more interesting to sort them based on orientation. - const uint32_t cellCodeCount = grid.mortonCount(); - for (uint32_t cellCode = 0; cellCode < cellCodeCount; cellCode++) { - int cell = grid.mortonIndex(cellCode); - if (cell < 0) continue; - cellsVisited++; - const std::vector<uint32_t> &indexArray = grid.cellArray[cell].indexArray; - for (uint32_t i = 0; i < indexArray.size(); i++) { - uint32_t idx = indexArray[i]; - halfedge::Vertex *vertex = m_chartMesh->vertexAt(idx); - xaDebugAssert(vertexIndexArray[idx] == -1); - std::vector<uint32_t> neighbors; - grid.gather(vertex->pos, positionThreshold, /*ref*/neighbors); - // Compare against all nearby vertices, cluster greedily. - for (uint32_t j = 0; j < neighbors.size(); j++) { - uint32_t otherIdx = neighbors[j]; - if (vertexIndexArray[otherIdx] != -1) { - halfedge::Vertex *otherVertex = m_chartMesh->vertexAt(otherIdx); - if (distance(vertex->pos, otherVertex->pos) < positionThreshold && - distance(vertex->nor, otherVertex->nor) < normalThreshold) { - vertexIndexArray[idx] = vertexIndexArray[otherIdx]; - break; + result = m_unifiedMesh->addFace(unifiedIndices); + XA_UNUSED(result); + XA_DEBUG_ASSERT(result == Mesh::AddFaceResult::OK); + } + m_mesh->createBoundaries(); // For AtlasPacker::computeBoundingBox + m_unifiedMesh->createBoundaries(); + m_unifiedMesh->linkBoundaries(); + m_isPlanar = meshIsPlanar(*m_unifiedMesh); + if (m_isPlanar) { + m_isDisk = true; + } else { +#if XA_DEBUG_EXPORT_OBJ_BEFORE_FIX_TJUNCTION + m_unifiedMesh->writeObjFile("debug_before_fix_tjunction.obj"); +#endif + bool duplicatedEdge = false, failed = false; + 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_unifiedMesh->~Mesh(); + XA_FREE(m_unifiedMesh); + m_unifiedMesh = fixedUnifiedMesh; + m_unifiedMesh->createBoundaries(); + m_unifiedMesh->linkBoundaries(); + } + // See if there are any holes that need closing. + Array<uint32_t> boundaryLoops; + meshGetBoundaryLoops(*m_unifiedMesh, boundaryLoops); + if (boundaryLoops.size() > 1) { +#if XA_DEBUG_EXPORT_OBJ_CLOSE_HOLES_ERROR + const uint32_t faceCountBeforeHolesClosed = m_unifiedMesh->faceCount(); +#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. + Array<uint32_t> holeFaceCounts; + XA_PROFILE_START(closeChartMeshHoles) + failed = !meshCloseHoles(m_unifiedMesh, boundaryLoops, basis.normal, holeFaceCounts); + 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 = holeFaceCounts.size(); +#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", meshId, 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); } } - // If index not assigned, assign new one. - if (vertexIndexArray[idx] == -1) { - vertexIndexArray[idx] = texelCount++; - } - verticesVisited++; - } - } - xaDebugAssert(cellsVisited == grid.cellArray.size()); - xaDebugAssert(verticesVisited == chartVertexCount); - vertexMapWidth = ftoi_ceil(sqrtf(float(texelCount))); - vertexMapWidth = (vertexMapWidth + 3) & ~3; // Width aligned to 4. - vertexMapHeight = vertexMapWidth == 0 ? 0 : (texelCount + vertexMapWidth - 1) / vertexMapWidth; - //vertexMapHeight = (vertexMapHeight + 3) & ~3; // Height aligned to 4. - xaDebugAssert(vertexMapWidth >= vertexMapHeight); - xaPrint("Reduced vertex count from %d to %d.\n", chartVertexCount, texelCount); - // Lay down the clustered vertices in morton order. - std::vector<uint32_t> texelCodes(texelCount); - // For each texel, assign one morton code. - uint32_t texelCode = 0; - for (uint32_t i = 0; i < texelCount; i++) { - uint32_t x, y; - do { - x = morton::decodeMorton2X(texelCode); - y = morton::decodeMorton2Y(texelCode); - texelCode++; - } while (x >= uint32_t(vertexMapWidth) || y >= uint32_t(vertexMapHeight)); - texelCodes[i] = texelCode - 1; - } - for (uint32_t i = 0; i < chartVertexCount; i++) { - halfedge::Vertex *vertex = m_chartMesh->vertexAt(i); - int idx = vertexIndexArray[i]; - if (idx != -1) { - uint32_t tc = texelCodes[idx]; - uint32_t x = morton::decodeMorton2X(tc); - uint32_t y = morton::decodeMorton2Y(tc); - vertex->tex.x = float(x); - vertex->tex.y = float(y); - } - } - } - - bool closeHoles() - { - xaDebugAssert(!m_isVertexMapped); - std::vector<halfedge::Edge *> boundaryEdges; - getBoundaryEdges(m_unifiedMesh.get(), boundaryEdges); - uint32_t boundaryCount = boundaryEdges.size(); - if (boundaryCount <= 1) { - // Nothing to close. - return true; - } - // Compute lengths and areas. - std::vector<float> boundaryLengths; - for (uint32_t i = 0; i < boundaryCount; i++) { - const halfedge::Edge *startEdge = boundaryEdges[i]; - xaAssert(startEdge->face == NULL); - //float boundaryEdgeCount = 0; - float boundaryLength = 0.0f; - //Vector3 boundaryCentroid(zero); - const halfedge::Edge *edge = startEdge; - do { - Vector3 t0 = edge->from()->pos; - Vector3 t1 = edge->to()->pos; - //boundaryEdgeCount++; - boundaryLength += length(t1 - t0); - //boundaryCentroid += edge->vertex()->pos; - edge = edge->next; - } while (edge != startEdge); - boundaryLengths.push_back(boundaryLength); - //boundaryCentroids.append(boundaryCentroid / boundaryEdgeCount); - } - // 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. - for (uint32_t i = 0; i < boundaryCount; i++) { - if (diskBoundary == i) { - // Skip disk boundary. - continue; +#endif } - halfedge::Edge *startEdge = boundaryEdges[i]; - xaDebugAssert(startEdge != NULL); - xaDebugAssert(startEdge->face == NULL); - std::vector<halfedge::Vertex *> vertexLoop; - std::vector<halfedge::Edge *> edgeLoop; - halfedge::Edge *edge = startEdge; - do { - halfedge::Vertex *vertex = edge->next->vertex; // edge->to() - uint32_t j; - for (j = 0; j < vertexLoop.size(); j++) { - if (vertex->isColocal(vertexLoop[j])) { - break; - } - } - bool isCrossing = (j != vertexLoop.size()); - if (isCrossing) { - halfedge::Edge *prev = edgeLoop[j]; // Previous edge before the loop. - halfedge::Edge *next = edge->next; // Next edge after the loop. - xaDebugAssert(prev->to()->isColocal(next->from())); - // Close loop. - edgeLoop.push_back(edge); - closeLoop(j + 1, edgeLoop); - // Link boundary loop. - prev->setNext(next); - vertex->setEdge(next); - // Start over again. - vertexLoop.clear(); - edgeLoop.clear(); - edge = startEdge; - vertex = edge->to(); - } - vertexLoop.push_back(vertex); - edgeLoop.push_back(edge); - edge = edge->next; - } while (edge != startEdge); - closeLoop(0, edgeLoop); + // Note: MeshTopology needs linked boundaries. + MeshTopology topology(m_unifiedMesh); + m_isDisk = topology.isDisk(); +#if XA_DEBUG_EXPORT_OBJ_NOT_DISK + if (!m_isDisk) { + char filename[256]; + XA_SPRINTF(filename, sizeof(filename), "debug_mesh_%03u_chartgroup_%03u_chart_%03u_not_disk.obj", meshId, chartGroupId, chartId); + m_unifiedMesh->writeObjFile(filename); + } +#endif } - getBoundaryEdges(m_unifiedMesh.get(), boundaryEdges); - boundaryCount = boundaryEdges.size(); - xaDebugAssert(boundaryCount == 1); - return boundaryCount == 1; - } - - bool isDisk() const - { - return m_isDisk; - } - bool isVertexMapped() const - { - return m_isVertexMapped; } - uint32_t vertexCount() const + ~Chart() { - return m_chartMesh->vertexCount(); - } - uint32_t colocalVertexCount() const - { - return m_unifiedMesh->vertexCount(); - } - - uint32_t faceCount() const - { - return m_faceArray.size(); - } - uint32_t faceAt(uint32_t i) const - { - return m_faceArray[i]; - } - - const halfedge::Mesh *chartMesh() const - { - return m_chartMesh.get(); - } - halfedge::Mesh *chartMesh() - { - return m_chartMesh.get(); - } - const halfedge::Mesh *unifiedMesh() const - { - return m_unifiedMesh.get(); - } - halfedge::Mesh *unifiedMesh() - { - return m_unifiedMesh.get(); + if (m_mesh) { + m_mesh->~Mesh(); + XA_FREE(m_mesh); + } + if (m_unifiedMesh) { + m_unifiedMesh->~Mesh(); + XA_FREE(m_unifiedMesh); + } } - //uint32_t vertexIndex(uint32_t i) const { return m_vertexIndexArray[i]; } + const Basis &basis() const { return m_basis; } + bool isDisk() const { return m_isDisk; } + bool isOrtho() const { return m_isOrtho; } + bool isPlanar() const { return m_isPlanar; } + uint32_t warningFlags() const { return m_warningFlags; } + uint32_t closedHolesCount() const { return m_closedHolesCount; } + uint32_t fixedTJunctionsCount() const { return m_fixedTJunctionsCount; } + const ParameterizationQuality ¶mQuality() const { return m_paramQuality; } +#if XA_DEBUG_EXPORT_OBJ_INVALID_PARAMETERIZATION + const Array<uint32_t> ¶mFlippedFaces() const { return m_paramFlippedFaces; } +#endif + uint32_t mapFaceToSourceFace(uint32_t i) const { return m_faceArray[i]; } + const Mesh *mesh() const { return m_mesh; } + Mesh *mesh() { return m_mesh; } + const Mesh *unifiedMesh() const { return m_unifiedMesh; } + Mesh *unifiedMesh() { return m_unifiedMesh; } + uint32_t mapChartVertexToOriginalVertex(uint32_t i) const { return m_chartToOriginalMap[i]; } - uint32_t mapChartVertexToOriginalVertex(uint32_t i) const - { - return m_chartToOriginalMap[i]; - } - uint32_t mapChartVertexToUnifiedVertex(uint32_t i) const + void evaluateOrthoParameterizationQuality() { - return m_chartToUnifiedMap[i]; + XA_PROFILE_START(parameterizeChartsEvaluateQuality) + m_paramQuality = calculateParameterizationQuality(m_unifiedMesh, nullptr); + XA_PROFILE_END(parameterizeChartsEvaluateQuality) + // Use orthogonal parameterization if quality is acceptable. + if (!m_paramQuality.boundaryIntersection && m_paramQuality.geometricArea > 0.0f && m_paramQuality.stretchMetric <= 1.1f && m_paramQuality.maxStretchMetric <= 1.25f) + m_isOrtho = true; } - const std::vector<uint32_t> &faceArray() const + void evaluateParameterizationQuality() { - return m_faceArray; + XA_PROFILE_START(parameterizeChartsEvaluateQuality) +#if XA_DEBUG_EXPORT_OBJ_INVALID_PARAMETERIZATION + m_paramQuality = calculateParameterizationQuality(m_unifiedMesh, &m_paramFlippedFaces); +#else + m_paramQuality = calculateParameterizationQuality(m_unifiedMesh, nullptr); +#endif + XA_PROFILE_END(parameterizeChartsEvaluateQuality) } // Transfer parameterization from unified mesh to chart mesh. void transferParameterization() { - xaDebugAssert(!m_isVertexMapped); - uint32_t vertexCount = m_chartMesh->vertexCount(); - for (uint32_t v = 0; v < vertexCount; v++) { - halfedge::Vertex *vertex = m_chartMesh->vertexAt(v); - halfedge::Vertex *unifiedVertex = m_unifiedMesh->vertexAt(mapChartVertexToUnifiedVertex(v)); - vertex->tex = unifiedVertex->tex; - } + const uint32_t vertexCount = m_mesh->vertexCount(); + for (uint32_t v = 0; v < vertexCount; v++) + m_mesh->texcoord(v) = m_unifiedMesh->texcoord(m_chartToUnifiedMap[v]); } float computeSurfaceArea() const { - return halfedge::computeSurfaceArea(m_chartMesh.get()) * scale; + return m_mesh->computeSurfaceArea(); } float computeParametricArea() const { - // This only makes sense in parameterized meshes. - xaDebugAssert(m_isDisk); - xaDebugAssert(!m_isVertexMapped); - return halfedge::computeParametricArea(m_chartMesh.get()); + return m_mesh->computeParametricArea(); } Vector2 computeParametricBounds() const { - // This only makes sense in parameterized meshes. - xaDebugAssert(m_isDisk); - xaDebugAssert(!m_isVertexMapped); - Box bounds; - bounds.clearBounds(); - uint32_t vertexCount = m_chartMesh->vertexCount(); + Vector2 minCorner(FLT_MAX, FLT_MAX); + Vector2 maxCorner(-FLT_MAX, -FLT_MAX); + const uint32_t vertexCount = m_mesh->vertexCount(); for (uint32_t v = 0; v < vertexCount; v++) { - halfedge::Vertex *vertex = m_chartMesh->vertexAt(v); - bounds.addPointToBounds(Vector3(vertex->tex, 0)); + minCorner = min(minCorner, m_mesh->texcoord(v)); + maxCorner = max(maxCorner, m_mesh->texcoord(v)); } - return bounds.extents().xy(); + return (maxCorner - minCorner) * 0.5f; } - float scale = 1.0f; - uint32_t vertexMapWidth; - uint32_t vertexMapHeight; - bool blockAligned = true; - private: - bool closeLoop(uint32_t start, const std::vector<halfedge::Edge *> &loop) - { - const uint32_t vertexCount = loop.size() - start; - xaDebugAssert(vertexCount >= 3); - if (vertexCount < 3) return false; - xaDebugAssert(loop[start]->vertex->isColocal(loop[start + vertexCount - 1]->to())); - // If the hole is planar, then we add a single face that will be properly triangulated later. - // If the hole is not planar, we add a triangle fan with a vertex at the hole centroid. - // This is still a bit of a hack. There surely are better hole filling algorithms out there. - std::vector<Vector3> points(vertexCount); - for (uint32_t i = 0; i < vertexCount; i++) { - points[i] = loop[start + i]->vertex->pos; - } - bool isPlanar = Fit::isPlanar(vertexCount, points.data()); - if (isPlanar) { - // Add face and connect edges. - halfedge::Face *face = m_unifiedMesh->addFace(); - for (uint32_t i = 0; i < vertexCount; i++) { - halfedge::Edge *edge = loop[start + i]; - edge->face = face; - edge->setNext(loop[start + (i + 1) % vertexCount]); - } - face->edge = loop[start]; - xaDebugAssert(face->isValid()); - } else { - // If the polygon is not planar, we just cross our fingers, and hope this will work: - // Compute boundary centroid: - Vector3 centroidPos(0); - for (uint32_t i = 0; i < vertexCount; i++) { - centroidPos += points[i]; - } - centroidPos *= (1.0f / vertexCount); - halfedge::Vertex *centroid = m_unifiedMesh->addVertex(centroidPos); - // Add one pair of edges for each boundary vertex. - for (uint32_t j = vertexCount - 1, i = 0; i < vertexCount; j = i++) { - halfedge::Face *face = m_unifiedMesh->addFace(centroid->id, loop[start + j]->vertex->id, loop[start + i]->vertex->id); - xaDebugAssert(face != NULL); -#ifdef NDEBUG - face = NULL; // silence unused parameter warning -#endif - } - } - return true; - } - - static void getBoundaryEdges(halfedge::Mesh *mesh, std::vector<halfedge::Edge *> &boundaryEdges) - { - xaDebugAssert(mesh != NULL); - const uint32_t edgeCount = mesh->edgeCount(); - BitArray bitFlags(edgeCount); - bitFlags.clearAll(); - boundaryEdges.clear(); - // Search for boundary edges. Mark all the edges that belong to the same boundary. - for (uint32_t e = 0; e < edgeCount; e++) { - halfedge::Edge *startEdge = mesh->edgeAt(e); - if (startEdge != NULL && startEdge->isBoundary() && bitFlags.bitAt(e) == false) { - xaDebugAssert(startEdge->face != NULL); - xaDebugAssert(startEdge->pair->face == NULL); - startEdge = startEdge->pair; - const halfedge::Edge *edge = startEdge; - do { - xaDebugAssert(edge->face == NULL); - xaDebugAssert(bitFlags.bitAt(edge->id / 2) == false); - bitFlags.setBitAt(edge->id / 2); - edge = edge->next; - } while (startEdge != edge); - boundaryEdges.push_back(startEdge); - } - } - } - - // Chart mesh. - std::auto_ptr<halfedge::Mesh> m_chartMesh; + Basis m_basis; + Mesh *m_mesh; + Mesh *m_unifiedMesh; + bool m_isDisk, m_isOrtho, m_isPlanar; + uint32_t m_warningFlags; + uint32_t m_closedHolesCount, m_fixedTJunctionsCount; - std::auto_ptr<halfedge::Mesh> m_unifiedMesh; - bool m_isDisk; - bool m_isVertexMapped; - // List of faces of the original mesh that belong to this chart. - std::vector<uint32_t> m_faceArray; + Array<uint32_t> m_faceArray; // Map vertices of the chart mesh to vertices of the original mesh. - std::vector<uint32_t> m_chartToOriginalMap; + Array<uint32_t> m_chartToOriginalMap; - std::vector<uint32_t> m_chartToUnifiedMap; + Array<uint32_t> m_chartToUnifiedMap; + + ParameterizationQuality m_paramQuality; +#if XA_DEBUG_EXPORT_OBJ_INVALID_PARAMETERIZATION + Array<uint32_t> m_paramFlippedFaces; +#endif }; -// Estimate quality of existing parameterization. -class ParameterizationQuality +// Set of charts corresponding to mesh faces in the same face group. +class ChartGroup { public: - ParameterizationQuality() - { - m_totalTriangleCount = 0; - m_flippedTriangleCount = 0; - m_zeroAreaTriangleCount = 0; - m_parametricArea = 0.0f; - m_geometricArea = 0.0f; - m_stretchMetric = 0.0f; - m_maxStretchMetric = 0.0f; - m_conformalMetric = 0.0f; - m_authalicMetric = 0.0f; - } - - ParameterizationQuality(const halfedge::Mesh *mesh) - { - xaDebugAssert(mesh != NULL); - m_totalTriangleCount = 0; - m_flippedTriangleCount = 0; - m_zeroAreaTriangleCount = 0; - m_parametricArea = 0.0f; - m_geometricArea = 0.0f; - m_stretchMetric = 0.0f; - m_maxStretchMetric = 0.0f; - m_conformalMetric = 0.0f; - m_authalicMetric = 0.0f; - const uint32_t faceCount = mesh->faceCount(); + ChartGroup(uint32_t id, const Mesh *sourceMesh, uint32_t faceGroup) : m_sourceId(sourceMesh->id()), m_id(id), m_isVertexMap(faceGroup == UINT32_MAX), m_paramAddedChartsCount(0), m_paramDeletedChartsCount(0) + { + // Create new mesh from the source mesh, using faces that belong to this group. + const uint32_t sourceFaceCount = sourceMesh->faceCount(); + for (uint32_t f = 0; f < sourceFaceCount; f++) { + if (sourceMesh->faceGroupAt(f) == faceGroup) + m_faceToSourceFaceMap.push_back(f); + } + // Only initial meshes have face groups and ignored faces. The only flag we care about is HasNormals. + const uint32_t faceCount = m_faceToSourceFaceMap.size(); + m_mesh = XA_NEW(MemTag::Mesh, Mesh, sourceMesh->epsilon(), faceCount * 3, faceCount, sourceMesh->flags() & MeshFlags::HasNormals); + XA_DEBUG_ASSERT(faceCount > 0); + Array<uint32_t> meshIndices; + meshIndices.resize(sourceMesh->vertexCount(), (uint32_t)~0); for (uint32_t f = 0; f < faceCount; f++) { - const halfedge::Face *face = mesh->faceAt(f); - const halfedge::Vertex *vertex0 = NULL; - Vector3 p[3]; - Vector2 t[3]; - for (halfedge::Face::ConstEdgeIterator it(face->edges()); !it.isDone(); it.advance()) { - const halfedge::Edge *edge = it.current(); - if (vertex0 == NULL) { - vertex0 = edge->vertex; - p[0] = vertex0->pos; - t[0] = vertex0->tex; - } else if (edge->to() != vertex0) { - p[1] = edge->from()->pos; - p[2] = edge->to()->pos; - t[1] = edge->from()->tex; - t[2] = edge->to()->tex; - processTriangle(p, t); + const uint32_t face = m_faceToSourceFaceMap[f]; + for (uint32_t i = 0; i < 3; i++) { + const uint32_t vertex = sourceMesh->vertexAt(face * 3 + i); + if (meshIndices[vertex] == (uint32_t)~0) { + meshIndices[vertex] = m_mesh->vertexCount(); + m_vertexToSourceVertexMap.push_back(vertex); + Vector3 normal(0.0f); + if (sourceMesh->flags() & MeshFlags::HasNormals) + normal = sourceMesh->normal(vertex); + m_mesh->addVertex(sourceMesh->position(vertex), normal, sourceMesh->texcoord(vertex)); } } } - if (m_flippedTriangleCount + m_zeroAreaTriangleCount == faceCount) { - // If all triangles are flipped, then none is. - m_flippedTriangleCount = 0; - } - xaDebugAssert(std::isfinite(m_parametricArea) && m_parametricArea >= 0); - xaDebugAssert(std::isfinite(m_geometricArea) && m_geometricArea >= 0); - xaDebugAssert(std::isfinite(m_stretchMetric)); - xaDebugAssert(std::isfinite(m_maxStretchMetric)); - xaDebugAssert(std::isfinite(m_conformalMetric)); - xaDebugAssert(std::isfinite(m_authalicMetric)); - } - - bool isValid() const - { - return m_flippedTriangleCount == 0; // @@ Does not test for self-overlaps. - } - - float rmsStretchMetric() const - { - if (m_geometricArea == 0) return 0.0f; - float normFactor = sqrtf(m_parametricArea / m_geometricArea); - return sqrtf(m_stretchMetric / m_geometricArea) * normFactor; - } - - float maxStretchMetric() const - { - if (m_geometricArea == 0) return 0.0f; - float normFactor = sqrtf(m_parametricArea / m_geometricArea); - return m_maxStretchMetric * normFactor; - } - - float rmsConformalMetric() const - { - if (m_geometricArea == 0) return 0.0f; - return sqrtf(m_conformalMetric / m_geometricArea); - } - - float maxAuthalicMetric() const - { - if (m_geometricArea == 0) return 0.0f; - return sqrtf(m_authalicMetric / m_geometricArea); - } - - void operator+=(const ParameterizationQuality &pq) - { - m_totalTriangleCount += pq.m_totalTriangleCount; - m_flippedTriangleCount += pq.m_flippedTriangleCount; - m_zeroAreaTriangleCount += pq.m_zeroAreaTriangleCount; - m_parametricArea += pq.m_parametricArea; - m_geometricArea += pq.m_geometricArea; - m_stretchMetric += pq.m_stretchMetric; - m_maxStretchMetric = std::max(m_maxStretchMetric, pq.m_maxStretchMetric); - m_conformalMetric += pq.m_conformalMetric; - m_authalicMetric += pq.m_authalicMetric; - } - -private: - void processTriangle(Vector3 q[3], Vector2 p[3]) - { - m_totalTriangleCount++; - // Evaluate texture stretch metric. See: - // - "Texture Mapping Progressive Meshes", Sander, Snyder, Gortler & Hoppe - // - "Mesh Parameterization: Theory and Practice", Siggraph'07 Course Notes, Hormann, Levy & Sheffer. - float t1 = p[0].x; - float s1 = p[0].y; - float t2 = p[1].x; - float s2 = p[1].y; - float t3 = p[2].x; - float s3 = p[2].y; - float geometricArea = length(cross(q[1] - q[0], q[2] - q[0])) / 2; - float parametricArea = ((s2 - s1) * (t3 - t1) - (s3 - s1) * (t2 - t1)) / 2; - if (isZero(parametricArea)) { - m_zeroAreaTriangleCount++; - return; - } - Vector3 Ss = (q[0] * (t2 - t3) + q[1] * (t3 - t1) + q[2] * (t1 - t2)) / (2 * parametricArea); - Vector3 St = (q[0] * (s3 - s2) + q[1] * (s1 - s3) + q[2] * (s2 - s1)) / (2 * parametricArea); - float a = dot(Ss, Ss); // E - float b = dot(Ss, St); // F - float c = dot(St, St); // G - // Compute eigen-values of the first fundamental form: - float sigma1 = sqrtf(0.5f * std::max(0.0f, a + c - sqrtf(square(a - c) + 4 * square(b)))); // gamma uppercase, min eigenvalue. - float sigma2 = sqrtf(0.5f * std::max(0.0f, a + c + sqrtf(square(a - c) + 4 * square(b)))); // gamma lowercase, max eigenvalue. - xaAssert(sigma2 >= sigma1); - // isometric: sigma1 = sigma2 = 1 - // conformal: sigma1 / sigma2 = 1 - // authalic: sigma1 * sigma2 = 1 - float rmsStretch = sqrtf((a + c) * 0.5f); - float rmsStretch2 = sqrtf((square(sigma1) + square(sigma2)) * 0.5f); - xaDebugAssert(equal(rmsStretch, rmsStretch2, 0.01f)); -#ifdef NDEBUG - rmsStretch2 = 0; // silence unused parameter warning + // Add faces. + for (uint32_t f = 0; f < faceCount; f++) { + const uint32_t face = m_faceToSourceFaceMap[f]; + uint32_t indices[3]; + for (uint32_t i = 0; i < 3; i++) { + const uint32_t vertex = sourceMesh->vertexAt(face * 3 + i); + XA_DEBUG_ASSERT(meshIndices[vertex] != (uint32_t)~0); + indices[i] = meshIndices[vertex]; + } + // Don't copy flags, it doesn't matter if a face is ignored after this point. All ignored faces get their own vertex map (m_isVertexMap) ChartGroup. + // Don't hash edges if m_isVertexMap, they may be degenerate. + Mesh::AddFaceResult::Enum result = m_mesh->addFace(indices, false, !m_isVertexMap); + XA_UNUSED(result); + XA_DEBUG_ASSERT(result == Mesh::AddFaceResult::OK); + } + if (!m_isVertexMap) { + m_mesh->createColocals(); + m_mesh->createBoundaries(); + m_mesh->linkBoundaries(); + } +#if XA_DEBUG_EXPORT_OBJ_CHART_GROUPS + char filename[256]; + XA_SPRINTF(filename, sizeof(filename), "debug_mesh_%03u_chartgroup_%03u.obj", m_sourceId, m_id); + m_mesh->writeObjFile(filename); +#else + XA_UNUSED(m_id); #endif - if (parametricArea < 0.0f) { - // Count flipped triangles. - m_flippedTriangleCount++; - parametricArea = fabsf(parametricArea); - } - m_stretchMetric += square(rmsStretch) * geometricArea; - m_maxStretchMetric = std::max(m_maxStretchMetric, sigma2); - if (!isZero(sigma1, 0.000001f)) { - // sigma1 is zero when geometricArea is zero. - m_conformalMetric += (sigma2 / sigma1) * geometricArea; - } - m_authalicMetric += (sigma1 * sigma2) * geometricArea; - // Accumulate total areas. - m_geometricArea += geometricArea; - m_parametricArea += parametricArea; - //triangleConformalEnergy(q, p); } - uint32_t m_totalTriangleCount; - uint32_t m_flippedTriangleCount; - uint32_t m_zeroAreaTriangleCount; - float m_parametricArea; - float m_geometricArea; - float m_stretchMetric; - float m_maxStretchMetric; - float m_conformalMetric; - float m_authalicMetric; -}; - -// Set of charts corresponding to a single mesh. -class MeshCharts -{ -public: - MeshCharts(const halfedge::Mesh *mesh) : m_mesh(mesh) {} - - ~MeshCharts() + ~ChartGroup() { - for (size_t i = 0; i < m_chartArray.size(); i++) - delete m_chartArray[i]; - } - - uint32_t chartCount() const - { - return m_chartArray.size(); - } - uint32_t vertexCount () const - { - return m_totalVertexCount; - } - - const Chart *chartAt(uint32_t i) const - { - return m_chartArray[i]; - } - Chart *chartAt(uint32_t i) - { - return m_chartArray[i]; - } - - // Extract the charts of the input mesh. - void extractCharts() - { - const uint32_t faceCount = m_mesh->faceCount(); - int first = 0; - std::vector<uint32_t> queue; - queue.reserve(faceCount); - BitArray bitFlags(faceCount); - bitFlags.clearAll(); - for (uint32_t f = 0; f < faceCount; f++) { - if (bitFlags.bitAt(f) == false) { - // Start new patch. Reset queue. - first = 0; - queue.clear(); - queue.push_back(f); - bitFlags.setBitAt(f); - while (first != (int)queue.size()) { - const halfedge::Face *face = m_mesh->faceAt(queue[first]); - // Visit face neighbors of queue[first] - for (halfedge::Face::ConstEdgeIterator it(face->edges()); !it.isDone(); it.advance()) { - const halfedge::Edge *edge = it.current(); - xaDebugAssert(edge->pair != NULL); - if (!edge->isBoundary() && /*!edge->isSeam()*/ - //!(edge->from()->tex() != edge->pair()->to()->tex() || edge->to()->tex() != edge->pair()->from()->tex())) - !(edge->from() != edge->pair->to() || edge->to() != edge->pair->from())) { // Preserve existing seams (not just texture seams). - const halfedge::Face *neighborFace = edge->pair->face; - xaDebugAssert(neighborFace != NULL); - if (bitFlags.bitAt(neighborFace->id) == false) { - queue.push_back(neighborFace->id); - bitFlags.setBitAt(neighborFace->id); - } - } - } - first++; - } - Chart *chart = new Chart(); - chart->build(m_mesh, queue); - m_chartArray.push_back(chart); - } + m_mesh->~Mesh(); + XA_FREE(m_mesh); + for (uint32_t i = 0; i < m_chartArray.size(); i++) { + m_chartArray[i]->~Chart(); + XA_FREE(m_chartArray[i]); } } + uint32_t chartCount() const { return m_chartArray.size(); } + Chart *chartAt(uint32_t i) const { return m_chartArray[i]; } + uint32_t paramAddedChartsCount() const { return m_paramAddedChartsCount; } + uint32_t paramDeletedChartsCount() const { return m_paramDeletedChartsCount; } + bool isVertexMap() const { return m_isVertexMap; } + uint32_t mapFaceToSourceFace(uint32_t face) const { return m_faceToSourceFaceMap[face]; } + uint32_t mapVertexToSourceVertex(uint32_t i) const { return m_vertexToSourceVertexMap[i]; } + const Mesh *mesh() const { return m_mesh; } + /* Compute charts using a simple segmentation algorithm. @@ -6481,617 +6107,1122 @@ public: - emphasize roundness metrics to prevent those cases. - If interior self-overlaps: preserve boundary parameterization and use mean-value map. */ - void computeCharts(const CharterOptions &options, const std::vector<uint32_t> &unchartedMaterialArray) - { - Chart *vertexMap = NULL; - if (unchartedMaterialArray.size() != 0) { - vertexMap = new Chart(); - vertexMap->buildVertexMap(m_mesh, unchartedMaterialArray); - if (vertexMap->faceCount() == 0) { - delete vertexMap; - vertexMap = NULL; - } - } - AtlasBuilder builder(m_mesh); - if (vertexMap != NULL) { - // Mark faces that do not need to be charted. - builder.markUnchartedFaces(vertexMap->faceArray()); - m_chartArray.push_back(vertexMap); - } - if (builder.facesLeft != 0) { - // Tweak these values: - const float maxThreshold = 2; - const uint32_t growFaceCount = 32; - const uint32_t maxIterations = 4; - builder.options = options; - //builder.options.proxyFitMetricWeight *= 0.75; // relax proxy fit weight during initial seed placement. - //builder.options.roundnessMetricWeight = 0; - //builder.options.straightnessMetricWeight = 0; - // This seems a reasonable estimate. - uint32_t maxSeedCount = std::max(6U, builder.facesLeft); - // Create initial charts greedely. - xaPrint("### Placing seeds\n"); - builder.placeSeeds(maxThreshold, maxSeedCount); - xaPrint("### Placed %d seeds (max = %d)\n", builder.chartCount(), maxSeedCount); - builder.updateProxies(); - builder.mergeCharts(); - #if 1 - xaPrint("### Relocating seeds\n"); - builder.relocateSeeds(); - xaPrint("### Reset charts\n"); - builder.resetCharts(); - if (vertexMap != NULL) { - builder.markUnchartedFaces(vertexMap->faceArray()); - } - builder.options = options; - xaPrint("### Growing charts\n"); - // Restart process growing charts in parallel. - uint32_t iteration = 0; - while (true) { - if (!builder.growCharts(maxThreshold, growFaceCount)) { - xaPrint("### Can't grow anymore\n"); - // If charts cannot grow more: fill holes, merge charts, relocate seeds and start new iteration. - xaPrint("### Filling holes\n"); - builder.fillHoles(maxThreshold); - xaPrint("### Using %d charts now\n", builder.chartCount()); - builder.updateProxies(); - xaPrint("### Merging charts\n"); - builder.mergeCharts(); - xaPrint("### Using %d charts now\n", builder.chartCount()); - xaPrint("### Reseeding\n"); - if (!builder.relocateSeeds()) { - xaPrint("### Cannot relocate seeds anymore\n"); - // Done! - break; - } - if (iteration == maxIterations) { - xaPrint("### Reached iteration limit\n"); - break; - } - iteration++; - xaPrint("### Reset charts\n"); - builder.resetCharts(); - if (vertexMap != NULL) { - builder.markUnchartedFaces(vertexMap->faceArray()); - } - xaPrint("### Growing charts\n"); - } - }; - #endif - // Make sure no holes are left! - xaDebugAssert(builder.facesLeft == 0); - const uint32_t chartCount = builder.chartArray.size(); - for (uint32_t i = 0; i < chartCount; i++) { - Chart *chart = new Chart(); - m_chartArray.push_back(chart); - chart->build(m_mesh, builder.chartFaces(i)); - } - } - const uint32_t chartCount = m_chartArray.size(); - // Build face indices. - m_faceChart.resize(m_mesh->faceCount()); - m_faceIndex.resize(m_mesh->faceCount()); + void computeCharts(const ChartOptions &options) + { + m_chartOptions = options; + // This function may be called multiple times, so destroy existing charts. + for (uint32_t i = 0; i < m_chartArray.size(); i++) { + m_chartArray[i]->~Chart(); + XA_FREE(m_chartArray[i]); + } + m_chartArray.clear(); +#if XA_DEBUG_SINGLE_CHART + Array<uint32_t> chartFaces; + chartFaces.resize(m_mesh->faceCount()); + for (uint32_t i = 0; i < chartFaces.size(); i++) + chartFaces[i] = i; + Chart *chart = XA_NEW(MemTag::Default, Chart, m_mesh, chartFaces, m_sourceId, m_id, 0); + m_chartArray.push_back(chart); +#else + XA_PROFILE_START(atlasBuilder) + AtlasBuilder builder(m_mesh, nullptr, options); + runAtlasBuilder(builder, options); + XA_PROFILE_END(atlasBuilder) + XA_PROFILE_START(createChartMeshes) + const uint32_t chartCount = builder.chartCount(); for (uint32_t i = 0; i < chartCount; i++) { - const Chart *chart = m_chartArray[i]; - const uint32_t faceCount = chart->faceCount(); - for (uint32_t f = 0; f < faceCount; f++) { - uint32_t idx = chart->faceAt(f); - m_faceChart[idx] = i; - m_faceIndex[idx] = f; - } + Chart *chart = XA_NEW(MemTag::Default, Chart, m_mesh, builder.chartFaces(i), builder.chartBasis(i), m_sourceId, m_id, i); + m_chartArray.push_back(chart); } - // Build an exclusive prefix sum of the chart vertex counts. - m_chartVertexCountPrefixSum.resize(chartCount); - if (chartCount > 0) { - m_chartVertexCountPrefixSum[0] = 0; - for (uint32_t i = 1; i < chartCount; i++) { - const Chart *chart = m_chartArray[i - 1]; - m_chartVertexCountPrefixSum[i] = m_chartVertexCountPrefixSum[i - 1] + chart->vertexCount(); + XA_PROFILE_END(createChartMeshes) +#endif +#if XA_DEBUG_EXPORT_OBJ_CHARTS + char filename[256]; + XA_SPRINTF(filename, sizeof(filename), "debug_mesh_%03u_chartgroup_%03u_charts.obj", m_sourceId, m_id); + FILE *file; + XA_FOPEN(file, filename, "w"); + if (file) { + m_mesh->writeObjVertices(file); + for (uint32_t i = 0; i < chartCount; i++) { + fprintf(file, "o chart_%04d\n", i); + fprintf(file, "s off\n"); + const Array<uint32_t> &faces = builder.chartFaces(i); + for (uint32_t f = 0; f < faces.size(); f++) + m_mesh->writeObjFace(file, faces[f]); } - m_totalVertexCount = m_chartVertexCountPrefixSum[chartCount - 1] + m_chartArray[chartCount - 1]->vertexCount(); - } else { - m_totalVertexCount = 0; + m_mesh->writeObjBoundaryEges(file); + m_mesh->writeObjLinkedBoundaries(file); + fclose(file); } +#endif } - void parameterizeCharts() + void parameterizeCharts(ParameterizeFunc func) { - ParameterizationQuality globalParameterizationQuality; - // Parameterize the charts. - uint32_t diskCount = 0; +#if XA_RECOMPUTE_CHARTS + Array<Chart *> invalidCharts; const uint32_t chartCount = m_chartArray.size(); - for (uint32_t i = 0; i < chartCount; i++) - { + for (uint32_t i = 0; i < chartCount; i++) { Chart *chart = m_chartArray[i]; - - bool isValid = false; - - if (chart->isVertexMapped()) - { - continue; - } - - if (chart->isDisk()) - { - diskCount++; - ParameterizationQuality chartParameterizationQuality; - if (chart->faceCount() == 1) { - computeSingleFaceMap(chart->unifiedMesh()); - chartParameterizationQuality = ParameterizationQuality(chart->unifiedMesh()); - } else { - computeOrthogonalProjectionMap(chart->unifiedMesh()); - ParameterizationQuality orthogonalQuality(chart->unifiedMesh()); - computeLeastSquaresConformalMap(chart->unifiedMesh()); - ParameterizationQuality lscmQuality(chart->unifiedMesh()); - chartParameterizationQuality = lscmQuality; - } - isValid = chartParameterizationQuality.isValid(); - if (!isValid) { - xaPrint("*** Invalid parameterization.\n"); + parameterizeChart(chart, func); + const ParameterizationQuality &quality = chart->paramQuality(); + if (quality.boundaryIntersection || quality.flippedTriangleCount > 0) + invalidCharts.push_back(chart); + } + // Recompute charts with invalid parameterizations. + Array<uint32_t> meshFaces; + for (uint32_t i = 0; i < invalidCharts.size(); i++) { + Chart *invalidChart = invalidCharts[i]; + const Mesh *invalidMesh = invalidChart->mesh(); + const uint32_t faceCount = invalidMesh->faceCount(); + meshFaces.resize(faceCount); + float invalidChartArea = 0.0f; + for (uint32_t j = 0; j < faceCount; j++) { + meshFaces[j] = invalidChart->mapFaceToSourceFace(j); + invalidChartArea += invalidMesh->faceArea(j); + } + ChartOptions options = m_chartOptions; + options.maxChartArea = invalidChartArea * 0.2f; + options.maxThreshold = 0.25f; + options.maxIterations = 3; + AtlasBuilder builder(m_mesh, &meshFaces, options); + runAtlasBuilder(builder, options); + for (uint32_t j = 0; j < builder.chartCount(); j++) { + Chart *chart = XA_NEW(MemTag::Default, Chart, m_mesh, builder.chartFaces(j), builder.chartBasis(j), m_sourceId, m_id, m_chartArray.size()); + m_chartArray.push_back(chart); + m_paramAddedChartsCount++; + } +#if XA_DEBUG_EXPORT_OBJ_RECOMPUTED_CHARTS + char filename[256]; + XA_SPRINTF(filename, sizeof(filename), "debug_mesh_%03u_chartgroup_%03u_recomputed_chart_%u.obj", m_sourceId, m_id, i); + FILE *file; + XA_FOPEN(file, filename, "w"); + if (file) { + m_mesh->writeObjVertices(file); + for (uint32_t j = 0; j < builder.chartCount(); j++) { + fprintf(file, "o chart_%04d\n", j); + fprintf(file, "s off\n"); + const Array<uint32_t> &faces = builder.chartFaces(j); + for (uint32_t f = 0; f < faces.size(); f++) + m_mesh->writeObjFace(file, faces[f]); } - // @@ Check that parameterization quality is above a certain threshold. - // @@ Detect boundary self-intersections. - globalParameterizationQuality += chartParameterizationQuality; + fclose(file); } - - // Transfer parameterization from unified mesh to chart mesh. - chart->transferParameterization(); - +#endif + } + // Parameterize the new charts. + for (uint32_t i = chartCount; i < m_chartArray.size(); i++) + parameterizeChart(m_chartArray[i], func); + // Remove and delete the invalid charts. + for (uint32_t i = 0; i < invalidCharts.size(); i++) { + Chart *chart = invalidCharts[i]; + removeChart(chart); + chart->~Chart(); + XA_FREE(chart); + m_paramDeletedChartsCount++; } - xaPrint(" Parameterized %d/%d charts.\n", diskCount, chartCount); - xaPrint(" RMS stretch metric: %f\n", globalParameterizationQuality.rmsStretchMetric()); - xaPrint(" MAX stretch metric: %f\n", globalParameterizationQuality.maxStretchMetric()); - xaPrint(" RMS conformal metric: %f\n", globalParameterizationQuality.rmsConformalMetric()); - xaPrint(" RMS authalic metric: %f\n", globalParameterizationQuality.maxAuthalicMetric()); +#else + const uint32_t chartCount = m_chartArray.size(); + for (uint32_t i = 0; i < chartCount; i++) { + Chart *chart = m_chartArray[i]; + parameterizeChart(chart, func); + } +#endif } - uint32_t faceChartAt(uint32_t i) const - { - return m_faceChart[i]; - } - uint32_t faceIndexWithinChartAt(uint32_t i) const +private: + void runAtlasBuilder(AtlasBuilder &builder, const ChartOptions &options) { - return m_faceIndex[i]; + if (builder.facesLeft() == 0) + return; + // This seems a reasonable estimate. + XA_PROFILE_START(atlasBuilderCreateInitialCharts) + // Create initial charts greedely. + builder.placeSeeds(options.maxThreshold * 0.5f); + if (options.maxIterations == 0) { + XA_DEBUG_ASSERT(builder.facesLeft() == 0); + XA_PROFILE_END(atlasBuilderCreateInitialCharts) + return; + } + builder.updateProxies(); + builder.relocateSeeds(); + builder.resetCharts(); + XA_PROFILE_END(atlasBuilderCreateInitialCharts) + // Restart process growing charts in parallel. + uint32_t iteration = 0; + while (true) { + if (!builder.growCharts(options.maxThreshold, options.growFaceCount)) { + // If charts cannot grow more: fill holes, merge charts, relocate seeds and start new iteration. + builder.fillHoles(options.maxThreshold * 0.5f); + builder.updateProxies(); +#if XA_MERGE_CHARTS + builder.mergeCharts(); +#endif + if (++iteration == options.maxIterations) + break; + if (!builder.relocateSeeds()) + break; + builder.resetCharts(); + } + } + // Make sure no holes are left! + XA_DEBUG_ASSERT(builder.facesLeft() == 0); } - uint32_t vertexCountBeforeChartAt(uint32_t i) const + void parameterizeChart(Chart *chart, ParameterizeFunc func) { - return m_chartVertexCountPrefixSum[i]; + Mesh *mesh = chart->unifiedMesh(); + XA_PROFILE_START(parameterizeChartsOrthogonal) +#if 1 + computeOrthogonalProjectionMap(mesh); +#else + for (uint32_t i = 0; i < vertexCount; i++) + mesh->texcoord(i) = Vector2(dot(chart->basis().tangent, mesh->position(i)), dot(chart->basis().bitangent, mesh->position(i))); +#endif + XA_PROFILE_END(parameterizeChartsOrthogonal) + chart->evaluateOrthoParameterizationQuality(); + if (!chart->isOrtho() && !chart->isPlanar()) { + XA_PROFILE_START(parameterizeChartsLSCM) + if (func) + func(&mesh->position(0).x, &mesh->texcoord(0).x, mesh->vertexCount(), mesh->indices(), mesh->indexCount()); + else if (chart->isDisk()) + computeLeastSquaresConformalMap(mesh); + XA_PROFILE_END(parameterizeChartsLSCM) + chart->evaluateParameterizationQuality(); + } + // @@ Check that parameterization quality is above a certain threshold. + // Transfer parameterization from unified mesh to chart mesh. + chart->transferParameterization(); + } + + void removeChart(const Chart *chart) + { + for (uint32_t i = 0; i < m_chartArray.size(); i++) { + if (m_chartArray[i] == chart) { + m_chartArray.removeAt(i); + return; + } + } } -private: + uint32_t m_sourceId, m_id; + bool m_isVertexMap; + Mesh *m_mesh; + Array<uint32_t> m_faceToSourceFaceMap; // List of faces of the source mesh that belong to this chart group. + Array<uint32_t> m_vertexToSourceVertexMap; // Map vertices of the mesh to vertices of the source mesh. + Array<Chart *> m_chartArray; + ChartOptions m_chartOptions; + 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. +}; - const halfedge::Mesh *m_mesh; +struct CreateChartGroupTaskArgs +{ + uint32_t faceGroup; + uint32_t groupId; + const Mesh *mesh; + ChartGroup **chartGroup; +}; - std::vector<Chart *> m_chartArray; +static void runCreateChartGroupTask(void *userData) +{ + XA_PROFILE_START(addMeshCreateChartGroups) + auto args = (CreateChartGroupTaskArgs *)userData; + *(args->chartGroup) = XA_NEW(MemTag::Default, ChartGroup, args->groupId, args->mesh, args->faceGroup); + XA_PROFILE_END(addMeshCreateChartGroups) +} - std::vector<uint32_t> m_chartVertexCountPrefixSum; - uint32_t m_totalVertexCount; +struct ComputeChartsTaskArgs +{ + ChartGroup *chartGroup; + const ChartOptions *options; + Progress *progress; +}; + +static void runComputeChartsJob(void *userData) +{ + ComputeChartsTaskArgs *args = (ComputeChartsTaskArgs *)userData; + if (args->progress->cancel) + return; + XA_PROFILE_START(computeCharts) + args->chartGroup->computeCharts(*args->options); + XA_PROFILE_END(computeCharts) + args->progress->value++; + args->progress->update(); +} - std::vector<uint32_t> m_faceChart; // the chart of every face of the input mesh. - std::vector<uint32_t> m_faceIndex; // the index within the chart for every face of the input mesh. +struct ParameterizeChartsTaskArgs +{ + ChartGroup *chartGroup; + ParameterizeFunc func; + Progress *progress; }; -/// An atlas is a set of charts. +static void runParameterizeChartsJob(void *userData) +{ + ParameterizeChartsTaskArgs *args = (ParameterizeChartsTaskArgs *)userData; + if (args->progress->cancel) + return; + XA_PROFILE_START(parameterizeCharts) + args->chartGroup->parameterizeCharts(args->func); + XA_PROFILE_END(parameterizeCharts) + args->progress->value++; + args->progress->update(); +} + +/// An atlas is a set of chart groups. class Atlas { public: + Atlas() : m_chartsComputed(false), m_chartsParameterized(false) {} + ~Atlas() { - for (size_t i = 0; i < m_meshChartsArray.size(); i++) - delete m_meshChartsArray[i]; + for (uint32_t i = 0; i < m_chartGroups.size(); i++) { + m_chartGroups[i]->~ChartGroup(); + XA_FREE(m_chartGroups[i]); + } } - uint32_t meshCount() const - { - return m_meshChartsArray.size(); - } + bool chartsComputed() const { return m_chartsComputed; } + bool chartsParameterized() const { return m_chartsParameterized; } - const MeshCharts *meshAt(uint32_t i) const + uint32_t chartGroupCount(uint32_t mesh) const { - return m_meshChartsArray[i]; + uint32_t count = 0; + for (uint32_t i = 0; i < m_chartGroups.size(); i++) { + if (m_chartGroupSourceMeshes[i] == mesh) + count++; + } + return count; } - MeshCharts *meshAt(uint32_t i) + const ChartGroup *chartGroupAt(uint32_t mesh, uint32_t group) const { - return m_meshChartsArray[i]; + for (uint32_t c = 0; c < m_chartGroups.size(); c++) { + if (m_chartGroupSourceMeshes[c] != mesh) + continue; + if (group == 0) + return m_chartGroups[c]; + group--; + } + return nullptr; } uint32_t chartCount() const { uint32_t count = 0; - for (uint32_t c = 0; c < m_meshChartsArray.size(); c++) { - count += m_meshChartsArray[c]->chartCount(); - } + for (uint32_t i = 0; i < m_chartGroups.size(); i++) + count += m_chartGroups[i]->chartCount(); return count; } - const Chart *chartAt(uint32_t i) const + Chart *chartAt(uint32_t i) { - for (uint32_t c = 0; c < m_meshChartsArray.size(); c++) { - uint32_t count = m_meshChartsArray[c]->chartCount(); + for (uint32_t c = 0; c < m_chartGroups.size(); c++) { + uint32_t count = m_chartGroups[c]->chartCount(); if (i < count) { - return m_meshChartsArray[c]->chartAt(i); + return m_chartGroups[c]->chartAt(i); } i -= count; } - return NULL; + return nullptr; } - Chart *chartAt(uint32_t i) + // This function is thread safe. + void addMesh(TaskScheduler *taskScheduler, const Mesh *mesh) { - for (uint32_t c = 0; c < m_meshChartsArray.size(); c++) { - uint32_t count = m_meshChartsArray[c]->chartCount(); - if (i < count) { - return m_meshChartsArray[c]->chartAt(i); + // Get list of face groups. + const uint32_t faceCount = mesh->faceCount(); + Array<uint32_t> faceGroups; + for (uint32_t f = 0; f < faceCount; f++) { + const uint32_t group = mesh->faceGroupAt(f); + bool exists = false; + for (uint32_t g = 0; g < faceGroups.size(); g++) { + if (faceGroups[g] == group) { + exists = true; + break; + } + } + if (!exists) + faceGroups.push_back(group); + } + // Create one chart group per face group. + // Chart group creation is slow since it copies a chunk of the source mesh, so use tasks. + Array<ChartGroup *> chartGroups; + chartGroups.resize(faceGroups.size()); + Array<CreateChartGroupTaskArgs> taskArgs; + taskArgs.resize(chartGroups.size()); + for (uint32_t g = 0; g < chartGroups.size(); g++) { + CreateChartGroupTaskArgs &args = taskArgs[g]; + args.chartGroup = &chartGroups[g]; + args.faceGroup = faceGroups[g]; + args.groupId = g; + args.mesh = mesh; + } + TaskGroupHandle taskGroup; + for (uint32_t g = 0; g < chartGroups.size(); g++) { + Task task; + task.userData = &taskArgs[g]; + task.func = runCreateChartGroupTask; + taskScheduler->run(&taskGroup, task); + } + taskScheduler->wait(&taskGroup); + // Thread-safe append. + m_addMeshMutex.lock(); + for (uint32_t g = 0; g < chartGroups.size(); g++) { + m_chartGroups.push_back(chartGroups[g]); + m_chartGroupSourceMeshes.push_back(mesh->id()); + } + m_addMeshMutex.unlock(); + } + + bool computeCharts(TaskScheduler *taskScheduler, const ChartOptions &options, ProgressFunc progressFunc, void *progressUserData) + { + m_chartsComputed = false; + m_chartsParameterized = false; + uint32_t taskCount = 0; + for (uint32_t i = 0; i < m_chartGroups.size(); i++) { + if (!m_chartGroups[i]->isVertexMap()) + taskCount++; + } + Progress progress(ProgressCategory::ComputeCharts, progressFunc, progressUserData, taskCount); + Array<ComputeChartsTaskArgs> taskArgs; + taskArgs.reserve(taskCount); + for (uint32_t i = 0; i < m_chartGroups.size(); i++) { + if (!m_chartGroups[i]->isVertexMap()) { + ComputeChartsTaskArgs args; + args.chartGroup = m_chartGroups[i]; + args.options = &options; + args.progress = &progress; + taskArgs.push_back(args); + } + } + TaskGroupHandle taskGroup; + for (uint32_t i = 0; i < taskCount; i++) { + Task task; + task.userData = &taskArgs[i]; + task.func = runComputeChartsJob; + taskScheduler->run(&taskGroup, task); + } + taskScheduler->wait(&taskGroup); + if (progress.cancel) + return false; + m_chartsComputed = true; + return true; + } + + bool parameterizeCharts(TaskScheduler *taskScheduler, ParameterizeFunc func, ProgressFunc progressFunc, void *progressUserData) + { + m_chartsParameterized = false; + uint32_t taskCount = 0; + for (uint32_t i = 0; i < m_chartGroups.size(); i++) { + if (!m_chartGroups[i]->isVertexMap()) + taskCount++; + } + Progress progress(ProgressCategory::ParameterizeCharts, progressFunc, progressUserData, taskCount); + Array<ParameterizeChartsTaskArgs> taskArgs; + taskArgs.reserve(taskCount); + for (uint32_t i = 0; i < m_chartGroups.size(); i++) { + if (!m_chartGroups[i]->isVertexMap()) { + ParameterizeChartsTaskArgs args; + args.chartGroup = m_chartGroups[i]; + args.func = func; + args.progress = &progress; + taskArgs.push_back(args); } - i -= count; } - return NULL; + TaskGroupHandle taskGroup; + for (uint32_t i = 0; i < taskCount; i++) { + Task task; + task.userData = &taskArgs[i]; + task.func = runParameterizeChartsJob; + taskScheduler->run(&taskGroup, task); + } + taskScheduler->wait(&taskGroup); + if (progress.cancel) + return false; + // Save original texcoords so PackCharts can be called multiple times (packing overwrites the texcoords). + const uint32_t nCharts = chartCount(); + m_originalChartTexcoords.resize(nCharts); + for (uint32_t i = 0; i < nCharts; i++) { + const Mesh *mesh = chartAt(i)->mesh(); + m_originalChartTexcoords[i].resize(mesh->vertexCount()); + for (uint32_t j = 0; j < mesh->vertexCount(); j++) + m_originalChartTexcoords[i][j] = mesh->texcoord(j); + } + m_chartsParameterized = true; + return true; } - // Add mesh charts and takes ownership. - // Extract the charts and add to this atlas. - void addMeshCharts(MeshCharts *meshCharts) + void restoreOriginalChartTexcoords() { - m_meshChartsArray.push_back(meshCharts); + const uint32_t nCharts = chartCount(); + for (uint32_t i = 0; i < nCharts; i++) { + Mesh *mesh = chartAt(i)->mesh(); + for (uint32_t j = 0; j < mesh->vertexCount(); j++) + mesh->texcoord(j) = m_originalChartTexcoords[i][j]; + } } - void extractCharts(const halfedge::Mesh *mesh) +private: + std::mutex m_addMeshMutex; + bool m_chartsComputed; + bool m_chartsParameterized; + Array<ChartGroup *> m_chartGroups; + Array<uint32_t> m_chartGroupSourceMeshes; + Array<Array<Vector2> > m_originalChartTexcoords; +}; + +} // namespace param + +namespace pack { + +#if XA_DEBUG_EXPORT_ATLAS_IMAGES +const uint8_t TGA_TYPE_RGB = 2; +const uint8_t TGA_ORIGIN_UPPER = 0x20; + +#pragma pack(push, 1) +struct TgaHeader +{ + uint8_t id_length; + uint8_t colormap_type; + uint8_t image_type; + uint16_t colormap_index; + uint16_t colormap_length; + uint8_t colormap_size; + uint16_t x_origin; + uint16_t y_origin; + uint16_t width; + uint16_t height; + uint8_t pixel_size; + uint8_t flags; + enum { Size = 18 }; +}; +#pragma pack(pop) + +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"); + if (!f) + return; + TgaHeader tga; + tga.id_length = 0; + tga.colormap_type = 0; + tga.image_type = TGA_TYPE_RGB; + tga.colormap_index = 0; + tga.colormap_length = 0; + tga.colormap_size = 0; + tga.x_origin = 0; + tga.y_origin = 0; + tga.width = (uint16_t)width; + tga.height = (uint16_t)height; + tga.pixel_size = 24; + tga.flags = TGA_ORIGIN_UPPER; + fwrite(&tga, sizeof(TgaHeader), 1, f); + fwrite(data, sizeof(uint8_t), width * height * 3, f); + fclose(f); +} +#endif + +class AtlasImage +{ +public: + AtlasImage(uint32_t width, uint32_t height) : m_width(width), m_height(height) { - MeshCharts *meshCharts = new MeshCharts(mesh); - meshCharts->extractCharts(); - addMeshCharts(meshCharts); + m_data.resize(m_width * m_height); + memset(m_data.data(), 0, sizeof(uint32_t) * m_data.size()); } - void computeCharts(const halfedge::Mesh *mesh, const CharterOptions &options, const std::vector<uint32_t> &unchartedMaterialArray) + void resize(uint32_t width, uint32_t height) { - MeshCharts *meshCharts = new MeshCharts(mesh); - meshCharts->computeCharts(options, unchartedMaterialArray); - addMeshCharts(meshCharts); + Array<uint32_t> data; + data.resize(width * height); + memset(data.data(), 0, sizeof(uint32_t) * data.size()); + for (uint32_t y = 0; y < min(m_height, height); y++) + memcpy(&data[y * width], &m_data[y * m_width], min(m_width, width) * sizeof(uint32_t)); + m_width = width; + m_height = height; + swap(m_data, data); } - void parameterizeCharts() + void addChart(uint32_t chartIndex, const BitImage *image, bool imageHasPadding, int atlas_w, int atlas_h, int offset_x, int offset_y) { - for (uint32_t i = 0; i < m_meshChartsArray.size(); i++) { - m_meshChartsArray[i]->parameterizeCharts(); + const int w = image->width(); + const int h = image->height(); + for (int y = 0; y < h; y++) { + const int yy = y + offset_y; + if (yy < 0) + continue; + for (int x = 0; x < w; x++) { + const int xx = x + offset_x; + if (xx >= 0 && xx < atlas_w && yy < atlas_h && image->bitAt(x, y)) { + const uint32_t dataOffset = xx + yy * m_width; + if (m_data[dataOffset] != 0) + continue; + uint32_t value = chartIndex | kImageHasChartIndexBit; + if (imageHasPadding) + value |= kImageIsPaddingBit; + m_data[dataOffset] = value; + } + } + } + } + + void copyTo(uint32_t *dest, uint32_t destWidth, uint32_t destHeight) const + { + for (uint32_t y = 0; y < destHeight; y++) + memcpy(&dest[y * destWidth], &m_data[y * m_width], destWidth * sizeof(uint32_t)); + } + +#if XA_DEBUG_EXPORT_ATLAS_IMAGES + 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++) { + if (y >= m_height) + continue; + for (uint32_t x = 0; x < width; x++) { + if (x >= m_width) + continue; + const uint32_t data = m_data[x + y * m_width]; + if (!(data & kImageHasChartIndexBit)) + continue; + const uint32_t chartIndex = data & kImageChartIndexMask; + uint8_t *color = &image[(x + y * width) * 3]; + if (data & kImageIsPaddingBit) { + color[0] = 255; + color[1] = 0; + color[2] = 255; + } else { + const int mix = 192; + srand((unsigned int)chartIndex); + color[0] = uint8_t((rand() % 255 + mix) * 0.5f); + color[1] = uint8_t((rand() % 255 + mix) * 0.5f); + color[2] = uint8_t((rand() % 255 + mix) * 0.5f); + } + } } + WriteTga(filename, image.data(), width, height); } +#endif private: - std::vector<MeshCharts *> m_meshChartsArray; + uint32_t m_width, m_height; + Array<uint32_t> m_data; }; -struct AtlasPacker +struct Chart +{ + int32_t atlasIndex; + uint32_t material; + uint32_t indexCount; + const uint32_t *indices; + float parametricArea; + float surfaceArea; + Vector2 *vertices; + uint32_t vertexCount; + Array<uint32_t> uniqueVertices; + bool allowRotate; + // bounding box + Vector2 majorAxis, minorAxis, minCorner, maxCorner; + + Vector2 &uniqueVertexAt(uint32_t v) { return uniqueVertices.isEmpty() ? vertices[v] : vertices[uniqueVertices[v]]; } + uint32_t uniqueVertexCount() const { return uniqueVertices.isEmpty() ? vertexCount : uniqueVertices.size(); } +}; + +struct Atlas { - AtlasPacker(Atlas *atlas) : m_atlas(atlas), m_width(0), m_height(0) + ~Atlas() { - // Save the original uvs. - m_originalChartUvs.resize(m_atlas->chartCount()); - for (uint32_t i = 0; i < m_atlas->chartCount(); i++) { - const halfedge::Mesh *mesh = atlas->chartAt(i)->chartMesh(); - m_originalChartUvs[i].resize(mesh->vertexCount()); - for (uint32_t j = 0; j < mesh->vertexCount(); j++) - m_originalChartUvs[i][j] = mesh->vertexAt(j)->tex; + for (uint32_t i = 0; i < m_bitImages.size(); i++) { + m_bitImages[i]->~BitImage(); + XA_FREE(m_bitImages[i]); + } + for (uint32_t i = 0; i < m_charts.size(); i++) { + m_charts[i]->~Chart(); + XA_FREE(m_charts[i]); } } uint32_t getWidth() const { return m_width; } uint32_t getHeight() const { return m_height; } + uint32_t getNumAtlases() const { return m_bitImages.size(); } + float getTexelsPerUnit() const { return m_texelsPerUnit; } + const Chart *getChart(uint32_t index) const { return m_charts[index]; } + uint32_t getChartCount() const { return m_charts.size(); } + const Array<AtlasImage *> &getImages() const { return m_atlasImages; } + float getUtilization(uint32_t atlas) const { return m_utilization[atlas]; } + + void addChart(param::Chart *paramChart) + { + Mesh *mesh = paramChart->mesh(); + Chart *chart = XA_NEW(MemTag::Default, Chart); + chart->atlasIndex = -1; + chart->material = 0; + chart->indexCount = mesh->indexCount(); + chart->indices = mesh->indices(); + chart->parametricArea = paramChart->computeParametricArea(); + if (chart->parametricArea < kAreaEpsilon) { + // When the parametric area is too small we use a rough approximation to prevent divisions by very small numbers. + const Vector2 bounds = paramChart->computeParametricBounds(); + chart->parametricArea = bounds.x * bounds.y; + } + chart->surfaceArea = paramChart->computeSurfaceArea(); + chart->vertices = mesh->texcoords(); + chart->vertexCount = mesh->vertexCount(); + chart->allowRotate = true; + // Compute list of boundary vertices. + Array<Vector2> boundary; + boundary.reserve(16); + for (uint32_t v = 0; v < chart->vertexCount; v++) { + if (mesh->isBoundaryVertex(v)) + boundary.push_back(mesh->texcoord(v)); + } + XA_DEBUG_ASSERT(boundary.size() > 0); + // Compute bounding box of chart. + m_boundingBox.compute(boundary.data(), boundary.size(), mesh->texcoords(), mesh->vertexCount()); + chart->majorAxis = m_boundingBox.majorAxis(); + chart->minorAxis = m_boundingBox.minorAxis(); + chart->minCorner = m_boundingBox.minCorner(); + chart->maxCorner = m_boundingBox.maxCorner(); + m_charts.push_back(chart); + } + + void addUvMeshCharts(UvMeshInstance *mesh) + { + BitArray vertexUsed(mesh->texcoords.size()); + Array<Vector2> boundary; + boundary.reserve(16); + for (uint32_t c = 0; c < mesh->mesh->charts.size(); c++) { + UvMeshChart *uvChart = mesh->mesh->charts[c]; + 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; + // Find unique vertices. + vertexUsed.clearAll(); + for (uint32_t i = 0; i < chart->indexCount; i++) { + const uint32_t vertex = chart->indices[i]; + if (!vertexUsed.bitAt(vertex)) { + vertexUsed.setBitAt(vertex); + chart->uniqueVertices.push_back(vertex); + } + } + // Compute parametric and surface areas. + chart->parametricArea = 0.0f; + for (uint32_t f = 0; f < chart->indexCount / 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); + Vector2 maxCorner(-FLT_MAX, -FLT_MAX); + for (uint32_t v = 0; v < chart->uniqueVertexCount(); v++) { + minCorner = min(minCorner, chart->uniqueVertexAt(v)); + maxCorner = max(maxCorner, chart->uniqueVertexAt(v)); + } + const Vector2 bounds = (maxCorner - minCorner) * 0.5f; + chart->parametricArea = bounds.x * bounds.y; + } + // Compute list of boundary vertices. + // Using all unique vertices for simplicity, can compute real boundaries if this is too slow. + boundary.clear(); + for (uint32_t v = 0; v < chart->uniqueVertexCount(); v++) + boundary.push_back(chart->uniqueVertexAt(v)); + XA_DEBUG_ASSERT(boundary.size() > 0); + // Compute bounding box of chart. + m_boundingBox.compute(boundary.data(), boundary.size(), boundary.data(), boundary.size()); + chart->majorAxis = m_boundingBox.majorAxis(); + chart->minorAxis = m_boundingBox.minorAxis(); + chart->minCorner = m_boundingBox.minCorner(); + chart->maxCorner = m_boundingBox.maxCorner(); + m_charts.push_back(chart); + } + } // Pack charts in the smallest possible rectangle. - void packCharts(const PackerOptions &options) - { - const uint32_t chartCount = m_atlas->chartCount(); - if (chartCount == 0) return; - float texelsPerUnit = 1; - if (options.method == PackMethod::TexelArea) - texelsPerUnit = options.texelArea; - for (int iteration = 0;; iteration++) { - m_rand = MTRand(); - std::vector<float> chartOrderArray(chartCount); - std::vector<Vector2> chartExtents(chartCount); + bool packCharts(const PackOptions &options, ProgressFunc progressFunc, void *progressUserData) + { + if (progressFunc) { + if (!progressFunc(ProgressCategory::PackCharts, 0, progressUserData)) + return false; + } + const uint32_t chartCount = m_charts.size(); + XA_PRINT("Packing %u charts\n", chartCount); + if (chartCount == 0) { + if (progressFunc) { + if (!progressFunc(ProgressCategory::PackCharts, 100, progressUserData)) + return false; + } + return true; + } + uint32_t resolution = options.resolution; + m_texelsPerUnit = options.texelsPerUnit; + if (resolution <= 0 || m_texelsPerUnit <= 0) { + if (resolution <= 0 && m_texelsPerUnit <= 0) + resolution = 1024; float meshArea = 0; - for (uint32_t c = 0; c < chartCount; c++) { - Chart *chart = m_atlas->chartAt(c); - if (!chart->isVertexMapped() && !chart->isDisk()) { - chartOrderArray[c] = 0; - // Skip non-disks. - continue; - } - Vector2 extents(0.0f); - if (chart->isVertexMapped()) { - // Arrange vertices in a rectangle. - extents.x = float(chart->vertexMapWidth); - extents.y = float(chart->vertexMapHeight); + for (uint32_t c = 0; c < chartCount; c++) + meshArea += m_charts[c]->surfaceArea; + if (resolution <= 0) { + // Estimate resolution based on the mesh surface area and given texel scale. + const float texelCount = max(1.0f, meshArea * square(m_texelsPerUnit) / 0.75f); // Assume 75% utilization. + resolution = max(1u, nextPowerOfTwo(uint32_t(sqrtf(texelCount)))); + } + if (m_texelsPerUnit <= 0) { + // Estimate a suitable texelsPerUnit to fit the given resolution. + const float texelCount = max(1.0f, meshArea / 0.75f); // Assume 75% utilization. + m_texelsPerUnit = sqrtf((resolution * resolution) / texelCount); + XA_PRINT(" Estimating texelsPerUnit as %g\n", m_texelsPerUnit); + } + } + Array<float> chartOrderArray; + chartOrderArray.resize(chartCount); + Array<Vector2> chartExtents; + chartExtents.resize(chartCount); + float minChartPerimeter = FLT_MAX, maxChartPerimeter = 0.0f; + for (uint32_t c = 0; c < chartCount; c++) { + Chart *chart = m_charts[c]; + //chartOrderArray[c] = chart.surfaceArea; + // Compute chart scale + float scale = (chart->surfaceArea / chart->parametricArea) * m_texelsPerUnit; + if (chart->parametricArea == 0) { // < kAreaEpsilon) + scale = 0; + } + XA_ASSERT(isFinite(scale)); + // Sort charts by perimeter. @@ This is sometimes producing somewhat unexpected results. Is this right? + //chartOrderArray[c] = ((chart->maxCorner.x - chart->minCorner.x) + (chart->maxCorner.y - chart->minCorner.y)) * scale; + // Translate, rotate and scale vertices. Compute extents. + Vector2 minCorner(FLT_MAX, FLT_MAX); + if (!chart->allowRotate) { + 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) { + const float x = dot(texcoord, chart->majorAxis); + const float y = dot(texcoord, chart->minorAxis); + texcoord.x = x; + texcoord.y = y; + texcoord -= chart->minCorner; } else { - // Compute surface area to sort charts. - float chartArea = chart->computeSurfaceArea(); - meshArea += chartArea; - //chartOrderArray[c] = chartArea; - // Compute chart scale - float parametricArea = fabsf(chart->computeParametricArea()); // @@ There doesn't seem to be anything preventing parametric area to be negative. - if (parametricArea < NV_EPSILON) { - // When the parametric area is too small we use a rough approximation to prevent divisions by very small numbers. - Vector2 bounds = chart->computeParametricBounds(); - parametricArea = bounds.x * bounds.y; - } - float scale = (chartArea / parametricArea) * texelsPerUnit; - if (parametricArea == 0) { // < NV_EPSILON) - scale = 0; - } - xaAssert(std::isfinite(scale)); - // Compute bounding box of chart. - Vector2 majorAxis, minorAxis, origin, end; - computeBoundingBox(chart, &majorAxis, &minorAxis, &origin, &end); - xaAssert(isFinite(majorAxis) && isFinite(minorAxis) && isFinite(origin)); - // Sort charts by perimeter. @@ This is sometimes producing somewhat unexpected results. Is this right? - //chartOrderArray[c] = ((end.x - origin.x) + (end.y - origin.y)) * scale; - // Translate, rotate and scale vertices. Compute extents. - halfedge::Mesh *mesh = chart->chartMesh(); - const uint32_t vertexCount = mesh->vertexCount(); - for (uint32_t i = 0; i < vertexCount; i++) { - halfedge::Vertex *vertex = mesh->vertexAt(i); - //Vector2 t = vertex->tex - origin; - Vector2 tmp; - tmp.x = dot(vertex->tex, majorAxis); - tmp.y = dot(vertex->tex, minorAxis); - tmp -= origin; - tmp *= scale; - if (tmp.x < 0 || tmp.y < 0) { - xaPrint("tmp: %f %f\n", tmp.x, tmp.y); - xaPrint("scale: %f\n", scale); - xaPrint("origin: %f %f\n", origin.x, origin.y); - xaPrint("majorAxis: %f %f\n", majorAxis.x, majorAxis.y); - xaPrint("minorAxis: %f %f\n", minorAxis.x, minorAxis.y); - xaDebugAssert(false); - } - //xaAssert(tmp.x >= 0 && tmp.y >= 0); - vertex->tex = tmp; - xaAssert(std::isfinite(vertex->tex.x) && std::isfinite(vertex->tex.y)); - extents = max(extents, tmp); - } - xaDebugAssert(extents.x >= 0 && extents.y >= 0); - // Limit chart size. - if (extents.x > 1024 || extents.y > 1024) { - float limit = std::max(extents.x, extents.y); - scale = 1024 / (limit + 1); - for (uint32_t i = 0; i < vertexCount; i++) { - halfedge::Vertex *vertex = mesh->vertexAt(i); - vertex->tex *= scale; - } - extents *= scale; - xaDebugAssert(extents.x <= 1024 && extents.y <= 1024); - } - // Scale the charts to use the entire texel area available. So, if the width is 0.1 we could scale it to 1 without increasing the lightmap usage and making a better - // use of it. In many cases this also improves the look of the seams, since vertices on the chart boundaries have more chances of being aligned with the texel centers. - float scale_x = 1.0f; - float scale_y = 1.0f; - float divide_x = 1.0f; - float divide_y = 1.0f; - if (extents.x > 0) { - int cw = ftoi_ceil(extents.x); - if (options.blockAlign && chart->blockAligned) { - // Align all chart extents to 4x4 blocks, but taking padding into account. - if (options.conservative) { - cw = align(cw + 2, 4) - 2; - } else { - cw = align(cw + 1, 4) - 1; - } - } - scale_x = (float(cw) - NV_EPSILON); - divide_x = extents.x; - extents.x = float(cw); - } - if (extents.y > 0) { - int ch = ftoi_ceil(extents.y); - if (options.blockAlign && chart->blockAligned) { - // Align all chart extents to 4x4 blocks, but taking padding into account. - if (options.conservative) { - ch = align(ch + 2, 4) - 2; - } else { - ch = align(ch + 1, 4) - 1; - } - } - scale_y = (float(ch) - NV_EPSILON); - divide_y = extents.y; - extents.y = float(ch); - } - for (uint32_t v = 0; v < vertexCount; v++) { - halfedge::Vertex *vertex = mesh->vertexAt(v); - vertex->tex.x /= divide_x; - vertex->tex.y /= divide_y; - vertex->tex.x *= scale_x; - vertex->tex.y *= scale_y; - xaAssert(std::isfinite(vertex->tex.x) && std::isfinite(vertex->tex.y)); - } + texcoord -= minCorner; + } + texcoord *= scale; + XA_DEBUG_ASSERT(texcoord.x >= 0 && texcoord.y >= 0); + XA_DEBUG_ASSERT(isFinite(texcoord.x) && isFinite(texcoord.y)); + extents = max(extents, texcoord); + } + XA_DEBUG_ASSERT(extents.x >= 0 && extents.y >= 0); + // Limit chart size. + const float maxChartSize = (float)options.maxChartSize; + if (extents.x > maxChartSize || extents.y > maxChartSize) { + const float limit = max(extents.x, extents.y); + scale = maxChartSize / (limit + 1.0f); + for (uint32_t i = 0; i < chart->uniqueVertexCount(); i++) + chart->uniqueVertexAt(i) *= scale; + extents *= scale; + XA_DEBUG_ASSERT(extents.x <= maxChartSize && extents.y <= maxChartSize); + } + // Scale the charts to use the entire texel area available. So, if the width is 0.1 we could scale it to 1 without increasing the lightmap usage and making a better + // use of it. In many cases this also improves the look of the seams, since vertices on the chart boundaries have more chances of being aligned with the texel centers. + float scale_x = 1.0f; + float scale_y = 1.0f; + float divide_x = 1.0f; + float divide_y = 1.0f; + if (extents.x > 0) { + int cw = ftoi_ceil(extents.x); + if (options.blockAlign) { + // Align all chart extents to 4x4 blocks, but taking padding into account. + cw = align(cw + 2, 4) - 2; + } + scale_x = (float(cw) - kEpsilon); + divide_x = extents.x; + extents.x = float(cw); + } + if (extents.y > 0) { + int ch = ftoi_ceil(extents.y); + if (options.blockAlign) { + // Align all chart extents to 4x4 blocks, but taking padding into account. + ch = align(ch + 2, 4) - 2; + } + scale_y = (float(ch) - kEpsilon); + divide_y = extents.y; + extents.y = float(ch); + } + for (uint32_t v = 0; v < chart->uniqueVertexCount(); v++) { + Vector2 &texcoord = chart->uniqueVertexAt(v); + texcoord.x /= divide_x; + texcoord.y /= divide_y; + texcoord.x *= scale_x; + texcoord.y *= scale_y; + XA_ASSERT(isFinite(texcoord.x) && isFinite(texcoord.y)); + } + chartExtents[c] = extents; + // Sort charts by perimeter. + chartOrderArray[c] = extents.x + extents.y; + minChartPerimeter = min(minChartPerimeter, chartOrderArray[c]); + maxChartPerimeter = max(maxChartPerimeter, chartOrderArray[c]); + } + // Sort charts by perimeter. + m_radix = RadixSort(); + m_radix.sort(chartOrderArray); + const uint32_t *ranks = m_radix.ranks(); + // Divide chart perimeter range into buckets. + const float chartPerimeterBucketSize = (maxChartPerimeter - minChartPerimeter) / 16.0f; + uint32_t currentChartBucket = 0; + Array<Vector2i> chartStartPositions; // per atlas + chartStartPositions.push_back(Vector2i(0, 0)); + // Pack sorted charts. +#if XA_DEBUG_EXPORT_ATLAS_IMAGES + const bool createImage = true; +#else + const bool createImage = options.createImage; +#endif + BitImage chartBitImage, chartBitImageRotated; + int atlasWidth = 0, atlasHeight = 0; + const bool resizableAtlas = !(options.resolution > 0 && options.texelsPerUnit > 0.0f); + int progress = 0; + for (uint32_t i = 0; i < chartCount; i++) { + uint32_t c = ranks[chartCount - i - 1]; // largest chart first + Chart *chart = m_charts[c]; + // @@ Add special cases for dot and line charts. @@ Lightmap rasterizer also needs to handle these special cases. + // @@ We could also have a special case for chart quads. If the quad surface <= 4 texels, align vertices with texel centers and do not add padding. May be very useful for foliage. + // @@ In general we could reduce the padding of all charts by one texel by using a rasterizer that takes into account the 2-texel footprint of the tent bilinear filter. For example, + // if we have a chart that is less than 1 texel wide currently we add one texel to the left and one texel to the right creating a 3-texel-wide bitImage. However, if we know that the + // chart is only 1 texel wide we could align it so that it only touches the footprint of two texels: + // | | <- Touches texels 0, 1 and 2. + // | | <- Only touches texels 0 and 1. + // \ \ / \ / / + // \ X X / + // \ / \ / \ / + // V V V + // 0 1 2 + XA_PROFILE_START(packChartsRasterize) + // Leave room for padding. + chartBitImage.resize(ftoi_ceil(chartExtents[c].x) + 1 + options.padding * 2, ftoi_ceil(chartExtents[c].y) + 1 + options.padding * 2, true); + if (chart->allowRotate) + chartBitImageRotated.resize(chartBitImage.height(), chartBitImage.width(), true); + // Rasterize chart faces. + const uint32_t faceCount = chart->indexCount / 3; + for (uint32_t f = 0; f < faceCount; f++) { + // Offset vertices by padding. + Vector2 vertices[3]; + for (uint32_t v = 0; v < 3; v++) + vertices[v] = chart->vertices[chart->indices[f * 3 + v]] + Vector2(0.5f) + Vector2(float(options.padding)); + DrawTriangleCallbackArgs args; + args.chartBitImage = &chartBitImage; + args.chartBitImageRotated = chart->allowRotate ? &chartBitImageRotated : nullptr; + raster::drawTriangle(Vector2((float)chartBitImage.width(), (float)chartBitImage.height()), vertices, drawTriangleCallback, &args); + } + // Expand chart by padding pixels. (dilation) + BitImage chartBitImageNoPadding(chartBitImage), chartBitImageNoPaddingRotated(chartBitImageRotated); + if (options.padding > 0) { + XA_PROFILE_START(packChartsDilate) + chartBitImage.dilate(options.padding); + if (chart->allowRotate) + chartBitImageRotated.dilate(options.padding); + XA_PROFILE_END(packChartsDilate) + } + XA_PROFILE_END(packChartsRasterize) + // Update brute force bucketing. + if (options.bruteForce) { + if (chartOrderArray[c] > minChartPerimeter && chartOrderArray[c] <= maxChartPerimeter - (chartPerimeterBucketSize * (currentChartBucket + 1))) { + // Moved to a smaller bucket, reset start location. + for (uint32_t j = 0; j < chartStartPositions.size(); j++) + chartStartPositions[j] = Vector2i(0, 0); + currentChartBucket++; } - chartExtents[c] = extents; - // Sort charts by perimeter. - chartOrderArray[c] = extents.x + extents.y; - } - // @@ We can try to improve compression of small charts by sorting them by proximity like we do with vertex samples. - // @@ How to do that? One idea: compute chart centroid, insert into grid, compute morton index of the cell, sort based on morton index. - // @@ We would sort by morton index, first, then quantize the chart sizes, so that all small charts have the same size, and sort by size preserving the morton order. - //xaPrint("Sorting charts.\n"); - // Sort charts by area. - m_radix = RadixSort(); - m_radix.sort(chartOrderArray); - const uint32_t *ranks = m_radix.ranks(); - // First iteration - guess texelsPerUnit. - if (options.method != PackMethod::TexelArea && iteration == 0) { - // Estimate size of the map based on the mesh surface area and given texel scale. - const float texelCount = std::max(1.0f, meshArea * square(texelsPerUnit) / 0.75f); // Assume 75% utilization. - texelsPerUnit = sqrt((options.resolution * options.resolution) / texelCount); - resetUvs(); - continue; } - // Init bit map. - m_bitmap.clearAll(); - m_bitmap.resize(options.resolution, options.resolution, false); - int w = 0; - int h = 0; - // Add sorted charts to bitmap. - for (uint32_t i = 0; i < chartCount; i++) { - uint32_t c = ranks[chartCount - i - 1]; // largest chart first - Chart *chart = m_atlas->chartAt(c); - if (!chart->isVertexMapped() && !chart->isDisk()) continue; - //float scale_x = 1; - //float scale_y = 1; - BitMap chart_bitmap; - if (chart->isVertexMapped()) { - chart->blockAligned = false; - // Init all bits to 1. - chart_bitmap.resize(ftoi_ceil(chartExtents[c].x), ftoi_ceil(chartExtents[c].y), /*initValue=*/true); - // @@ Another alternative would be to try to map each vertex to a different texel trying to fill all the available unused texels. - } else { - // @@ Add special cases for dot and line charts. @@ Lightmap rasterizer also needs to handle these special cases. - // @@ We could also have a special case for chart quads. If the quad surface <= 4 texels, align vertices with texel centers and do not add padding. May be very useful for foliage. - // @@ In general we could reduce the padding of all charts by one texel by using a rasterizer that takes into account the 2-texel footprint of the tent bilinear filter. For example, - // if we have a chart that is less than 1 texel wide currently we add one texel to the left and one texel to the right creating a 3-texel-wide bitmap. However, if we know that the - // chart is only 1 texel wide we could align it so that it only touches the footprint of two texels: - // | | <- Touches texels 0, 1 and 2. - // | | <- Only touches texels 0 and 1. - // \ \ / \ / / - // \ X X / - // \ / \ / \ / - // V V V - // 0 1 2 - if (options.conservative) { - // Init all bits to 0. - chart_bitmap.resize(ftoi_ceil(chartExtents[c].x) + 1 + options.padding, ftoi_ceil(chartExtents[c].y) + 1 + options.padding, /*initValue=*/false); // + 2 to add padding on both sides. - // Rasterize chart and dilate. - drawChartBitmapDilate(chart, &chart_bitmap, options.padding); - } else { - // Init all bits to 0. - chart_bitmap.resize(ftoi_ceil(chartExtents[c].x) + 1, ftoi_ceil(chartExtents[c].y) + 1, /*initValue=*/false); // Add half a texels on each side. - // Rasterize chart and dilate. - drawChartBitmap(chart, &chart_bitmap, Vector2(1), Vector2(0.5)); - } + // Find a location to place the chart in the atlas. + uint32_t currentAtlas = 0; + int best_x = 0, best_y = 0; + int best_cw = 0, best_ch = 0; + int best_r = 0; + for (;;) + { + bool firstChartInBitImage = false; + if (currentAtlas + 1 > m_bitImages.size()) { + // Chart doesn't fit in the current bitImage, create a new one. + BitImage *bi = XA_NEW(MemTag::Default, BitImage); + bi->resize(resolution, resolution, true); + m_bitImages.push_back(bi); + firstChartInBitImage = true; + if (createImage) + m_atlasImages.push_back(XA_NEW(MemTag::Default, AtlasImage, resolution, resolution)); + // Start positions are per-atlas, so create a new one of those too. + chartStartPositions.push_back(Vector2i(0, 0)); } - int best_x, best_y; - int best_cw, best_ch; // Includes padding now. - int best_r; - findChartLocation(options.quality, &chart_bitmap, chartExtents[c], w, h, &best_x, &best_y, &best_cw, &best_ch, &best_r, chart->blockAligned); - /*if (w < best_x + best_cw || h < best_y + best_ch) - { - xaPrint("Resize extents to (%d, %d).\n", best_x + best_cw, best_y + best_ch); - }*/ - // Update parametric extents. - w = std::max(w, best_x + best_cw); - h = std::max(h, best_y + best_ch); - w = align(w, 4); - h = align(h, 4); - // Resize bitmap if necessary. - if (uint32_t(w) > m_bitmap.width() || uint32_t(h) > m_bitmap.height()) { - //xaPrint("Resize bitmap (%d, %d).\n", nextPowerOfTwo(w), nextPowerOfTwo(h)); - m_bitmap.resize(nextPowerOfTwo(uint32_t(w)), nextPowerOfTwo(uint32_t(h)), false); + XA_PROFILE_START(packChartsFindLocation) + const bool foundLocation = findChartLocation(chartStartPositions[currentAtlas], options.bruteForce, m_bitImages[currentAtlas], &chartBitImage, &chartBitImageRotated, atlasWidth, atlasHeight, &best_x, &best_y, &best_cw, &best_ch, &best_r, options.blockAlign, resizableAtlas, chart->allowRotate); + XA_PROFILE_END(packChartsFindLocation) + if (firstChartInBitImage && !foundLocation) { + // Chart doesn't fit in an empty, newly allocated bitImage. texelsPerUnit must be too large for the resolution. + XA_ASSERT(true && "chart doesn't fit"); + break; + } + if (resizableAtlas) { + XA_DEBUG_ASSERT(foundLocation); + break; + } + if (foundLocation) + break; + // Chart doesn't fit in the current bitImage, try the next one. + currentAtlas++; + } + // Update brute force start location. + if (options.bruteForce) { + // Reset start location if the chart expanded the atlas. + if (best_x + best_cw > atlasWidth || best_y + best_ch > atlasHeight) { + for (uint32_t j = 0; j < chartStartPositions.size(); j++) + chartStartPositions[j] = Vector2i(0, 0); } - //xaPrint("Add chart at (%d, %d).\n", best_x, best_y); - addChart(&chart_bitmap, w, h, best_x, best_y, best_r); - //float best_angle = 2 * PI * best_r; - // Translate and rotate chart texture coordinates. - halfedge::Mesh *mesh = chart->chartMesh(); - const uint32_t vertexCount = mesh->vertexCount(); - for (uint32_t v = 0; v < vertexCount; v++) { - halfedge::Vertex *vertex = mesh->vertexAt(v); - Vector2 t = vertex->tex; - if (best_r) std::swap(t.x, t.y); - //vertex->tex.x = best_x + t.x * cosf(best_angle) - t.y * sinf(best_angle); - //vertex->tex.y = best_y + t.x * sinf(best_angle) + t.y * cosf(best_angle); - vertex->tex.x = best_x + t.x + 0.5f; - vertex->tex.y = best_y + t.y + 0.5f; - xaAssert(vertex->tex.x >= 0 && vertex->tex.y >= 0); - xaAssert(std::isfinite(vertex->tex.x) && std::isfinite(vertex->tex.y)); + else { + chartStartPositions[currentAtlas] = Vector2i(best_x, best_y); } } - //w -= padding - 1; // Leave one pixel border! - //h -= padding - 1; - m_width = std::max(0, w); - m_height = std::max(0, h); - xaAssert(isAligned(m_width, 4)); - xaAssert(isAligned(m_height, 4)); - if (options.method == PackMethod::ExactResolution) { - texelsPerUnit *= sqrt((options.resolution * options.resolution) / (float)(m_width * m_height)); - if (iteration > 1 && m_width <= options.resolution && m_height <= options.resolution) { - m_width = m_height = options.resolution; - return; + // Update parametric extents. + atlasWidth = max(atlasWidth, best_x + best_cw); + atlasHeight = max(atlasHeight, best_y + best_ch); + if (resizableAtlas) { + // Resize bitImage if necessary. + if (uint32_t(atlasWidth) > m_bitImages[0]->width() || uint32_t(atlasHeight) > m_bitImages[0]->height()) { + m_bitImages[0]->resize(nextPowerOfTwo(uint32_t(atlasWidth)), nextPowerOfTwo(uint32_t(atlasHeight)), false); + if (createImage) + m_atlasImages[0]->resize(m_bitImages[0]->width(), m_bitImages[0]->height()); } - resetUvs(); } else { - return; + atlasWidth = min((int)options.resolution, atlasWidth); + atlasHeight = min((int)options.resolution, atlasHeight); + } + XA_PROFILE_START(packChartsBlit) + addChart(m_bitImages[currentAtlas], &chartBitImage, &chartBitImageRotated, atlasWidth, atlasHeight, best_x, best_y, best_r); + XA_PROFILE_END(packChartsBlit) + if (createImage) { + m_atlasImages[currentAtlas]->addChart(c, best_r == 0 ? &chartBitImageNoPadding : &chartBitImageNoPaddingRotated, false, atlasWidth, atlasHeight, best_x, best_y); + m_atlasImages[currentAtlas]->addChart(c, best_r == 0 ? &chartBitImage : &chartBitImageRotated, true, atlasWidth, atlasHeight, best_x, best_y); + } + chart->atlasIndex = (int32_t)currentAtlas; + // Translate and rotate chart texture coordinates. + for (uint32_t v = 0; v < chart->uniqueVertexCount(); v++) { + Vector2 &texcoord = chart->uniqueVertexAt(v); + Vector2 t = texcoord; + if (best_r) { + XA_DEBUG_ASSERT(chart->allowRotate); + swap(t.x, t.y); + } + texcoord.x = best_x + t.x + 0.5f; + texcoord.y = best_y + t.y + 0.5f; + XA_ASSERT(texcoord.x >= 0 && texcoord.y >= 0); + XA_ASSERT(isFinite(texcoord.x) && isFinite(texcoord.y)); + } + if (progressFunc) { + const int newProgress = int((i + 1) / (float)chartCount * 100.0f); + if (newProgress != progress) { + progress = newProgress; + if (!progressFunc(ProgressCategory::PackCharts, progress, progressUserData)) + return false; + } } } - } - - float computeAtlasUtilization() const - { - const uint32_t w = m_width; - const uint32_t h = m_height; - xaDebugAssert(w <= m_bitmap.width()); - xaDebugAssert(h <= m_bitmap.height()); - uint32_t count = 0; - for (uint32_t y = 0; y < h; y++) { - for (uint32_t x = 0; x < w; x++) { - count += m_bitmap.bitAt(x, y); + if (resizableAtlas) { + m_width = max(0, atlasWidth - (int)options.padding * 2); + m_height = max(0, atlasHeight - (int)options.padding * 2); + } else { + m_width = m_height = options.resolution; + } + XA_PRINT(" %dx%d resolution\n", m_width, m_height); + m_utilization.resize(m_bitImages.size()); + for (uint32_t i = 0; i < m_utilization.size(); i++) { + uint32_t count = 0; + for (uint32_t y = 0; y < m_height; y++) { + for (uint32_t x = 0; x < m_width; x++) + count += m_bitImages[i]->bitAt(x, y); + } + m_utilization[i] = float(count) / (m_width * m_height); + if (m_utilization.size() > 1) { + XA_PRINT(" %u: %f%% utilization\n", i, m_utilization[i] * 100.0f); + } + else { + XA_PRINT(" %f%% utilization\n", m_utilization[i] * 100.0f); } } - return float(count) / (w * h); - } - -private: - void resetUvs() - { - for (uint32_t i = 0; i < m_atlas->chartCount(); i++) { - halfedge::Mesh *mesh = m_atlas->chartAt(i)->chartMesh(); - for (uint32_t j = 0; j < mesh->vertexCount(); j++) - mesh->vertexAt(j)->tex = m_originalChartUvs[i][j]; +#if XA_DEBUG_EXPORT_ATLAS_IMAGES + for (uint32_t i = 0; i < m_atlasImages.size(); i++) { + char filename[256]; + XA_SPRINTF(filename, sizeof(filename), "debug_atlas_image%02u.tga", i); + m_atlasImages[i]->writeTga(filename, m_width, m_height); + } +#endif + if (progressFunc && progress != 100) { + if (!progressFunc(ProgressCategory::PackCharts, 100, progressUserData)) + return false; } + return true; } +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. - void findChartLocation(int quality, const BitMap *bitmap, Vector2::Arg extents, int w, int h, int *best_x, int *best_y, int *best_w, int *best_h, int *best_r, bool blockAligned) - { - int attempts = 256; - if (quality == 1) attempts = 4096; - if (quality == 2) attempts = 2048; - if (quality == 3) attempts = 1024; - if (quality == 4) attempts = 512; - if (quality == 0 || w * h < attempts) { - findChartLocation_bruteForce(bitmap, extents, w, h, best_x, best_y, best_w, best_h, best_r, blockAligned); - } else { - findChartLocation_random(bitmap, extents, w, h, best_x, best_y, best_w, best_h, best_r, attempts, blockAligned); - } + 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, bool resizableAtlas, bool allowRotate) + { + 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, resizableAtlas, allowRotate); + return findChartLocation_random(atlasBitImage, chartBitImage, chartBitImageRotated, w, h, best_x, best_y, best_w, best_h, best_r, attempts, blockAligned, resizableAtlas, allowRotate); } - void findChartLocation_bruteForce(const BitMap *bitmap, Vector2::Arg /*extents*/, int w, int h, int *best_x, int *best_y, int *best_w, int *best_h, int *best_r, bool blockAligned) + 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, bool resizableAtlas, bool allowRotate) { + bool result = false; const int BLOCK_SIZE = 4; int best_metric = INT_MAX; int step_size = blockAligned ? BLOCK_SIZE : 1; // Try two different orientations. for (int r = 0; r < 2; r++) { - int cw = bitmap->width(); - int ch = bitmap->height(); - if (r & 1) std::swap(cw, ch); - for (int y = 0; y <= h + 1; y += step_size) { // + 1 to extend atlas in case atlas full. - for (int x = 0; x <= w + 1; x += step_size) { // + 1 not really necessary here. + int cw = chartBitImage->width(); + int ch = chartBitImage->height(); + if (r == 1) { + if (allowRotate) + swap(cw, ch); + else + break; + } + for (int y = startPosition.y; y <= h + step_size; y += step_size) { // + 1 to extend atlas in case atlas full. + for (int x = (y == startPosition.y ? startPosition.x : 0); x <= w + step_size; x += step_size) { // + 1 not really necessary here. + if (!resizableAtlas && (x > (int)atlasBitImage->width() - cw || y > (int)atlasBitImage->height() - ch)) + continue; // Early out. - int area = std::max(w, x + cw) * std::max(h, y + ch); + int area = max(w, x + cw) * max(h, y + ch); //int perimeter = max(w, x+cw) + max(h, y+ch); - int extents = std::max(std::max(w, x + cw), std::max(h, y + ch)); + int extents = max(max(w, x + cw), max(h, y + ch)); int metric = extents * extents + area; if (metric > best_metric) { continue; } - if (metric == best_metric && std::max(x, y) >= std::max(*best_x, *best_y)) { + if (metric == best_metric && max(x, y) >= max(*best_x, *best_y)) { // If metric is the same, pick the one closest to the origin. continue; } - if (canAddChart(bitmap, w, h, x, y, r)) { + if (atlasBitImage->canBlit(r == 1 ? *chartBitImageRotated : *chartBitImage, x, y)) { + result = true; best_metric = metric; *best_x = x; *best_y = y; @@ -7107,241 +7238,81 @@ private: } } done: - xaDebugAssert (best_metric != INT_MAX); + XA_DEBUG_ASSERT (best_metric != INT_MAX); + return result; } - void findChartLocation_random(const BitMap *bitmap, Vector2::Arg /*extents*/, int w, int h, int *best_x, int *best_y, int *best_w, int *best_h, int *best_r, int minTrialCount, bool blockAligned) + 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, bool resizableAtlas, bool allowRotate) { + bool result = false; const int BLOCK_SIZE = 4; int best_metric = INT_MAX; - for (int i = 0; i < minTrialCount || best_metric == INT_MAX; i++) { - int r = m_rand.getRange(1); - int x = m_rand.getRange(w + 1); // + 1 to extend atlas in case atlas full. We may want to use a higher number to increase probability of extending atlas. - int y = m_rand.getRange(h + 1); // + 1 to extend atlas in case atlas full. + for (int i = 0; i < minTrialCount; i++) { + int cw = chartBitImage->width(); + int ch = chartBitImage->height(); + int r = allowRotate ? 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. + int xRange = w + 1; + int yRange = h + 1; + if (!resizableAtlas) { + xRange = min(xRange, (int)atlasBitImage->width() - cw); + yRange = min(yRange, (int)atlasBitImage->height() - ch); + } + int x = m_rand.getRange(xRange); + int y = m_rand.getRange(yRange); if (blockAligned) { x = align(x, BLOCK_SIZE); y = align(y, BLOCK_SIZE); + if (!resizableAtlas && (x > (int)atlasBitImage->width() - cw || y > (int)atlasBitImage->height() - ch)) + continue; // Block alignment pushed the chart outside the atlas. } - int cw = bitmap->width(); - int ch = bitmap->height(); - if (r & 1) std::swap(cw, ch); // Early out. - int area = std::max(w, x + cw) * std::max(h, y + ch); + int area = max(w, x + cw) * max(h, y + ch); //int perimeter = max(w, x+cw) + max(h, y+ch); - int extents = std::max(std::max(w, x + cw), std::max(h, y + ch)); + int extents = max(max(w, x + cw), max(h, y + ch)); int metric = extents * extents + area; if (metric > best_metric) { continue; } - if (metric == best_metric && std::min(x, y) > std::min(*best_x, *best_y)) { + if (metric == best_metric && min(x, y) > min(*best_x, *best_y)) { // If metric is the same, pick the one closest to the origin. continue; } - if (canAddChart(bitmap, w, h, x, y, r)) { + if (atlasBitImage->canBlit(r == 1 ? *chartBitImageRotated : *chartBitImage, x, y)) { + result = true; best_metric = metric; *best_x = x; *best_y = y; *best_w = cw; *best_h = ch; - *best_r = r; + *best_r = allowRotate ? r : 0; if (area == w * h) { // Chart is completely inside, do not look at any other location. break; } } } + return result; } - void drawChartBitmapDilate(const Chart *chart, BitMap *bitmap, int padding) - { - const int w = bitmap->width(); - const int h = bitmap->height(); - const Vector2 extents = Vector2(float(w), float(h)); - // Rasterize chart faces, check that all bits are not set. - const uint32_t faceCount = chart->faceCount(); - for (uint32_t f = 0; f < faceCount; f++) { - const halfedge::Face *face = chart->chartMesh()->faceAt(f); - Vector2 vertices[4]; - uint32_t edgeCount = 0; - for (halfedge::Face::ConstEdgeIterator it(face->edges()); !it.isDone(); it.advance()) { - if (edgeCount < 4) { - vertices[edgeCount] = it.vertex()->tex + Vector2(0.5) + Vector2(float(padding), float(padding)); - } - edgeCount++; - } - if (edgeCount == 3) { - raster::drawTriangle(raster::Mode_Antialiased, extents, true, vertices, AtlasPacker::setBitsCallback, bitmap); - } else { - raster::drawQuad(raster::Mode_Antialiased, extents, true, vertices, AtlasPacker::setBitsCallback, bitmap); - } - } - // Expand chart by padding pixels. (dilation) - BitMap tmp(w, h); - for (int i = 0; i < padding; i++) { - tmp.clearAll(); - for (int y = 0; y < h; y++) { - for (int x = 0; x < w; x++) { - bool b = bitmap->bitAt(x, y); - if (!b) { - if (x > 0) { - b |= bitmap->bitAt(x - 1, y); - if (y > 0) b |= bitmap->bitAt(x - 1, y - 1); - if (y < h - 1) b |= bitmap->bitAt(x - 1, y + 1); - } - if (y > 0) b |= bitmap->bitAt(x, y - 1); - if (y < h - 1) b |= bitmap->bitAt(x, y + 1); - if (x < w - 1) { - b |= bitmap->bitAt(x + 1, y); - if (y > 0) b |= bitmap->bitAt(x + 1, y - 1); - if (y < h - 1) b |= bitmap->bitAt(x + 1, y + 1); - } - } - if (b) tmp.setBitAt(x, y); - } - } - std::swap(tmp, *bitmap); - } - } - - void drawChartBitmap(const Chart *chart, BitMap *bitmap, const Vector2 &scale, const Vector2 &offset) + void addChart(BitImage *atlasBitImage, const BitImage *chartBitImage, const BitImage *chartBitImageRotated, int atlas_w, int atlas_h, int offset_x, int offset_y, int r) { - const int w = bitmap->width(); - const int h = bitmap->height(); - const Vector2 extents = Vector2(float(w), float(h)); - static const Vector2 pad[4] = { - Vector2(-0.5, -0.5), - Vector2(0.5, -0.5), - Vector2(-0.5, 0.5), - Vector2(0.5, 0.5) - }; - // Rasterize 4 times to add proper padding. - for (int i = 0; i < 4; i++) { - // Rasterize chart faces, check that all bits are not set. - const uint32_t faceCount = chart->chartMesh()->faceCount(); - for (uint32_t f = 0; f < faceCount; f++) { - const halfedge::Face *face = chart->chartMesh()->faceAt(f); - Vector2 vertices[4]; - uint32_t edgeCount = 0; - for (halfedge::Face::ConstEdgeIterator it(face->edges()); !it.isDone(); it.advance()) { - if (edgeCount < 4) { - vertices[edgeCount] = it.vertex()->tex * scale + offset + pad[i]; - xaAssert(ftoi_ceil(vertices[edgeCount].x) >= 0); - xaAssert(ftoi_ceil(vertices[edgeCount].y) >= 0); - xaAssert(ftoi_ceil(vertices[edgeCount].x) <= w); - xaAssert(ftoi_ceil(vertices[edgeCount].y) <= h); - } - edgeCount++; - } - if (edgeCount == 3) { - raster::drawTriangle(raster::Mode_Antialiased, extents, /*enableScissors=*/true, vertices, AtlasPacker::setBitsCallback, bitmap); - } else { - raster::drawQuad(raster::Mode_Antialiased, extents, /*enableScissors=*/true, vertices, AtlasPacker::setBitsCallback, bitmap); - } - } - } - // Expand chart by padding pixels. (dilation) - BitMap tmp(w, h); - tmp.clearAll(); + XA_DEBUG_ASSERT(r == 0 || r == 1); + const BitImage *image = r == 0 ? chartBitImage : chartBitImageRotated; + const int w = image->width(); + const int h = image->height(); for (int y = 0; y < h; y++) { - for (int x = 0; x < w; x++) { - bool b = bitmap->bitAt(x, y); - if (!b) { - if (x > 0) { - b |= bitmap->bitAt(x - 1, y); - if (y > 0) b |= bitmap->bitAt(x - 1, y - 1); - if (y < h - 1) b |= bitmap->bitAt(x - 1, y + 1); - } - if (y > 0) b |= bitmap->bitAt(x, y - 1); - if (y < h - 1) b |= bitmap->bitAt(x, y + 1); - if (x < w - 1) { - b |= bitmap->bitAt(x + 1, y); - if (y > 0) b |= bitmap->bitAt(x + 1, y - 1); - if (y < h - 1) b |= bitmap->bitAt(x + 1, y + 1); - } - } - if (b) tmp.setBitAt(x, y); - } - } - std::swap(tmp, *bitmap); - } - - bool canAddChart(const BitMap *bitmap, int atlas_w, int atlas_h, int offset_x, int offset_y, int r) - { - xaDebugAssert(r == 0 || r == 1); - // Check whether the two bitmaps overlap. - const int w = bitmap->width(); - const int h = bitmap->height(); - if (r == 0) { - for (int y = 0; y < h; y++) { - int yy = y + offset_y; - if (yy >= 0) { - for (int x = 0; x < w; x++) { - int xx = x + offset_x; - if (xx >= 0) { - if (bitmap->bitAt(x, y)) { - if (xx < atlas_w && yy < atlas_h) { - if (m_bitmap.bitAt(xx, yy)) return false; - } - } - } - } - } - } - } else if (r == 1) { - for (int y = 0; y < h; y++) { - int xx = y + offset_x; - if (xx >= 0) { - for (int x = 0; x < w; x++) { - int yy = x + offset_y; - if (yy >= 0) { - if (bitmap->bitAt(x, y)) { - if (xx < atlas_w && yy < atlas_h) { - if (m_bitmap.bitAt(xx, yy)) return false; - } - } - } - } - } - } - } - return true; - } - - void addChart(const BitMap *bitmap, int atlas_w, int atlas_h, int offset_x, int offset_y, int r) - { - xaDebugAssert(r == 0 || r == 1); - // Check whether the two bitmaps overlap. - const int w = bitmap->width(); - const int h = bitmap->height(); - if (r == 0) { - for (int y = 0; y < h; y++) { - int yy = y + offset_y; - if (yy >= 0) { - for (int x = 0; x < w; x++) { - int xx = x + offset_x; - if (xx >= 0) { - if (bitmap->bitAt(x, y)) { - if (xx < atlas_w && yy < atlas_h) { - xaDebugAssert(m_bitmap.bitAt(xx, yy) == false); - m_bitmap.setBitAt(xx, yy); - } - } - } - } - } - } - } else if (r == 1) { - for (int y = 0; y < h; y++) { - int xx = y + offset_x; - if (xx >= 0) { - for (int x = 0; x < w; x++) { - int yy = x + offset_y; - if (yy >= 0) { - if (bitmap->bitAt(x, y)) { - if (xx < atlas_w && yy < atlas_h) { - xaDebugAssert(m_bitmap.bitAt(xx, yy) == false); - m_bitmap.setBitAt(xx, yy); - } + int yy = y + offset_y; + if (yy >= 0) { + for (int x = 0; x < w; x++) { + int xx = x + offset_x; + if (xx >= 0) { + if (image->bitAt(x, y)) { + if (xx < atlas_w && yy < atlas_h) { + XA_DEBUG_ASSERT(atlasBitImage->bitAt(xx, yy) == false); + atlasBitImage->setBitAt(xx, yy); } } } @@ -7350,458 +7321,992 @@ private: } } - static bool setBitsCallback(void *param, int x, int y, Vector3::Arg, Vector3::Arg, Vector3::Arg, float area) + struct DrawTriangleCallbackArgs { - BitMap *bitmap = (BitMap * )param; - if (area > 0.0) { - bitmap->setBitAt(x, y); - } - return true; - } + BitImage *chartBitImage; + BitImage *chartBitImageRotated; + }; - // Compute the convex hull using Graham Scan. - static void convexHull(const std::vector<Vector2> &input, std::vector<Vector2> &output, float epsilon) + static bool drawTriangleCallback(void *param, int x, int y) { - const uint32_t inputCount = input.size(); - std::vector<float> coords(inputCount); - for (uint32_t i = 0; i < inputCount; i++) { - coords[i] = input[i].x; - } - RadixSort radix; - radix.sort(coords); - const uint32_t *ranks = radix.ranks(); - std::vector<Vector2> top; - top.reserve(inputCount); - std::vector<Vector2> bottom; - bottom.reserve(inputCount); - Vector2 P = input[ranks[0]]; - Vector2 Q = input[ranks[inputCount - 1]]; - float topy = std::max(P.y, Q.y); - float boty = std::min(P.y, Q.y); - for (uint32_t i = 0; i < inputCount; i++) { - Vector2 p = input[ranks[i]]; - if (p.y >= boty) top.push_back(p); - } - for (uint32_t i = 0; i < inputCount; i++) { - Vector2 p = input[ranks[inputCount - 1 - i]]; - if (p.y <= topy) bottom.push_back(p); - } - // Filter top list. - output.clear(); - output.push_back(top[0]); - output.push_back(top[1]); - for (uint32_t i = 2; i < top.size(); ) { - Vector2 a = output[output.size() - 2]; - Vector2 b = output[output.size() - 1]; - Vector2 c = top[i]; - float area = triangleArea(a, b, c); - if (area >= -epsilon) { - output.pop_back(); - } - if (area < -epsilon || output.size() == 1) { - output.push_back(c); - i++; - } - } - uint32_t top_count = output.size(); - output.push_back(bottom[1]); - // Filter bottom list. - for (uint32_t i = 2; i < bottom.size(); ) { - Vector2 a = output[output.size() - 2]; - Vector2 b = output[output.size() - 1]; - Vector2 c = bottom[i]; - float area = triangleArea(a, b, c); - if (area >= -epsilon) { - output.pop_back(); - } - if (area < -epsilon || output.size() == top_count) { - output.push_back(c); - i++; - } - } - // Remove duplicate element. - xaDebugAssert(output.front() == output.back()); - output.pop_back(); + auto args = (DrawTriangleCallbackArgs *)param; + args->chartBitImage->setBitAt(x, y); + if (args->chartBitImageRotated) + args->chartBitImageRotated->setBitAt(y, x); + return true; } - // This should compute convex hull and use rotating calipers to find the best box. Currently it uses a brute force method. - static void computeBoundingBox(Chart *chart, Vector2 *majorAxis, Vector2 *minorAxis, Vector2 *minCorner, Vector2 *maxCorner) - { - // Compute list of boundary points. - std::vector<Vector2> points; - points.reserve(16); - halfedge::Mesh *mesh = chart->chartMesh(); - const uint32_t vertexCount = mesh->vertexCount(); - for (uint32_t i = 0; i < vertexCount; i++) { - halfedge::Vertex *vertex = mesh->vertexAt(i); - if (vertex->isBoundary()) { - points.push_back(vertex->tex); - } - } - xaDebugAssert(points.size() > 0); - std::vector<Vector2> hull; - convexHull(points, 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; - Vector2 best_max; - Vector2 best_axis; - const uint32_t hullCount = hull.size(); - for (uint32_t i = 0, j = hullCount - 1; i < hullCount; j = i, i++) { - if (equal(hull[i], hull[j])) { - continue; - } - Vector2 axis = normalize(hull[i] - hull[j], 0.0f); - xaDebugAssert(isFinite(axis)); - // Compute bounding box. - Vector2 box_min(FLT_MAX, FLT_MAX); - Vector2 box_max(-FLT_MAX, -FLT_MAX); - for (uint32_t v = 0; v < hullCount; v++) { - Vector2 point = hull[v]; - float x = dot(axis, point); - if (x < box_min.x) box_min.x = x; - if (x > box_max.x) box_max.x = x; - float y = dot(Vector2(-axis.y, axis.x), point); - if (y < box_min.y) box_min.y = y; - if (y > box_max.y) box_max.y = y; - } - // Compute box area. - float area = (box_max.x - box_min.x) * (box_max.y - box_min.y); - if (area < best_area) { - best_area = area; - best_min = box_min; - best_max = box_max; - best_axis = axis; - } - } - // Consider all points, not only boundary points, in case the input chart is malformed. - for (uint32_t i = 0; i < vertexCount; i++) { - halfedge::Vertex *vertex = mesh->vertexAt(i); - Vector2 point = vertex->tex; - float x = dot(best_axis, point); - if (x < best_min.x) best_min.x = x; - if (x > best_max.x) best_max.x = x; - float y = dot(Vector2(-best_axis.y, best_axis.x), point); - if (y < best_min.y) best_min.y = y; - if (y > best_max.y) best_max.y = y; - } - *majorAxis = best_axis; - *minorAxis = Vector2(-best_axis.y, best_axis.x); - *minCorner = best_min; - *maxCorner = best_max; - } - - Atlas *m_atlas; - BitMap m_bitmap; + Array<AtlasImage *> m_atlasImages; + Array<float> m_utilization; + Array<BitImage *> m_bitImages; + BoundingBox2D m_boundingBox; + Array<Chart *> m_charts; RadixSort m_radix; - uint32_t m_width; - uint32_t m_height; - MTRand m_rand; - std::vector<std::vector<Vector2> > m_originalChartUvs; + uint32_t m_width = 0; + uint32_t m_height = 0; + float m_texelsPerUnit = 0.0f; + KISSRng m_rand; }; -} // namespace param +} // namespace pack } // namespace internal -struct Atlas -{ - internal::param::Atlas atlas; - std::vector<internal::halfedge::Mesh *> heMeshes; - uint32_t width = 0; - uint32_t height = 0; - OutputMesh **outputMeshes = NULL; +struct Context +{ + Atlas atlas; + uint32_t meshCount = 0; + internal::Progress *addMeshProgress = nullptr; + internal::TaskGroupHandle addMeshTaskGroup; + internal::param::Atlas paramAtlas; + ProgressFunc progressFunc = nullptr; + void *progressUserData = nullptr; + internal::TaskScheduler *taskScheduler; + internal::Array<internal::UvMesh *> uvMeshes; + internal::Array<internal::UvMeshInstance *> uvMeshInstances; }; -void SetPrint(PrintFunc print) +Atlas *Create() { - internal::s_print = print; + 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; } -Atlas *Create() -{ - Atlas *atlas = new Atlas(); - return atlas; +static void DestroyOutputMeshes(Context *ctx) +{ + if (!ctx->atlas.meshes) + return; + for (int i = 0; i < (int)ctx->atlas.meshCount; i++) { + Mesh &mesh = ctx->atlas.meshes[i]; + for (uint32_t j = 0; j < mesh.chartCount; j++) { + if (mesh.chartArray[j].indexArray) + XA_FREE(mesh.chartArray[j].indexArray); + } + if (mesh.chartArray) + XA_FREE(mesh.chartArray); + if (mesh.vertexArray) + XA_FREE(mesh.vertexArray); + if (mesh.indexArray) + XA_FREE(mesh.indexArray); + } + if (ctx->atlas.meshes) + XA_FREE(ctx->atlas.meshes); + ctx->atlas.meshes = nullptr; } void Destroy(Atlas *atlas) { - xaAssert(atlas); - for (int i = 0; i < (int)atlas->heMeshes.size(); i++) { - delete atlas->heMeshes[i]; - if (atlas->outputMeshes) { - OutputMesh *outputMesh = atlas->outputMeshes[i]; - for (uint32_t j = 0; j < outputMesh->chartCount; j++) - delete [] outputMesh->chartArray[j].indexArray; - delete [] outputMesh->chartArray; - delete [] outputMesh->vertexArray; - delete [] outputMesh->indexArray; - delete outputMesh; - } - } - delete [] atlas->outputMeshes; - delete atlas; + XA_DEBUG_ASSERT(atlas); + Context *ctx = (Context *)atlas; + if (atlas->utilization) + XA_FREE(atlas->utilization); + if (atlas->image) + XA_FREE(atlas->image); + DestroyOutputMeshes(ctx); + if (ctx->addMeshProgress) { + ctx->addMeshProgress->cancel = true; + AddMeshJoin(atlas); // frees addMeshProgress + } + ctx->taskScheduler->~TaskScheduler(); + XA_FREE(ctx->taskScheduler); + 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++) { + mesh->charts[j]->~UvMeshChart(); + XA_FREE(mesh->charts[j]); + } + mesh->~UvMesh(); + XA_FREE(mesh); + } + for (uint32_t i = 0; i < ctx->uvMeshInstances.size(); i++) { + internal::UvMeshInstance *mesh = ctx->uvMeshInstances[i]; + mesh->~UvMeshInstance(); + XA_FREE(mesh); + } + ctx->~Context(); + XA_FREE(ctx); +#if XA_DEBUG_HEAP + internal::ReportLeaks(); +#endif } -static internal::Vector3 DecodePosition(const InputMesh &mesh, uint32_t index) +struct AddMeshTaskArgs { - xaAssert(mesh.vertexPositionData); - return *((const internal::Vector3 *)&((const uint8_t *)mesh.vertexPositionData)[mesh.vertexPositionStride * index]); + Context *ctx; + internal::Mesh *mesh; +}; + +static void runAddMeshTask(void *userData) +{ + XA_PROFILE_START(addMesh) + 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) + if (progress->cancel) + goto cleanup; + XA_PROFILE_START(addMeshCreateFaceGroups) + mesh->createFaceGroups(); + XA_PROFILE_END(addMeshCreateFaceGroups) + if (progress->cancel) + goto cleanup; + XA_PROFILE_START(addMeshCreateBoundaries) + mesh->createBoundaries(); + XA_PROFILE_END(addMeshCreateBoundaries) + if (progress->cancel) + goto cleanup; +#if XA_DEBUG_EXPORT_OBJ_SOURCE_MESHES + char filename[256]; + XA_SPRINTF(filename, sizeof(filename), "debug_mesh_%03u.obj", mesh->id()); + FILE *file; + XA_FOPEN(file, filename, "w"); + if (file) { + mesh->writeObjVertices(file); + // groups + uint32_t numGroups = 0; + for (uint32_t i = 0; i < mesh->faceGroupCount(); i++) { + if (mesh->faceGroupAt(i) != UINT32_MAX) + numGroups = internal::max(numGroups, mesh->faceGroupAt(i) + 1); + } + for (uint32_t i = 0; i < numGroups; i++) { + fprintf(file, "o group_%04d\n", i); + fprintf(file, "s off\n"); + for (uint32_t f = 0; f < mesh->faceGroupCount(); f++) { + if (mesh->faceGroupAt(f) == i) + mesh->writeObjFace(file, f); + } + } + fprintf(file, "o group_ignored\n"); + fprintf(file, "s off\n"); + for (uint32_t f = 0; f < mesh->faceGroupCount(); f++) { + if (mesh->faceGroupAt(f) == UINT32_MAX) + mesh->writeObjFace(file, f); + } + mesh->writeObjBoundaryEges(file); + fclose(file); + } +#endif + XA_PROFILE_START(addMeshCreateChartGroupsConcurrent) + args->ctx->paramAtlas.addMesh(args->ctx->taskScheduler, mesh); // addMesh is thread safe + XA_PROFILE_END(addMeshCreateChartGroupsConcurrent) + if (progress->cancel) + goto cleanup; + progress->value++; + progress->update(); +cleanup: + mesh->~Mesh(); + XA_FREE(mesh); + args->~AddMeshTaskArgs(); + XA_FREE(args); + XA_PROFILE_END(addMesh) } -static internal::Vector3 DecodeNormal(const InputMesh &mesh, uint32_t index) +static internal::Vector3 DecodePosition(const MeshDecl &meshDecl, uint32_t index) { - xaAssert(mesh.vertexNormalData); - return *((const internal::Vector3 *)&((const uint8_t *)mesh.vertexNormalData)[mesh.vertexNormalStride * 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::Vector2 DecodeUv(const InputMesh &mesh, uint32_t index) +static internal::Vector3 DecodeNormal(const MeshDecl &meshDecl, uint32_t index) { - xaAssert(mesh.vertexUvData); - return *((const internal::Vector2 *)&((const uint8_t *)mesh.vertexUvData)[mesh.vertexUvStride * 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 uint32_t DecodeIndex(IndexFormat::Enum format, const void *indexData, uint32_t i) +static internal::Vector2 DecodeUv(const MeshDecl &meshDecl, uint32_t index) { - if (format == IndexFormat::HalfFloat) - return (uint32_t)((const uint16_t *)indexData)[i]; - return ((const uint32_t *)indexData)[i]; + XA_DEBUG_ASSERT(meshDecl.vertexUvData); + XA_DEBUG_ASSERT(meshDecl.vertexUvStride > 0); + return *((const internal::Vector2 *)&((const uint8_t *)meshDecl.vertexUvData)[meshDecl.vertexUvStride * index]); } -static float EdgeLength(internal::Vector3 pos1, internal::Vector3 pos2) +static uint32_t DecodeIndex(IndexFormat::Enum format, const void *indexData, int32_t offset, uint32_t i) { - return internal::length(pos2 - pos1); + 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 AddMesh(Atlas *atlas, const InputMesh &mesh, bool useColocalVertices) +AddMeshError::Enum AddMesh(Atlas *atlas, const MeshDecl &meshDecl, uint32_t meshCountHint) { - xaAssert(atlas); - AddMeshError error; - error.code = AddMeshErrorCode::Success; - error.face = error.index0 = error.index1 = UINT32_MAX; - // Expecting triangle faces. - if ((mesh.indexCount % 3) != 0) - { - error.code = AddMeshErrorCode::InvalidIndexCount; - return error; - } - // Check if any index is out of range. - for (uint32_t j = 0; j < mesh.indexCount; j++) { - const uint32_t index = DecodeIndex(mesh.indexFormat, mesh.indexData, j); - if (index < 0 || index >= mesh.vertexCount) { - error.code = AddMeshErrorCode::IndexOutOfRange; - error.index0 = index; - return error; - } - } - // Build half edge mesh. - internal::halfedge::Mesh *heMesh = new internal::halfedge::Mesh; - std::vector<uint32_t> canonicalMap; - canonicalMap.reserve(mesh.vertexCount); - for (uint32_t i = 0; i < mesh.vertexCount; i++) { - internal::halfedge::Vertex *vertex = heMesh->addVertex(DecodePosition(mesh, i)); - if (mesh.vertexNormalData) - vertex->nor = DecodeNormal(mesh, i); - if (mesh.vertexUvData) - vertex->tex = DecodeUv(mesh, i); - // Link colocals. You probably want to do this more efficiently! Sort by one axis or use a hash or grid. - uint32_t firstColocal = i; - if (useColocalVertices) { - for (uint32_t j = 0; j < i; j++) { - if (vertex->pos != DecodePosition(mesh, j)) - continue; -#if 0 - if (mesh.vertexNormalData && vertex->nor != DecodeNormal(mesh, j)) - continue; + XA_DEBUG_ASSERT(atlas); + if (!atlas) { + XA_PRINT_WARNING("AddMesh: atlas is null.\n"); + return AddMeshError::Error; + } + Context *ctx = (Context *)atlas; + if (!ctx->uvMeshes.isEmpty()) { + XA_PRINT_WARNING("AddMesh: Meshes and UV meshes cannot be added to the same atlas.\n"); + return AddMeshError::Error; + } + // Don't know how many times AddMesh will be called, so progress needs to adjusted each time. + if (!ctx->addMeshProgress) { + ctx->addMeshProgress = XA_NEW(internal::MemTag::Default, internal::Progress, ProgressCategory::AddMesh, ctx->progressFunc, ctx->progressUserData, 1); +#if XA_PROFILE + internal::s_profile.addMeshConcurrent = clock(); #endif - if (mesh.vertexUvData && vertex->tex != DecodeUv(mesh, j)) - continue; - firstColocal = j; - break; - } - } - canonicalMap.push_back(firstColocal); } - heMesh->linkColocalsWithCanonicalMap(canonicalMap); - for (uint32_t i = 0; i < mesh.indexCount / 3; i++) { + else { + ctx->addMeshProgress->setMaxValue(internal::max(ctx->meshCount + 1, meshCountHint)); + } + bool decoded = (meshDecl.indexCount <= 0); + uint32_t indexCount = decoded ? meshDecl.vertexCount : meshDecl.indexCount; + XA_PRINT("Adding mesh %d: %u vertices, %u triangles\n", ctx->meshCount, meshDecl.vertexCount, indexCount / 3); + XA_PROFILE_START(addMesh) + // Expecting triangle faces. + if ((indexCount % 3) != 0) + return AddMeshError::InvalidIndexCount; + if (!decoded) { + // 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 meshFlags = internal::MeshFlags::HasFaceGroups | internal::MeshFlags::HasIgnoredFaces; + if (meshDecl.vertexNormalData) + meshFlags |= internal::MeshFlags::HasNormals; + internal::Mesh *mesh = XA_NEW(internal::MemTag::Mesh, internal::Mesh, meshDecl.epsilon, meshDecl.vertexCount, indexCount / 3, meshFlags, ctx->meshCount); + for (uint32_t i = 0; i < meshDecl.vertexCount; i++) { + internal::Vector3 normal(0.0f); + internal::Vector2 texcoord(0.0f); + if (meshDecl.vertexNormalData) + normal = DecodeNormal(meshDecl, i); + if (meshDecl.vertexUvData) + texcoord = DecodeUv(meshDecl, i); + mesh->addVertex(DecodePosition(meshDecl, i), normal, texcoord); + } + for (uint32_t i = 0; i < indexCount / 3; i++) { uint32_t tri[3]; for (int j = 0; j < 3; j++) - tri[j] = DecodeIndex(mesh.indexFormat, mesh.indexData, i * 3 + j); - // Check for zero length edges. + tri[j] = decoded ? i * 3 + j : DecodeIndex(meshDecl.indexFormat, meshDecl.indexData, meshDecl.indexOffset, i * 3 + j); + bool ignore = false; + // Check for degenerate or zero length edges. for (int j = 0; j < 3; j++) { - const uint32_t edges[6] = { 0, 1, 1, 2, 2, 0 }; - const uint32_t index1 = tri[edges[j * 2 + 0]]; - const uint32_t index2 = tri[edges[j * 2 + 1]]; - const internal::Vector3 pos1 = DecodePosition(mesh, index1); - const internal::Vector3 pos2 = DecodePosition(mesh, index2); - if (EdgeLength(pos1, pos2) <= 0.0f) { - delete heMesh; - error.code = AddMeshErrorCode::ZeroLengthEdge; - error.face = i; - error.index0 = index1; - error.index1 = index2; - return error; - } - } - // Check for zero area faces. - { - const internal::Vector3 a = DecodePosition(mesh, tri[0]); - const internal::Vector3 b = DecodePosition(mesh, tri[1]); - const internal::Vector3 c = DecodePosition(mesh, tri[2]); - const float area = internal::length(internal::cross(b - a, c - a)) * 0.5f; - if (area <= 0.0f) { - delete heMesh; - error.code = AddMeshErrorCode::ZeroAreaFace; - error.face = i; - return error; - } - } - internal::halfedge::Face *face = heMesh->addFace(tri[0], tri[1], tri[2]); - - // -- GODOT start -- - if (!face && heMesh->errorCode == internal::halfedge::Mesh::ErrorCode::AlreadyAddedEdge) { - //there is still hope for this, no reason to not add, at least add as separate - face = heMesh->addUniqueFace(tri[0], tri[1], tri[2]); - } - // -- GODOT end -- - - if (!face) { - if (heMesh->errorCode == internal::halfedge::Mesh::ErrorCode::AlreadyAddedEdge) - error.code = AddMeshErrorCode::AlreadyAddedEdge; - else if (heMesh->errorCode == internal::halfedge::Mesh::ErrorCode::DegenerateColocalEdge) - error.code = AddMeshErrorCode::DegenerateColocalEdge; - else if (heMesh->errorCode == internal::halfedge::Mesh::ErrorCode::DegenerateEdge) - error.code = AddMeshErrorCode::DegenerateEdge; - else if (heMesh->errorCode == internal::halfedge::Mesh::ErrorCode::DuplicateEdge) - error.code = AddMeshErrorCode::DuplicateEdge; - error.face = i; - error.index0 = heMesh->errorIndex0; - error.index1 = heMesh->errorIndex1; - delete heMesh; - return error; - } - if (mesh.faceMaterialData) - face->material = mesh.faceMaterialData[i]; - } - heMesh->linkBoundary(); - atlas->heMeshes.push_back(heMesh); - return error; + const uint32_t index1 = tri[j]; + const uint32_t index2 = tri[(j + 1) % 3]; + if (index1 == index2) { + ignore = true; + XA_PRINT(" Degenerate edge: index %d, index %d\n", index1, index2); + break; + } + const internal::Vector3 &pos1 = mesh->position(index1); + const internal::Vector3 &pos2 = mesh->position(index2); + if (internal::length(pos2 - pos1) <= 0.0f) { + ignore = true; + XA_PRINT(" Zero length edge: index %d position (%g %g %g), index %d position (%g %g %g)\n", index1, pos1.x, pos1.y, pos1.z, index2, pos2.x, pos2.y, pos2.z); + 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. Don't bother if a degenerate or zero length edge was already detected. + float area = 0.0f; + if (!ignore) { + area = internal::length(internal::cross(b - a, c - a)) * 0.5f; + if (area <= internal::kAreaEpsilon) { + ignore = true; + XA_PRINT(" Zero area face: %d, indices (%d %d %d), area is %f\n", i, tri[0], tri[1], tri[2], area); + } + } + if (!ignore) { + if (internal::equal(a, b, meshDecl.epsilon) || internal::equal(a, c, meshDecl.epsilon) || internal::equal(b, c, meshDecl.epsilon)) { + ignore = true; + XA_PRINT(" Degenerate face: %d, area is %f\n", i, area); + } + } + if (meshDecl.faceIgnoreData && meshDecl.faceIgnoreData[i]) + ignore = true; + mesh->addFace(tri[0], tri[1], tri[2], ignore); + } + AddMeshTaskArgs *taskArgs = XA_NEW(internal::MemTag::Default, AddMeshTaskArgs); // The task frees this. + taskArgs->ctx = ctx; + taskArgs->mesh = mesh; + internal::Task task; + task.userData = taskArgs; + task.func = runAddMeshTask; + ctx->taskScheduler->run(&ctx->addMeshTaskGroup, task); + ctx->meshCount++; + XA_PROFILE_END(addMesh) + return AddMeshError::Success; +} + +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 XA_PROFILE + XA_PRINT("Added %u meshes\n", ctx->meshCount); + internal::s_profile.addMeshConcurrent = clock() - internal::s_profile.addMeshConcurrent; +#endif + XA_PROFILE_PRINT(" Total (concurrent): ", addMeshConcurrent) + XA_PROFILE_PRINT(" Total: ", addMesh) + XA_PROFILE_PRINT(" Create colocals: ", addMeshCreateColocals) + XA_PROFILE_PRINT(" Create face groups: ", addMeshCreateFaceGroups) + XA_PROFILE_PRINT(" Create boundaries: ", addMeshCreateBoundaries) + XA_PROFILE_PRINT(" Create chart groups (concurrent): ", addMeshCreateChartGroupsConcurrent) + XA_PROFILE_PRINT(" Create chart groups: ", addMeshCreateChartGroups) + XA_PRINT_MEM_USAGE +} + +struct EdgeKey +{ + EdgeKey() {} + EdgeKey(const EdgeKey &k) : v0(k.v0), v1(k.v1) {} + EdgeKey(uint32_t v0, uint32_t v1) : v0(v0), v1(v1) {} + + void operator=(const EdgeKey &k) + { + v0 = k.v0; + v1 = k.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) +{ + XA_DEBUG_ASSERT(atlas); + if (!atlas) { + XA_PRINT_WARNING("AddUvMesh: atlas is null.\n"); + return AddMeshError::Error; + } + Context *ctx = (Context *)atlas; + if (ctx->meshCount > 0) { + 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; + 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) { + // 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); + if (index >= decl.vertexCount) + return AddMeshError::IndexOutOfRange; + } + } + internal::UvMeshInstance *meshInstance = XA_NEW(internal::MemTag::Default, internal::UvMeshInstance); + meshInstance->texcoords.resize(decl.vertexCount); + for (uint32_t i = 0; i < decl.vertexCount; i++) + meshInstance->texcoords[i] = *((const internal::Vector2 *)&((const uint8_t *)decl.vertexUvData)[decl.vertexStride * i]); + meshInstance->rotateCharts = decl.rotateCharts; + // 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]; + break; + } + } + if (!mesh) { + // Copy geometry to mesh. + meshInstance->mesh = mesh = XA_NEW(internal::MemTag::Default, internal::UvMesh); + mesh->decl = decl; + 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, uint32_t> vertexToFaceMap(internal::MemTag::Default, indexCount); + const uint32_t faceCount = indexCount / 3; + for (uint32_t i = 0; i < indexCount; i++) + vertexToFaceMap.add(meshInstance->texcoords[mesh->indices[i]], i / 3); + internal::BitArray faceAssigned(faceCount); + faceAssigned.clearAll(); + internal::Array<uint32_t> chartFaces; + for (uint32_t f = 0; f < faceCount; f++) { + if (faceAssigned.bitAt(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.setBitAt(f); + chartFaces.clear(); + chartFaces.push_back(f); + for (;;) { + bool newFaceAssigned = false; + const uint32_t faceCount2 = chartFaces.size(); + for (uint32_t f2 = 0; f2 < faceCount2; f2++) { + const uint32_t face = chartFaces[f2]; + for (uint32_t i = 0; i < 3; i++) { + const internal::Vector2 &texcoord = meshInstance->texcoords[meshInstance->mesh->indices[face * 3 + i]]; + uint32_t mapFaceIndex = vertexToFaceMap.get(texcoord); + while (mapFaceIndex != UINT32_MAX) { + const uint32_t face2 = vertexToFaceMap.value(mapFaceIndex); + // Materials must match. + if (!faceAssigned.bitAt(face2) && (!decl.faceMaterialData || decl.faceMaterialData[face] == decl.faceMaterialData[face2])) { + faceAssigned.setBitAt(face2); + chartFaces.push_back(face2); + newFaceAssigned = true; + } + mapFaceIndex = vertexToFaceMap.getNext(mapFaceIndex); + } + } + } + if (!newFaceAssigned) + break; + } + for (uint32_t i = 0; i < chartFaces.size(); i++) { + for (uint32_t j = 0; j < 3; j++) { + const uint32_t vertex = meshInstance->mesh->indices[chartFaces[i] * 3 + j]; + chart->indices.push_back(vertex); + mesh->vertexToChartMap[vertex] = mesh->charts.size(); + } + } + mesh->charts.push_back(chart); + } + ctx->uvMeshes.push_back(mesh); + } else { + XA_PRINT(" instance of a previous UV mesh\n"); + } + XA_PRINT(" %u charts\n", meshInstance->mesh->charts.size()); + ctx->uvMeshInstances.push_back(meshInstance); + return AddMeshError::Success; +} + +void ComputeCharts(Atlas *atlas, ChartOptions chartOptions) +{ + 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->meshCount == 0) { + XA_PRINT_WARNING("ComputeCharts: No meshes. Call AddMesh first.\n"); + return; + } + XA_PRINT("Computing charts\n"); + uint32_t chartCount = 0, chartsWithHolesCount = 0, holesCount = 0, chartsWithTJunctionsCount = 0, tJunctionsCount = 0; + XA_PROFILE_START(computeChartsConcurrent) + if (!ctx->paramAtlas.computeCharts(ctx->taskScheduler, chartOptions, ctx->progressFunc, ctx->progressUserData)) { + XA_PRINT(" Cancelled by user\n"); + return; + } + XA_PROFILE_END(computeChartsConcurrent) + // Count charts and print warnings. + for (uint32_t i = 0; i < ctx->meshCount; i++) { + for (uint32_t j = 0; j < ctx->paramAtlas.chartGroupCount(i); j++) { + const internal::param::ChartGroup *chartGroup = ctx->paramAtlas.chartGroupAt(i, j); + if (chartGroup->isVertexMap()) + continue; + for (uint32_t k = 0; k < chartGroup->chartCount(); k++) { + const internal::param::Chart *chart = chartGroup->chartAt(k); + 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); + if (!chart->isDisk()) + XA_PRINT_WARNING(" Chart %u (mesh %u, group %u, id %u): doesn't have disk topology\n", chartCount, i, j, k); + holesCount += chart->closedHolesCount(); + if (chart->closedHolesCount() > 0) + chartsWithHolesCount++; + tJunctionsCount += chart->fixedTJunctionsCount(); + if (chart->fixedTJunctionsCount() > 0) + chartsWithTJunctionsCount++; + chartCount++; + } + } + } + if (holesCount > 0) + XA_PRINT(" Closed %u holes in %u charts\n", holesCount, chartsWithHolesCount); + if (tJunctionsCount > 0) + XA_PRINT(" Fixed %u t-junctions in %u charts\n", tJunctionsCount, chartsWithTJunctionsCount); + XA_PRINT(" %u charts\n", chartCount); + XA_PROFILE_PRINT(" Total (concurrent): ", computeChartsConcurrent) + XA_PROFILE_PRINT(" Total: ", computeCharts) + XA_PROFILE_PRINT(" Atlas builder: ", atlasBuilder) + XA_PROFILE_PRINT(" Init: ", atlasBuilderInit) + XA_PROFILE_PRINT(" Create initial charts: ", atlasBuilderCreateInitialCharts) + XA_PROFILE_PRINT(" Grow charts: ", atlasBuilderGrowCharts) + XA_PROFILE_PRINT(" Merge charts: ", atlasBuilderMergeCharts) + XA_PROFILE_PRINT(" Create chart meshes: ", createChartMeshes) + XA_PROFILE_PRINT(" Fix t-junctions: ", fixChartMeshTJunctions); + XA_PROFILE_PRINT(" Close holes: ", closeChartMeshHoles) + XA_PRINT_MEM_USAGE } -void Generate(Atlas *atlas, CharterOptions charterOptions, PackerOptions packerOptions) -{ - xaAssert(atlas); - xaAssert(packerOptions.texelArea > 0); - // Chart meshes. - for (int i = 0; i < (int)atlas->heMeshes.size(); i++) { - std::vector<uint32_t> uncharted_materials; - atlas->atlas.computeCharts(atlas->heMeshes[i], charterOptions, uncharted_materials); - } - atlas->atlas.parameterizeCharts(); - internal::param::AtlasPacker packer(&atlas->atlas); - packer.packCharts(packerOptions); - //float utilization = return packer.computeAtlasUtilization(); - atlas->width = packer.getWidth(); - atlas->height = packer.getHeight(); - // Build output meshes. - atlas->outputMeshes = new OutputMesh*[atlas->heMeshes.size()]; - for (int i = 0; i < (int)atlas->heMeshes.size(); i++) { - const internal::halfedge::Mesh *heMesh = atlas->heMeshes[i]; - OutputMesh *outputMesh = atlas->outputMeshes[i] = new OutputMesh; - const internal::param::MeshCharts *charts = atlas->atlas.meshAt(i); - // Vertices. - outputMesh->vertexCount = charts->vertexCount(); - outputMesh->vertexArray = new OutputVertex[outputMesh->vertexCount]; - for (uint32_t i = 0; i < charts->chartCount(); i++) { - const internal::param::Chart *chart = charts->chartAt(i); - const uint32_t vertexOffset = charts->vertexCountBeforeChartAt(i); - for (uint32_t v = 0; v < chart->vertexCount(); v++) { - OutputVertex &output_vertex = outputMesh->vertexArray[vertexOffset + v]; - output_vertex.xref = chart->mapChartVertexToOriginalVertex(v); - internal::Vector2 uv = chart->chartMesh()->vertexAt(v)->tex; - output_vertex.uv[0] = uv.x; - output_vertex.uv[1] = uv.y; - } - } - // Indices. - outputMesh->indexCount = heMesh->faceCount() * 3; - outputMesh->indexArray = new uint32_t[outputMesh->indexCount]; - for (uint32_t f = 0; f < heMesh->faceCount(); f++) { - const uint32_t c = charts->faceChartAt(f); - const uint32_t i = charts->faceIndexWithinChartAt(f); - const uint32_t vertexOffset = charts->vertexCountBeforeChartAt(c); - const internal::param::Chart *chart = charts->chartAt(c); - xaDebugAssert(i < chart->chartMesh()->faceCount()); - xaDebugAssert(chart->faceAt(i) == f); - const internal::halfedge::Face *face = chart->chartMesh()->faceAt(i); - const internal::halfedge::Edge *edge = face->edge; - outputMesh->indexArray[3 * f + 0] = vertexOffset + edge->vertex->id; - outputMesh->indexArray[3 * f + 1] = vertexOffset + edge->next->vertex->id; - outputMesh->indexArray[3 * f + 2] = vertexOffset + edge->next->next->vertex->id; - } - // Charts. - outputMesh->chartCount = charts->chartCount(); - outputMesh->chartArray = new OutputChart[outputMesh->chartCount]; - for (uint32_t i = 0; i < charts->chartCount(); i++) { - OutputChart *outputChart = &outputMesh->chartArray[i]; - const internal::param::Chart *chart = charts->chartAt(i); - const uint32_t vertexOffset = charts->vertexCountBeforeChartAt(i); - const internal::halfedge::Mesh *mesh = chart->chartMesh(); - outputChart->indexCount = mesh->faceCount() * 3; - outputChart->indexArray = new uint32_t[outputChart->indexCount]; - for (uint32_t j = 0; j < mesh->faceCount(); j++) { - const internal::halfedge::Face *face = mesh->faceAt(j); - const internal::halfedge::Edge *edge = face->edge; - outputChart->indexArray[3 * j + 0] = vertexOffset + edge->vertex->id; - outputChart->indexArray[3 * j + 1] = vertexOffset + edge->next->vertex->id; - outputChart->indexArray[3 * j + 2] = vertexOffset + edge->next->next->vertex->id; +void ParameterizeCharts(Atlas *atlas, ParameterizeFunc func) +{ + 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"); + return; + } + atlas->atlasCount = 0; + atlas->height = 0; + atlas->texelsPerUnit = 0; + atlas->width = 0; + if (atlas->utilization) { + XA_FREE(atlas->utilization); + atlas->utilization = nullptr; + } + if (atlas->image) { + XA_FREE(atlas->image); + atlas->image = nullptr; + } + DestroyOutputMeshes(ctx); + XA_PRINT("Parameterizing charts\n"); + XA_PROFILE_START(parameterizeChartsConcurrent) + if (!ctx->paramAtlas.parameterizeCharts(ctx->taskScheduler, func, ctx->progressFunc, ctx->progressUserData)) { + XA_PRINT(" Cancelled by user\n"); + return; + } + XA_PROFILE_END(parameterizeChartsConcurrent) + uint32_t chartCount = 0, orthoChartsCount = 0, planarChartsCount = 0, chartsAddedCount = 0, chartsDeletedCount = 0; + for (uint32_t i = 0; i < ctx->meshCount; i++) { + for (uint32_t j = 0; j < ctx->paramAtlas.chartGroupCount(i); j++) { + const internal::param::ChartGroup *chartGroup = ctx->paramAtlas.chartGroupAt(i, j); + if (chartGroup->isVertexMap()) + continue; + for (uint32_t k = 0; k < chartGroup->chartCount(); k++) { + const internal::param::Chart *chart = chartGroup->chartAt(k); + if (chart->isPlanar()) + planarChartsCount++; + else if (chart->isOrtho()) + orthoChartsCount++; + } + chartCount += chartGroup->chartCount(); + chartsAddedCount += chartGroup->paramAddedChartsCount(); + chartsDeletedCount += chartGroup->paramDeletedChartsCount(); + } + } + XA_PRINT(" %u planar charts, %u ortho charts, %u other\n", planarChartsCount, orthoChartsCount, chartCount - (planarChartsCount + orthoChartsCount)); + if (chartsDeletedCount > 0) { + XA_PRINT(" %u charts deleted due to invalid parameterizations, %u new charts added\n", chartsDeletedCount, chartsAddedCount); + XA_PRINT(" %u charts\n", ctx->paramAtlas.chartCount()); + } + uint32_t chartIndex = 0, invalidParamCount = 0; + for (uint32_t i = 0; i < ctx->meshCount; i++) { + for (uint32_t j = 0; j < ctx->paramAtlas.chartGroupCount(i); j++) { + const internal::param::ChartGroup *chartGroup = ctx->paramAtlas.chartGroupAt(i, j); + if (chartGroup->isVertexMap()) + continue; + for (uint32_t k = 0; k < chartGroup->chartCount(); k++) { + const internal::param::Chart *chart = chartGroup->chartAt(k); + const internal::param::ParameterizationQuality &quality = chart->paramQuality(); +#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 + bool invalid = false; + if (quality.boundaryIntersection) { + invalid = true; + XA_PRINT_WARNING(" Chart %u (mesh %u, group %u, id %u) (%s): invalid parameterization, self-intersecting boundary.\n", chartIndex, i, j, k, chart->isPlanar() ? "planar" : chart->isOrtho() ? "ortho" : "other"); + } + if (quality.flippedTriangleCount > 0) { + invalid = true; + XA_PRINT_WARNING(" Chart %u (mesh %u, group %u, id %u) (%s): invalid parameterization, %u / %u flipped triangles.\n", chartIndex, i, j, k, chart->isPlanar() ? "planar" : chart->isOrtho() ? "ortho" : "other", quality.flippedTriangleCount, quality.totalTriangleCount); + } + if (invalid) + invalidParamCount++; +#if XA_DEBUG_EXPORT_OBJ_INVALID_PARAMETERIZATION + if (invalid) { + 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); + mesh->writeObjLinkedBoundaries(file); + fclose(file); + } + } +#endif + chartIndex++; } } } + if (invalidParamCount > 0) + XA_PRINT_WARNING(" %u charts with invalid parameterizations\n", invalidParamCount); + XA_PROFILE_PRINT(" Total (concurrent): ", parameterizeChartsConcurrent) + XA_PROFILE_PRINT(" Total: ", parameterizeCharts) + XA_PROFILE_PRINT(" Orthogonal: ", parameterizeChartsOrthogonal) + XA_PROFILE_PRINT(" LSCM: ", parameterizeChartsLSCM) + XA_PROFILE_PRINT(" Evaluate quality: ", parameterizeChartsEvaluateQuality) + XA_PRINT_MEM_USAGE } -uint32_t GetWidth(const Atlas *atlas) +void PackCharts(Atlas *atlas, PackOptions packOptions) { - xaAssert(atlas); - return atlas->width; + // Validate arguments and context state. + if (!atlas) { + XA_PRINT_WARNING("PackCharts: atlas is null.\n"); + return; + } + Context *ctx = (Context *)atlas; + if (ctx->meshCount == 0 && ctx->uvMeshInstances.isEmpty()) { + XA_PRINT_WARNING("PackCharts: No meshes. Call AddMesh or AddUvMesh first.\n"); + return; + } + if (ctx->uvMeshInstances.isEmpty()) { + if (!ctx->paramAtlas.chartsComputed()) { + 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; + } + } + if (packOptions.texelsPerUnit < 0.0f) { + XA_PRINT_WARNING("PackCharts: PackOptions::texelsPerUnit is negative.\n"); + packOptions.texelsPerUnit = 0.0f; + } + // Cleanup atlas. + DestroyOutputMeshes(ctx); + if (atlas->utilization) { + XA_FREE(atlas->utilization); + atlas->utilization = nullptr; + } + if (atlas->image) { + XA_FREE(atlas->image); + atlas->image = nullptr; + } + atlas->meshCount = 0; + // Pack charts. + internal::pack::Atlas packAtlas; + if (!ctx->uvMeshInstances.isEmpty()) { + for (uint32_t i = 0; i < ctx->uvMeshInstances.size(); i++) + packAtlas.addUvMeshCharts(ctx->uvMeshInstances[i]); + } + else if (ctx->paramAtlas.chartCount() > 0) { + ctx->paramAtlas.restoreOriginalChartTexcoords(); + for (uint32_t i = 0; i < ctx->paramAtlas.chartCount(); i++) + packAtlas.addChart(ctx->paramAtlas.chartAt(i)); + } + XA_PROFILE_START(packCharts) + if (!packAtlas.packCharts(packOptions, ctx->progressFunc, ctx->progressUserData)) + return; + XA_PROFILE_END(packCharts) + // Populate atlas object with pack results. + atlas->atlasCount = packAtlas.getNumAtlases(); + atlas->chartCount = packAtlas.getChartCount(); + atlas->width = packAtlas.getWidth(); + atlas->height = packAtlas.getHeight(); + atlas->texelsPerUnit = packAtlas.getTexelsPerUnit(); + if (atlas->atlasCount > 0) { + atlas->utilization = XA_ALLOC_ARRAY(internal::MemTag::Default, float, atlas->atlasCount); + for (uint32_t i = 0; i < atlas->atlasCount; i++) + atlas->utilization[i] = packAtlas.getUtilization(i); + } + if (packOptions.createImage) { + atlas->image = XA_ALLOC_ARRAY(internal::MemTag::Default, uint32_t, atlas->atlasCount * atlas->width * atlas->height); + for (uint32_t i = 0; i < atlas->atlasCount; i++) + packAtlas.getImages()[i]->copyTo(&atlas->image[atlas->width * atlas->height * i], atlas->width, atlas->height); + } + XA_PROFILE_PRINT(" Total: ", packCharts) + XA_PROFILE_PRINT(" Rasterize: ", packChartsRasterize) + XA_PROFILE_PRINT(" Dilate (padding): ", packChartsDilate) + XA_PROFILE_PRINT(" Find location: ", packChartsFindLocation) + XA_PROFILE_PRINT(" Blit: ", packChartsBlit) + XA_PRINT_MEM_USAGE +#if XA_PROFILE + internal::s_profile.packCharts = 0; + internal::s_profile.packChartsRasterize = 0; + internal::s_profile.packChartsDilate = 0; + internal::s_profile.packChartsFindLocation = 0; + internal::s_profile.packChartsBlit = 0; +#endif + XA_PRINT("Building output meshes\n"); + int progress = 0; + if (ctx->progressFunc) { + if (!ctx->progressFunc(ProgressCategory::BuildOutputMeshes, 0, ctx->progressUserData)) + return; + } + if (ctx->uvMeshInstances.isEmpty()) + atlas->meshCount = ctx->meshCount; + else + atlas->meshCount = ctx->uvMeshInstances.size(); + atlas->meshes = XA_ALLOC_ARRAY(internal::MemTag::Default, Mesh, atlas->meshCount); + memset(atlas->meshes, 0, sizeof(Mesh) * atlas->meshCount); + if (ctx->uvMeshInstances.isEmpty()) { + uint32_t chartIndex = 0; + for (uint32_t i = 0; i < ctx->meshCount; i++) { + Mesh &outputMesh = atlas->meshes[i]; + // Count and alloc arrays. Ignore vertex mapped chart groups in Mesh::chartCount, since they're ignored faces. + for (uint32_t cg = 0; cg < ctx->paramAtlas.chartGroupCount(i); cg++) { + const internal::param::ChartGroup *chartGroup = ctx->paramAtlas.chartGroupAt(i, cg); + if (chartGroup->isVertexMap()) { + outputMesh.vertexCount += chartGroup->mesh()->vertexCount(); + outputMesh.indexCount += chartGroup->mesh()->faceCount() * 3; + } else { + 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.chartCount++; + } + } + } + outputMesh.vertexArray = XA_ALLOC_ARRAY(internal::MemTag::Default, Vertex, outputMesh.vertexCount); + outputMesh.indexArray = XA_ALLOC_ARRAY(internal::MemTag::Default, uint32_t, outputMesh.indexCount); + outputMesh.chartArray = XA_ALLOC_ARRAY(internal::MemTag::Default, Chart, outputMesh.chartCount); + XA_PRINT(" mesh %u: %u vertices, %u triangles, %u charts\n", i, outputMesh.vertexCount, outputMesh.indexCount / 3, outputMesh.chartCount); + // Copy mesh data. + uint32_t firstVertex = 0; + uint32_t meshChartIndex = 0; + for (uint32_t cg = 0; cg < ctx->paramAtlas.chartGroupCount(i); cg++) { + const internal::param::ChartGroup *chartGroup = ctx->paramAtlas.chartGroupAt(i, cg); + if (chartGroup->isVertexMap()) { + const internal::Mesh *mesh = chartGroup->mesh(); + // Vertices. + for (uint32_t v = 0; v < mesh->vertexCount(); v++) { + Vertex &vertex = outputMesh.vertexArray[firstVertex + v]; + vertex.atlasIndex = -1; + vertex.chartIndex = -1; + vertex.uv[0] = vertex.uv[1] = 0.0f; + vertex.xref = chartGroup->mapVertexToSourceVertex(v); + } + // Indices. + for (uint32_t f = 0; f < mesh->faceCount(); f++) { + const uint32_t indexOffset = chartGroup->mapFaceToSourceFace(f) * 3; + for (uint32_t j = 0; j < 3; j++) + outputMesh.indexArray[indexOffset + j] = firstVertex + mesh->vertexAt(f * 3 + j); + } + firstVertex += mesh->vertexCount(); + } else { + for (uint32_t c = 0; c < chartGroup->chartCount(); c++) { + const internal::param::Chart *chart = chartGroup->chartAt(c); + const internal::Mesh *mesh = chart->mesh(); + // Vertices. + for (uint32_t v = 0; v < mesh->vertexCount(); 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); + vertex.uv[0] = internal::max(0.0f, uv.x); + vertex.uv[1] = internal::max(0.0f, uv.y); + vertex.xref = chartGroup->mapVertexToSourceVertex(chart->mapChartVertexToOriginalVertex(v)); + } + // Indices. + for (uint32_t f = 0; f < mesh->faceCount(); f++) { + const uint32_t indexOffset = chartGroup->mapFaceToSourceFace(chart->mapFaceToSourceFace(f)) * 3; + for (uint32_t j = 0; j < 3; j++) + outputMesh.indexArray[indexOffset + j] = firstVertex + mesh->vertexAt(f * 3 + j); + } + // Charts. + Chart *outputChart = &outputMesh.chartArray[meshChartIndex]; + const int32_t atlasIndex = packAtlas.getChart(chartIndex)->atlasIndex; + XA_DEBUG_ASSERT(atlasIndex >= 0); + outputChart->atlasIndex = (uint32_t)atlasIndex; + outputChart->flags = 0; + if (chart->paramQuality().boundaryIntersection || chart->paramQuality().flippedTriangleCount > 0) + outputChart->flags |= ChartFlags::Invalid; + outputChart->indexCount = mesh->faceCount() * 3; + outputChart->indexArray = XA_ALLOC_ARRAY(internal::MemTag::Default, uint32_t, outputChart->indexCount); + for (uint32_t f = 0; f < mesh->faceCount(); f++) { + for (uint32_t j = 0; j < 3; j++) + outputChart->indexArray[3 * f + j] = firstVertex + mesh->vertexAt(f * 3 + j); + } + outputChart->material = 0; + meshChartIndex++; + chartIndex++; + firstVertex += chart->mesh()->vertexCount(); + } + } + } + XA_DEBUG_ASSERT(outputMesh.vertexCount == firstVertex); + XA_DEBUG_ASSERT(outputMesh.chartCount == meshChartIndex); + if (ctx->progressFunc) { + const int newProgress = int((i + 1) / (float)atlas->meshCount * 100.0f); + if (newProgress != progress) { + progress = newProgress; + if (!ctx->progressFunc(ProgressCategory::BuildOutputMeshes, progress, ctx->progressUserData)) + return; + } + } + } + } else { + uint32_t chartIndex = 0; + for (uint32_t m = 0; m < ctx->uvMeshInstances.size(); m++) { + Mesh &outputMesh = atlas->meshes[m]; + const internal::UvMeshInstance *mesh = ctx->uvMeshInstances[m]; + // Alloc arrays. + outputMesh.vertexCount = mesh->texcoords.size(); + outputMesh.indexCount = mesh->mesh->indices.size(); + outputMesh.chartCount = mesh->mesh->charts.size(); + outputMesh.vertexArray = XA_ALLOC_ARRAY(internal::MemTag::Default, Vertex, outputMesh.vertexCount); + outputMesh.indexArray = XA_ALLOC_ARRAY(internal::MemTag::Default, uint32_t, outputMesh.indexCount); + outputMesh.chartArray = XA_ALLOC_ARRAY(internal::MemTag::Default, Chart, outputMesh.chartCount); + XA_PRINT(" UV mesh %u: %u vertices, %u triangles, %u charts\n", m, outputMesh.vertexCount, outputMesh.indexCount / 3, outputMesh.chartCount); + // Copy mesh data. + // Vertices. + for (uint32_t v = 0; v < mesh->texcoords.size(); v++) { + Vertex &vertex = outputMesh.vertexArray[v]; + vertex.uv[0] = mesh->texcoords[v].x; + vertex.uv[1] = mesh->texcoords[v].y; + vertex.xref = v; + const uint32_t meshChartIndex = mesh->mesh->vertexToChartMap[v]; + if (meshChartIndex == UINT32_MAX) { + // Vertex doesn't exist in any chart. + vertex.atlasIndex = -1; + vertex.chartIndex = -1; + } else { + const internal::pack::Chart *chart = packAtlas.getChart(chartIndex + meshChartIndex); + vertex.atlasIndex = chart->atlasIndex; + vertex.chartIndex = (int32_t)chartIndex + meshChartIndex; + } + } + // Indices. + memcpy(outputMesh.indexArray, mesh->mesh->indices.data(), mesh->mesh->indices.size() * sizeof(uint32_t)); + // Charts. + for (uint32_t c = 0; c < mesh->mesh->charts.size(); c++) { + Chart *outputChart = &outputMesh.chartArray[c]; + const internal::pack::Chart *chart = packAtlas.getChart(chartIndex); + XA_DEBUG_ASSERT(chart->atlasIndex >= 0); + outputChart->atlasIndex = (uint32_t)chart->atlasIndex; + outputChart->indexCount = chart->indexCount; + outputChart->indexArray = XA_ALLOC_ARRAY(internal::MemTag::Default, uint32_t, outputChart->indexCount); + outputChart->material = chart->material; + memcpy(outputChart->indexArray, chart->indices, chart->indexCount * sizeof(uint32_t)); + chartIndex++; + } + if (ctx->progressFunc) { + const int newProgress = int((m + 1) / (float)atlas->meshCount * 100.0f); + if (newProgress != progress) { + progress = newProgress; + if (!ctx->progressFunc(ProgressCategory::BuildOutputMeshes, progress, ctx->progressUserData)) + return; + } + } + } + } + if (ctx->progressFunc && progress != 100) + ctx->progressFunc(ProgressCategory::BuildOutputMeshes, 100, ctx->progressUserData); + XA_PRINT_MEM_USAGE +} + +void Generate(Atlas *atlas, ChartOptions chartOptions, ParameterizeFunc paramFunc, 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->meshCount == 0) { + XA_PRINT_WARNING("Generate: No meshes. Call AddMesh first.\n"); + return; + } + ComputeCharts(atlas, chartOptions); + ParameterizeCharts(atlas, paramFunc); + PackCharts(atlas, packOptions); } -uint32_t GetHeight(const Atlas *atlas) +void SetProgressCallback(Atlas *atlas, ProgressFunc progressFunc, void *progressUserData) { - xaAssert(atlas); - return atlas->height; + if (!atlas) { + XA_PRINT_WARNING("SetProgressCallback: atlas is null.\n"); + return; + } + Context *ctx = (Context *)atlas; + ctx->progressFunc = progressFunc; + ctx->progressUserData = progressUserData; } -uint32_t GetNumCharts(const Atlas *atlas) +void SetRealloc(ReallocFunc reallocFunc) { - xaAssert(atlas); - return atlas->atlas.chartCount(); + internal::s_realloc = reallocFunc; +} + +void SetPrint(PrintFunc print, bool verbose) +{ + internal::s_print = print; + internal::s_printVerbose = verbose; } -const OutputMesh * const *GetOutputMeshes(const Atlas *atlas) +const char *StringForEnum(AddMeshError::Enum error) { - xaAssert(atlas); - return atlas->outputMeshes; + if (error == AddMeshError::Error) + return "Unspecified error"; + if (error == AddMeshError::IndexOutOfRange) + return "Index out of range"; + if (error == AddMeshError::InvalidIndexCount) + return "Invalid index count"; + return "Success"; } -const char *StringForEnum(AddMeshErrorCode::Enum error) -{ - if (error == AddMeshErrorCode::AlreadyAddedEdge) - return "already added edge"; - if (error == AddMeshErrorCode::DegenerateColocalEdge) - return "degenerate colocal edge"; - if (error == AddMeshErrorCode::DegenerateEdge) - return "degenerate edge"; - if (error == AddMeshErrorCode::DuplicateEdge) - return "duplicate edge"; - if (error == AddMeshErrorCode::IndexOutOfRange) - return "index out of range"; - if (error == AddMeshErrorCode::InvalidIndexCount) - return "invalid index count"; - if (error == AddMeshErrorCode::ZeroAreaFace) - return "zero area face"; - if (error == AddMeshErrorCode::ZeroLengthEdge) - return "zero length edge"; - return "success"; +const char *StringForEnum(ProgressCategory::Enum 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) + return "Building output meshes"; + return ""; } } // namespace xatlas |