diff options
Diffstat (limited to 'thirdparty')
-rw-r--r-- | thirdparty/README.md | 21 | ||||
-rw-r--r-- | thirdparty/b2d_convexdecomp/b2Glue.h | 174 | ||||
-rw-r--r-- | thirdparty/b2d_convexdecomp/b2Polygon.cpp | 1591 | ||||
-rw-r--r-- | thirdparty/b2d_convexdecomp/b2Polygon.h | 133 | ||||
-rw-r--r-- | thirdparty/b2d_convexdecomp/b2Triangle.cpp | 82 | ||||
-rw-r--r-- | thirdparty/b2d_convexdecomp/b2Triangle.h | 41 | ||||
-rw-r--r-- | thirdparty/enet/enet/godot.h | 4 | ||||
-rw-r--r-- | thirdparty/enet/godot.cpp | 4 | ||||
-rw-r--r-- | thirdparty/misc/ifaddrs-android.h | 19 | ||||
-rw-r--r-- | thirdparty/vhacd/0003-fix-musl-build.patch | 15 | ||||
-rw-r--r-- | thirdparty/vhacd/0004-fix-uwp-arm-build.patch | 16 | ||||
-rw-r--r-- | thirdparty/vhacd/inc/btScalar.h | 5 | ||||
-rw-r--r-- | thirdparty/vhacd/inc/vhacdMutex.h | 4 | ||||
-rw-r--r-- | thirdparty/xatlas/LICENSE | 27 | ||||
-rw-r--r-- | thirdparty/xatlas/xatlas.cpp | 3806 | ||||
-rw-r--r-- | thirdparty/xatlas/xatlas.h | 11 |
16 files changed, 2422 insertions, 3531 deletions
diff --git a/thirdparty/README.md b/thirdparty/README.md index 9571ee49b9..58d979ccca 100644 --- a/thirdparty/README.md +++ b/thirdparty/README.md @@ -8,21 +8,6 @@ - License: BSD-3-Clause -## b2d_convexdecomp - -- Upstream: https://github.com/erincatto/Box2D/tree/master/Contributions/Utilities/ConvexDecomposition -- Version: git (25615e0, 2015) with modifications -- License: zlib - -The files were adapted to Godot by removing the dependency on b2Math (replacing -it by b2Glue.h) and commenting out some verbose printf calls. -Upstream code has not changed in 10 years, no need to keep track of changes. - -Important: Some files have Godot-made changes. -They are marked with `// -- GODOT start --` and `// -- GODOT end --` -comments. - - ## bullet - Upstream: https://github.com/bulletphysics/bullet3 @@ -511,15 +496,13 @@ File extracted from upstream release tarball: ## xatlas - Upstream: https://github.com/jpcy/xatlas -- Version: git (b4b5426, 2019) +- Version: git (e12ea82, 2019) - License: MIT Files extracted from upstream source: - `xatlas.{cpp,h}` - -Note: License is marked as Public Domain in the files, but it was -later clarified upstream to MIT license. +- `LICENSE` ## zlib diff --git a/thirdparty/b2d_convexdecomp/b2Glue.h b/thirdparty/b2d_convexdecomp/b2Glue.h deleted file mode 100644 index 175f75be75..0000000000 --- a/thirdparty/b2d_convexdecomp/b2Glue.h +++ /dev/null @@ -1,174 +0,0 @@ -/* -* Copyright (c) 2006-2009 Erin Catto http://www.gphysics.com -* -* This software is provided 'as-is', without any express or implied -* warranty. In no event will the authors be held liable for any damages -* arising from the use of this software. -* Permission is granted to anyone to use this software for any purpose, -* including commercial applications, and to alter it and redistribute it -* freely, subject to the following restrictions: -* 1. The origin of this software must not be misrepresented; you must not -* claim that you wrote the original software. If you use this software -* in a product, an acknowledgment in the product documentation would be -* appreciated but is not required. -* 2. Altered source versions must be plainly marked as such, and must not be -* misrepresented as being the original software. -* 3. This notice may not be removed or altered from any source distribution. -*/ - -#ifndef B2GLUE_H -#define B2GLUE_H - -#include "core/math/vector2.h" - -#include <limits.h> - -namespace b2ConvexDecomp { - -typedef real_t float32; -typedef int32_t int32; - -static inline float32 b2Sqrt(float32 val) { return Math::sqrt(val); } -#define b2_maxFloat FLT_MAX -#define b2_epsilon CMP_EPSILON -#define b2_pi 3.14159265359f -#define b2_maxPolygonVertices 16 -#define b2Max MAX -#define b2Min MIN -#define b2Clamp CLAMP -#define b2Abs ABS -/// A small length used as a collision and constraint tolerance. Usually it is -/// chosen to be numerically significant, but visually insignificant. -#define b2_linearSlop 0.005f - -/// A small angle used as a collision and constraint tolerance. Usually it is -/// chosen to be numerically significant, but visually insignificant. -#define b2_angularSlop (2.0f / 180.0f * b2_pi) - -/// A 2D column vector. -struct b2Vec2 -{ - /// Default constructor does nothing (for performance). - b2Vec2() {} - - /// Construct using coordinates. - b2Vec2(float32 x, float32 y) : x(x), y(y) {} - - /// Set this vector to all zeros. - void SetZero() { x = 0.0f; y = 0.0f; } - - /// Set this vector to some specified coordinates. - void Set(float32 x_, float32 y_) { x = x_; y = y_; } - - /// Negate this vector. - b2Vec2 operator -() const { b2Vec2 v; v.Set(-x, -y); return v; } - - /// Read from and indexed element. - float32 operator () (int32 i) const - { - return (&x)[i]; - } - - /// Write to an indexed element. - float32& operator () (int32 i) - { - return (&x)[i]; - } - - /// Add a vector to this vector. - void operator += (const b2Vec2& v) - { - x += v.x; y += v.y; - } - - /// Subtract a vector from this vector. - void operator -= (const b2Vec2& v) - { - x -= v.x; y -= v.y; - } - - /// Multiply this vector by a scalar. - void operator *= (float32 a) - { - x *= a; y *= a; - } - - /// Get the length of this vector (the norm). - float32 Length() const - { - return b2Sqrt(x * x + y * y); - } - - /// Get the length squared. For performance, use this instead of - /// b2Vec2::Length (if possible). - float32 LengthSquared() const - { - return x * x + y * y; - } - - bool operator==(const b2Vec2& p_v) const { - return x==p_v.x && y==p_v.y; - } - b2Vec2 operator+(const b2Vec2& p_v) const { - return b2Vec2(x+p_v.x,y+p_v.y); - } - b2Vec2 operator-(const b2Vec2& p_v) const { - return b2Vec2(x-p_v.x,y-p_v.y); - } - - b2Vec2 operator*(float32 f) const { - return b2Vec2(f*x,f*y); - } - - /// Convert this vector into a unit vector. Returns the length. - float32 Normalize() - { - float32 length = Length(); - if (length < b2_epsilon) - { - return 0.0f; - } - float32 invLength = 1.0f / length; - x *= invLength; - y *= invLength; - - return length; - } - - /* - /// Does this vector contain finite coordinates? - bool IsValid() const - { - return b2IsValid(x) && b2IsValid(y); - } - */ - - float32 x, y; -}; - -inline b2Vec2 operator*(float32 f,const b2Vec2& p_v) { - return b2Vec2(f*p_v.x,f*p_v.y); -} - -/// Perform the dot product on two vectors. -inline float32 b2Dot(const b2Vec2& a, const b2Vec2& b) -{ - return a.x * b.x + a.y * b.y; -} - -/// Perform the cross product on two vectors. In 2D this produces a scalar. -inline float32 b2Cross(const b2Vec2& a, const b2Vec2& b) -{ - return a.x * b.y - a.y * b.x; -} - -/// Perform the cross product on a vector and a scalar. In 2D this produces -/// a vector. -inline b2Vec2 b2Cross(const b2Vec2& a, float32 s) -{ - return b2Vec2(s * a.y, -s * a.x); -} - -} - -#endif diff --git a/thirdparty/b2d_convexdecomp/b2Polygon.cpp b/thirdparty/b2d_convexdecomp/b2Polygon.cpp deleted file mode 100644 index c80204ae21..0000000000 --- a/thirdparty/b2d_convexdecomp/b2Polygon.cpp +++ /dev/null @@ -1,1591 +0,0 @@ -/* - * Copyright (c) 2007 Eric Jordan - * - * This software is provided 'as-is', without any express or implied - * warranty. In no event will the authors be held liable for any damages - * arising from the use of this software. - * Permission is granted to anyone to use this software for any purpose, - * including commercial applications, and to alter it and redistribute it - * freely, subject to the following restrictions: - * 1. The origin of this software must not be misrepresented; you must not - * claim that you wrote the original software. If you use this software - * in a product, an acknowledgment in the product documentation would be - * appreciated but is not required. - * 2. Altered source versions must be plainly marked as such, and must not be - * misrepresented as being the original software. - * 3. This notice may not be removed or altered from any source distribution. - */ - -// This utility works with Box2d version 2.0 (or higher), and not with 1.4.3 - -#include "b2Triangle.h" -#include "b2Polygon.h" - -#include <math.h> -#include <limits.h> -#include <assert.h> -#define b2Assert assert - -namespace b2ConvexDecomp { - - -//If you're using 1.4.3, b2_toiSlop won't exist, so set this equal to 0 -static const float32 toiSlop = 0.0f; - -/* - * Check if the lines a0->a1 and b0->b1 cross. - * If they do, intersectionPoint will be filled - * with the point of crossing. - * - * Grazing lines should not return true. - */ -bool intersect(const b2Vec2& a0, const b2Vec2& a1, - const b2Vec2& b0, const b2Vec2& b1, - b2Vec2& intersectionPoint) { - - if (a0 == b0 || a0 == b1 || a1 == b0 || a1 == b1) return false; - float x1 = a0.x; float y1 = a0.y; - float x2 = a1.x; float y2 = a1.y; - float x3 = b0.x; float y3 = b0.y; - float x4 = b1.x; float y4 = b1.y; - - //AABB early exit - if (b2Max(x1,x2) < b2Min(x3,x4) || b2Max(x3,x4) < b2Min(x1,x2) ) return false; - if (b2Max(y1,y2) < b2Min(y3,y4) || b2Max(y3,y4) < b2Min(y1,y2) ) return false; - - float ua = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)); - float ub = ((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)); - float denom = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1); - if (b2Abs(denom) < CMP_EPSILON) { - //Lines are too close to parallel to call - return false; - } - ua /= denom; - ub /= denom; - - if ((0 < ua) && (ua < 1) && (0 < ub) && (ub < 1)) { - //if (intersectionPoint){ - intersectionPoint.x = (x1 + ua * (x2 - x1)); - intersectionPoint.y = (y1 + ua * (y2 - y1)); - //} - //printf("%f, %f -> %f, %f crosses %f, %f -> %f, %f\n",x1,y1,x2,y2,x3,y3,x4,y4); - return true; - } - - return false; -} - -/* - * True if line from a0->a1 intersects b0->b1 - */ -bool intersect(const b2Vec2& a0, const b2Vec2& a1, - const b2Vec2& b0, const b2Vec2& b1) { - b2Vec2 myVec(0.0f,0.0f); - return intersect(a0, a1, b0, b1, myVec); -} - -b2Polygon::b2Polygon(float32* _x, float32* _y, int32 nVert) { - nVertices = nVert; - x = new float32[nVertices]; - y = new float32[nVertices]; - for (int32 i = 0; i < nVertices; ++i) { - x[i] = _x[i]; - y[i] = _y[i]; - } - areaIsSet = false; -} - -b2Polygon::b2Polygon(b2Vec2* v, int32 nVert) { - nVertices = nVert; - x = new float32[nVertices]; - y = new float32[nVertices]; - for (int32 i = 0; i < nVertices; ++i) { - x[i] = v[i].x; - y[i] = v[i].y; - - } - areaIsSet = false; -} - -b2Polygon::b2Polygon() { - x = NULL; - y = NULL; - nVertices = 0; - areaIsSet = false; -} - -b2Polygon::~b2Polygon() { - //printf("About to delete poly with %d vertices\n",nVertices); - delete[] x; - delete[] y; -} - -float32 b2Polygon::GetArea() { - // TODO: fix up the areaIsSet caching so that it can be used - //if (areaIsSet) return area; - area = 0.0f; - - //First do wraparound - area += x[nVertices-1]*y[0]-x[0]*y[nVertices-1]; - for (int i=0; i<nVertices-1; ++i){ - area += x[i]*y[i+1]-x[i+1]*y[i]; - } - area *= .5f; - areaIsSet = true; - return area; -} - -bool b2Polygon::IsCCW() { - return (GetArea() > 0.0f); -} - -void b2Polygon::MergeParallelEdges(float32 tolerance) { - if (nVertices <= 3) return; //Can't do anything useful here to a triangle - bool* mergeMe = new bool[nVertices]; - int32 newNVertices = nVertices; - for (int32 i = 0; i < nVertices; ++i) { - int32 lower = (i == 0) ? (nVertices - 1) : (i - 1); - int32 middle = i; - int32 upper = (i == nVertices - 1) ? (0) : (i + 1); - float32 dx0 = x[middle] - x[lower]; - float32 dy0 = y[middle] - y[lower]; - float32 dx1 = x[upper] - x[middle]; - float32 dy1 = y[upper] - y[middle]; - float32 norm0 = sqrtf(dx0*dx0+dy0*dy0); - float32 norm1 = sqrtf(dx1*dx1+dy1*dy1); - if ( !(norm0 > 0.0f && norm1 > 0.0f) && newNVertices > 3 ) { - //Merge identical points - mergeMe[i] = true; - --newNVertices; - } - dx0 /= norm0; dy0 /= norm0; - dx1 /= norm1; dy1 /= norm1; - float32 cross = dx0 * dy1 - dx1 * dy0; - float32 dot = dx0 * dx1 + dy0 * dy1; - if (fabs(cross) < tolerance && dot > 0 && newNVertices > 3) { - mergeMe[i] = true; - --newNVertices; - } else { - mergeMe[i] = false; - } - } - if(newNVertices == nVertices || newNVertices == 0) { - delete[] mergeMe; - return; - } - float32* newx = new float32[newNVertices]; - float32* newy = new float32[newNVertices]; - int32 currIndex = 0; - for (int32 i=0; i < nVertices; ++i) { - if (mergeMe[i] || newNVertices == 0 || currIndex == newNVertices) continue; - b2Assert(currIndex < newNVertices); - newx[currIndex] = x[i]; - newy[currIndex] = y[i]; - ++currIndex; - } - delete[] x; - delete[] y; - delete[] mergeMe; - x = newx; - y = newy; - nVertices = newNVertices; - //printf("%d \n", newNVertices); -} - - /* - * Allocates and returns pointer to vector vertex array. - * Length of array is nVertices. - */ -b2Vec2* b2Polygon::GetVertexVecs() { - b2Vec2* out = new b2Vec2[nVertices]; - for (int32 i = 0; i < nVertices; ++i) { - out[i].Set(x[i], y[i]); - } - return out; -} - -b2Polygon::b2Polygon(b2Triangle& t) { - nVertices = 3; - x = new float[nVertices]; - y = new float[nVertices]; - for (int32 i = 0; i < nVertices; ++i) { - x[i] = t.x[i]; - y[i] = t.y[i]; - } -} - -void b2Polygon::Set(const b2Polygon& p) { - if (nVertices != p.nVertices){ - nVertices = p.nVertices; - delete[] x; - delete[] y; - x = new float32[nVertices]; - y = new float32[nVertices]; - } - - for (int32 i = 0; i < nVertices; ++i) { - x[i] = p.x[i]; - y[i] = p.y[i]; - } - areaIsSet = false; -} - - /* - * Assuming the polygon is simple, checks if it is convex. - */ -bool b2Polygon::IsConvex() { - bool isPositive = false; - for (int32 i = 0; i < nVertices; ++i) { - int32 lower = (i == 0) ? (nVertices - 1) : (i - 1); - int32 middle = i; - int32 upper = (i == nVertices - 1) ? (0) : (i + 1); - float32 dx0 = x[middle] - x[lower]; - float32 dy0 = y[middle] - y[lower]; - float32 dx1 = x[upper] - x[middle]; - float32 dy1 = y[upper] - y[middle]; - float32 cross = dx0 * dy1 - dx1 * dy0; - // Cross product should have same sign - // for each vertex if poly is convex. - bool newIsP = (cross >= 0) ? true : false; - if (i == 0) { - isPositive = newIsP; - } - else if (isPositive != newIsP) { - return false; - } - } - return true; -} - -/* - * Pulled from b2Shape.cpp, assertions removed - */ -static b2Vec2 PolyCentroid(const b2Vec2* vs, int32 count) -{ - b2Vec2 c; c.Set(0.0f, 0.0f); - float32 area = 0.0f; - - const float32 inv3 = 1.0f / 3.0f; - b2Vec2 pRef(0.0f, 0.0f); - for (int32 i = 0; i < count; ++i) - { - // Triangle vertices. - b2Vec2 p1 = pRef; - b2Vec2 p2 = vs[i]; - b2Vec2 p3 = i + 1 < count ? vs[i+1] : vs[0]; - - b2Vec2 e1 = p2 - p1; - b2Vec2 e2 = p3 - p1; - - float32 D = b2Cross(e1, e2); - - float32 triangleArea = 0.5f * D; - area += triangleArea; - - // Area weighted centroid - c += (p1 + p2 + p3) * triangleArea * inv3; - } - - // Centroid - c *= 1.0f / area; - return c; -} - - -/* - * Checks if polygon is valid for use in Box2d engine. - * Last ditch effort to ensure no invalid polygons are - * added to world geometry. - * - * Performs a full check, for simplicity, convexity, - * orientation, minimum angle, and volume. This won't - * be very efficient, and a lot of it is redundant when - * other tools in this section are used. - */ -bool b2Polygon::IsUsable(bool printErrors){ - int32 error = -1; - bool noError = true; - if (nVertices < 3 || nVertices > b2_maxPolygonVertices) {noError = false; error = 0;} - if (!IsConvex()) {noError = false; error = 1;} - if (!IsSimple()) {noError = false; error = 2;} - if (GetArea() < CMP_EPSILON) {noError = false; error = 3;} - - //Compute normals - b2Vec2* normals = new b2Vec2[nVertices]; - b2Vec2* vertices = new b2Vec2[nVertices]; - for (int32 i = 0; i < nVertices; ++i){ - vertices[i].Set(x[i],y[i]); - int32 i1 = i; - int32 i2 = i + 1 < nVertices ? i + 1 : 0; - b2Vec2 edge(x[i2]-x[i1],y[i2]-y[i1]); - normals[i] = b2Cross(edge, 1.0f); - normals[i].Normalize(); - } - - //Required side checks - for (int32 i=0; i<nVertices; ++i){ - int32 iminus = (i==0)?nVertices-1:i-1; - //int32 iplus = (i==nVertices-1)?0:i+1; - - //Parallel sides check - float32 cross = b2Cross(normals[iminus], normals[i]); - cross = b2Clamp(cross, -1.0f, 1.0f); - float32 angle = asinf(cross); - if(angle <= b2_angularSlop){ - noError = false; - error = 4; - break; - } - - //Too skinny check - for (int32 j=0; j<nVertices; ++j){ - if (j == i || j == (i + 1) % nVertices){ - continue; - } - float32 s = b2Dot(normals[i], vertices[j] - vertices[i]); - if (s >= -b2_linearSlop){ - noError = false; - error = 5; - } - } - - - b2Vec2 centroid = PolyCentroid(vertices,nVertices); - b2Vec2 n1 = normals[iminus]; - b2Vec2 n2 = normals[i]; - b2Vec2 v = vertices[i] - centroid; - - b2Vec2 d; - d.x = b2Dot(n1, v) - toiSlop; - d.y = b2Dot(n2, v) - toiSlop; - - // Shifting the edge inward by b2_toiSlop should - // not cause the plane to pass the centroid. - if ((d.x < 0.0f)||(d.y < 0.0f)){ - noError = false; - error = 6; - } - - } - delete[] vertices; - delete[] normals; - - if (!noError && printErrors){ - printf("Found invalid polygon, "); - switch(error){ - case 0: - printf("must have between 3 and %d vertices.\n",b2_maxPolygonVertices); - break; - case 1: - printf("must be convex.\n"); - break; - case 2: - printf("must be simple (cannot intersect itself).\n"); - break; - case 3: - printf("area is too small.\n"); - break; - case 4: - printf("sides are too close to parallel.\n"); - break; - case 5: - printf("polygon is too thin.\n"); - break; - case 6: - printf("core shape generation would move edge past centroid (too thin).\n"); - break; - default: - printf("don't know why.\n"); - } - } - return noError; -} - - -bool b2Polygon::IsUsable(){ - return IsUsable(B2_POLYGON_REPORT_ERRORS); -} - -//Check for edge crossings -bool b2Polygon::IsSimple() { - for (int32 i=0; i<nVertices; ++i){ - int32 iplus = (i+1 > nVertices-1)?0:i+1; - b2Vec2 a1(x[i],y[i]); - b2Vec2 a2(x[iplus],y[iplus]); - for (int32 j=i+1; j<nVertices; ++j){ - int32 jplus = (j+1 > nVertices-1)?0:j+1; - b2Vec2 b1(x[j],y[j]); - b2Vec2 b2(x[jplus],y[jplus]); - if (intersect(a1,a2,b1,b2)){ - return false; - } - } - } - return true; -} - - /* - * Tries to add a triangle to the polygon. Returns null if it can't connect - * properly, otherwise returns a pointer to the new Polygon. Assumes bitwise - * equality of joined vertex positions. - * - * Remember to delete the pointer afterwards. - * Todo: Make this return a b2Polygon instead - * of a pointer to a heap-allocated one. - * - * For internal use. - */ -b2Polygon* b2Polygon::Add(b2Triangle& t) { - // First, find vertices that connect - int32 firstP = -1; - int32 firstT = -1; - int32 secondP = -1; - int32 secondT = -1; - for (int32 i = 0; i < nVertices; i++) { - if (t.x[0] == x[i] && t.y[0] == y[i]) { - if (firstP == -1) { - firstP = i; - firstT = 0; - } - else { - secondP = i; - secondT = 0; - } - } - else if (t.x[1] == x[i] && t.y[1] == y[i]) { - if (firstP == -1) { - firstP = i; - firstT = 1; - } - else { - secondP = i; - secondT = 1; - } - } - else if (t.x[2] == x[i] && t.y[2] == y[i]) { - if (firstP == -1) { - firstP = i; - firstT = 2; - } - else { - secondP = i; - secondT = 2; - } - } - else { - } - } - // Fix ordering if first should be last vertex of poly - if (firstP == 0 && secondP == nVertices - 1) { - firstP = nVertices - 1; - secondP = 0; - } - - // Didn't find it - if (secondP == -1) { - return NULL; - } - - // Find tip index on triangle - int32 tipT = 0; - if (tipT == firstT || tipT == secondT) - tipT = 1; - if (tipT == firstT || tipT == secondT) - tipT = 2; - - float32* newx = new float[nVertices + 1]; - float32* newy = new float[nVertices + 1]; - int32 currOut = 0; - for (int32 i = 0; i < nVertices; i++) { - newx[currOut] = x[i]; - newy[currOut] = y[i]; - if (i == firstP) { - ++currOut; - newx[currOut] = t.x[tipT]; - newy[currOut] = t.y[tipT]; - } - ++currOut; - } - b2Polygon* result = new b2Polygon(newx, newy, nVertices+1); - delete[] newx; - delete[] newy; - return result; -} - - /** - * Adds this polygon to a PolyDef. - */ -#if 0 -void b2Polygon::AddTo(b2FixtureDef& pd) { - if (nVertices < 3) return; - - b2Assert(nVertices <= b2_maxPolygonVertices); - - b2Vec2* vecs = GetVertexVecs(); - b2Vec2* vecsToAdd = new b2Vec2[nVertices]; - - int32 offset = 0; - - b2PolygonShape *polyShape = new b2PolygonShape; - int32 ind; - - for (int32 i = 0; i < nVertices; ++i) { - - //Omit identical neighbors (including wraparound) - ind = i - offset; - if (vecs[i].x==vecs[remainder(i+1,nVertices)].x && - vecs[i].y==vecs[remainder(i+1,nVertices)].y){ - offset++; - continue; - } - - vecsToAdd[ind] = vecs[i]; - - } - - polyShape->Set((const b2Vec2*)vecsToAdd, ind+1); - pd.shape = polyShape; - - delete[] vecs; - delete[] vecsToAdd; -} -#endif - /** - * Finds and fixes "pinch points," points where two polygon - * vertices are at the same point. - * - * If a pinch point is found, pin is broken up into poutA and poutB - * and true is returned; otherwise, returns false. - * - * Mostly for internal use. - */ -bool ResolvePinchPoint(const b2Polygon& pin, b2Polygon& poutA, b2Polygon& poutB){ - if (pin.nVertices < 3) return false; - float32 tol = .001f; - bool hasPinchPoint = false; - int32 pinchIndexA = -1; - int32 pinchIndexB = -1; - for (int i=0; i<pin.nVertices; ++i){ - for (int j=i+1; j<pin.nVertices; ++j){ - //Don't worry about pinch points where the points - //are actually just dupe neighbors - if (b2Abs(pin.x[i]-pin.x[j])<tol&&b2Abs(pin.y[i]-pin.y[j])<tol&&j!=i+1){ - pinchIndexA = i; - pinchIndexB = j; - //printf("pinch: %f, %f == %f, %f\n",pin.x[i],pin.y[i],pin.x[j],pin.y[j]); - //printf("at indexes %d, %d\n",i,j); - hasPinchPoint = true; - break; - } - } - if (hasPinchPoint) break; - } - if (hasPinchPoint){ - //printf("Found pinch point\n"); - int32 sizeA = pinchIndexB - pinchIndexA; - if (sizeA == pin.nVertices) return false;//has dupe points at wraparound, not a problem here - float32* xA = new float32[sizeA]; - float32* yA = new float32[sizeA]; - for (int32 i=0; i < sizeA; ++i){ - int32 ind = remainder(pinchIndexA+i,pin.nVertices); - xA[i] = pin.x[ind]; - yA[i] = pin.y[ind]; - } - b2Polygon tempA(xA,yA,sizeA); - poutA.Set(tempA); - delete[] xA; - delete[] yA; - - int32 sizeB = pin.nVertices - sizeA; - float32* xB = new float32[sizeB]; - float32* yB = new float32[sizeB]; - for (int32 i=0; i<sizeB; ++i){ - int32 ind = remainder(pinchIndexB+i,pin.nVertices); - xB[i] = pin.x[ind]; - yB[i] = pin.y[ind]; - } - b2Polygon tempB(xB,yB,sizeB); - poutB.Set(tempB); - //printf("Size of a: %d, size of b: %d\n",sizeA,sizeB); - delete[] xB; - delete[] yB; - } - return hasPinchPoint; -} - - /** - * Triangulates a polygon using simple ear-clipping algorithm. Returns - * size of Triangle array unless the polygon can't be triangulated. - * This should only happen if the polygon self-intersects, - * though it will not _always_ return null for a bad polygon - it is the - * caller's responsibility to check for self-intersection, and if it - * doesn't, it should at least check that the return value is non-null - * before using. You're warned! - * - * Triangles may be degenerate, especially if you have identical points - * in the input to the algorithm. Check this before you use them. - * - * This is totally unoptimized, so for large polygons it should not be part - * of the simulation loop. - * - * Returns: - * -1 if algorithm fails (self-intersection most likely) - * 0 if there are not enough vertices to triangulate anything. - * Number of triangles if triangulation was successful. - * - * results will be filled with results - ear clipping always creates vNum - 2 - * or fewer (due to pinch point polygon snipping), so allocate an array of - * this size. - */ - -int32 TriangulatePolygon(float32* xv, float32* yv, int32 vNum, b2Triangle* results) { - if (vNum < 3) - return 0; - - //Recurse and split on pinch points - b2Polygon pA,pB; - b2Polygon pin(xv,yv,vNum); - if (ResolvePinchPoint(pin,pA,pB)){ - b2Triangle* mergeA = new b2Triangle[pA.nVertices]; - b2Triangle* mergeB = new b2Triangle[pB.nVertices]; - int32 nA = TriangulatePolygon(pA.x,pA.y,pA.nVertices,mergeA); - int32 nB = TriangulatePolygon(pB.x,pB.y,pB.nVertices,mergeB); - if (nA==-1 || nB==-1){ - delete[] mergeA; - delete[] mergeB; - return -1; - } - for (int32 i=0; i<nA; ++i){ - results[i].Set(mergeA[i]); - } - for (int32 i=0; i<nB; ++i){ - results[nA+i].Set(mergeB[i]); - } - delete[] mergeA; - delete[] mergeB; - return (nA+nB); - } - - b2Triangle* buffer = new b2Triangle[vNum-2]; - int32 bufferSize = 0; - float32* xrem = new float32[vNum]; - float32* yrem = new float32[vNum]; - for (int32 i = 0; i < vNum; ++i) { - xrem[i] = xv[i]; - yrem[i] = yv[i]; - } - - int xremLength = vNum; - - while (vNum > 3) { - // Find an ear - int32 earIndex = -1; - //float32 earVolume = -1.0f; - float32 earMaxMinCross = -10.0f; - for (int32 i = 0; i < vNum; ++i) { - if (IsEar(i, xrem, yrem, vNum)) { - int32 lower = remainder(i-1,vNum); - int32 upper = remainder(i+1,vNum); - b2Vec2 d1(xrem[upper]-xrem[i],yrem[upper]-yrem[i]); - b2Vec2 d2(xrem[i]-xrem[lower],yrem[i]-yrem[lower]); - b2Vec2 d3(xrem[lower]-xrem[upper],yrem[lower]-yrem[upper]); - - d1.Normalize(); - d2.Normalize(); - d3.Normalize(); - float32 cross12 = b2Abs( b2Cross(d1,d2) ); - float32 cross23 = b2Abs( b2Cross(d2,d3) ); - float32 cross31 = b2Abs( b2Cross(d3,d1) ); - //Find the maximum minimum angle - float32 minCross = b2Min(cross12, b2Min(cross23,cross31)); - if (minCross > earMaxMinCross){ - earIndex = i; - earMaxMinCross = minCross; - } - - /*//This bit chooses the ear with greatest volume first - float32 testVol = b2Abs( d1.x*d2.y-d2.x*d1.y ); - if (testVol > earVolume){ - earIndex = i; - earVolume = testVol; - }*/ - } - } - - // If we still haven't found an ear, we're screwed. - // Note: sometimes this is happening because the - // remaining points are collinear. Really these - // should just be thrown out without halting triangulation. - if (earIndex == -1){ - if (B2_POLYGON_REPORT_ERRORS){ - b2Polygon dump(xrem,yrem,vNum); - printf("Couldn't find an ear, dumping remaining poly:\n"); - dump.printFormatted(); - printf("Please submit this dump to ewjordan at Box2d forums\n"); - } - for (int32 i = 0; i < bufferSize; i++) { - results[i].Set(buffer[i]); - } - - delete[] buffer; - - if (bufferSize > 0) return bufferSize; - else return -1; - } - - // Clip off the ear: - // - remove the ear tip from the list - - --vNum; - float32* newx = new float32[vNum]; - float32* newy = new float32[vNum]; - int32 currDest = 0; - for (int32 i = 0; i < vNum; ++i) { - if (currDest == earIndex) ++currDest; - newx[i] = xrem[currDest]; - newy[i] = yrem[currDest]; - ++currDest; - } - - // - add the clipped triangle to the triangle list - int32 under = (earIndex == 0) ? (vNum) : (earIndex - 1); - int32 over = (earIndex == vNum) ? 0 : (earIndex + 1); - b2Triangle toAdd = b2Triangle(xrem[earIndex], yrem[earIndex], xrem[over], yrem[over], xrem[under], yrem[under]); - buffer[bufferSize].Set(toAdd); - ++bufferSize; - - // - replace the old list with the new one - delete[] xrem; - delete[] yrem; - xrem = newx; - yrem = newy; - } - - b2Triangle toAdd = b2Triangle(xrem[1], yrem[1], xrem[2], yrem[2], - xrem[0], yrem[0]); - buffer[bufferSize].Set(toAdd); - ++bufferSize; - - delete[] xrem; - delete[] yrem; - - b2Assert(bufferSize == xremLength-2); - - for (int32 i = 0; i < bufferSize; i++) { - results[i].Set(buffer[i]); - } - - delete[] buffer; - - return bufferSize; -} - - /** - * Turns a list of triangles into a list of convex polygons. Very simple - * method - start with a seed triangle, keep adding triangles to it until - * you can't add any more without making the polygon non-convex. - * - * Returns an integer telling how many polygons were created. Will fill - * polys array up to polysLength entries, which may be smaller or larger - * than the return value. - * - * Takes O(N*P) where P is the number of resultant polygons, N is triangle - * count. - * - * The final polygon list will not necessarily be minimal, though in - * practice it works fairly well. - */ -int32 PolygonizeTriangles(b2Triangle* triangulated, int32 triangulatedLength, b2Polygon* polys, int32 polysLength) { - int32 polyIndex = 0; - - if (triangulatedLength <= 0) { - return 0; - } - else { - int* covered = new int[triangulatedLength]; - for (int32 i = 0; i < triangulatedLength; ++i) { - covered[i] = 0; - //Check here for degenerate triangles - if ( ( (triangulated[i].x[0] == triangulated[i].x[1]) && (triangulated[i].y[0] == triangulated[i].y[1]) ) - || ( (triangulated[i].x[1] == triangulated[i].x[2]) && (triangulated[i].y[1] == triangulated[i].y[2]) ) - || ( (triangulated[i].x[0] == triangulated[i].x[2]) && (triangulated[i].y[0] == triangulated[i].y[2]) ) ) { - covered[i] = 1; - } - } - - bool notDone = true; - while (notDone) { - int32 currTri = -1; - for (int32 i = 0; i < triangulatedLength; ++i) { - if (covered[i]) - continue; - currTri = i; - break; - } - if (currTri == -1) { - notDone = false; - } - else { - b2Polygon poly(triangulated[currTri]); - covered[currTri] = 1; - int32 index = 0; - for (int32 i = 0; i < 2*triangulatedLength; ++i,++index) { - while (index >= triangulatedLength) index -= triangulatedLength; - if (covered[index]) { - continue; - } - b2Polygon* newP = poly.Add(triangulated[index]); - if (!newP) { - continue; - } - if (newP->nVertices > b2Polygon::maxVerticesPerPolygon) { - delete newP; - newP = NULL; - continue; - } - if (newP->IsConvex()) { //Or should it be IsUsable? Maybe re-write IsConvex to apply the angle threshold from Box2d - poly.Set(*newP); - delete newP; - newP = NULL; - covered[index] = 1; - } else { - delete newP; - newP = NULL; - } - } - if (polyIndex < polysLength){ - poly.MergeParallelEdges(b2_angularSlop); - //If identical points are present, a triangle gets - //borked by the MergeParallelEdges function, hence - //the vertex number check - if (poly.nVertices >= 3) polys[polyIndex].Set(poly); - //else printf("Skipping corrupt poly\n"); - } - if (poly.nVertices >= 3) polyIndex++; //Must be outside (polyIndex < polysLength) test - } - //printf("MEMCHECK: %d\n",_CrtCheckMemory()); - } - delete[] covered; - } - return polyIndex; -} - - /** - * Checks if vertex i is the tip of an ear in polygon defined by xv[] and - * yv[]. - * - * Assumes clockwise orientation of polygon...ick - */ -bool IsEar(int32 i, float32* xv, float32* yv, int32 xvLength) { - float32 dx0, dy0, dx1, dy1; - dx0 = dy0 = dx1 = dy1 = 0; - if (i >= xvLength || i < 0 || xvLength < 3) { - return false; - } - int32 upper = i + 1; - int32 lower = i - 1; - if (i == 0) { - dx0 = xv[0] - xv[xvLength - 1]; - dy0 = yv[0] - yv[xvLength - 1]; - dx1 = xv[1] - xv[0]; - dy1 = yv[1] - yv[0]; - lower = xvLength - 1; - } - else if (i == xvLength - 1) { - dx0 = xv[i] - xv[i - 1]; - dy0 = yv[i] - yv[i - 1]; - dx1 = xv[0] - xv[i]; - dy1 = yv[0] - yv[i]; - upper = 0; - } - else { - dx0 = xv[i] - xv[i - 1]; - dy0 = yv[i] - yv[i - 1]; - dx1 = xv[i + 1] - xv[i]; - dy1 = yv[i + 1] - yv[i]; - } - float32 cross = dx0 * dy1 - dx1 * dy0; - if (cross > 0) - return false; - b2Triangle myTri(xv[i], yv[i], xv[upper], yv[upper], - xv[lower], yv[lower]); - for (int32 j = 0; j < xvLength; ++j) { - if (j == i || j == lower || j == upper) - continue; - if (myTri.IsInside(xv[j], yv[j])) - return false; - } - return true; -} - -void ReversePolygon(b2Polygon& p){ - ReversePolygon(p.x,p.y,p.nVertices); -} - -void ReversePolygon(float* x, float* y, int n) { - if (n == 1) - return; - int32 low = 0; - int32 high = n - 1; - while (low < high) { - float32 buffer = x[low]; - x[low] = x[high]; - x[high] = buffer; - buffer = y[low]; - y[low] = y[high]; - y[high] = buffer; - ++low; - --high; - } -} - - /** - * Decomposes a non-convex polygon into a number of convex polygons, up - * to maxPolys (remaining pieces are thrown out, but the total number - * is returned, so the return value can be greater than maxPolys). - * - * Each resulting polygon will have no more than maxVerticesPerPolygon - * vertices (set to b2MaxPolyVertices by default, though you can change - * this). - * - * Returns -1 if operation fails (usually due to self-intersection of - * polygon). - */ -int32 DecomposeConvex(b2Polygon* p, b2Polygon* results, int32 maxPolys) { - if (p->nVertices < 3) return 0; - - b2Triangle* triangulated = new b2Triangle[p->nVertices - 2]; - int32 nTri; - if (p->IsCCW()) { - //printf("It is ccw \n"); - b2Polygon tempP; - tempP.Set(*p); - ReversePolygon(tempP.x, tempP.y, tempP.nVertices); - nTri = TriangulatePolygon(tempP.x, tempP.y, tempP.nVertices, triangulated); - //ReversePolygon(p->x, p->y, p->nVertices); //reset orientation - } else { - //printf("It is not ccw \n"); - nTri = TriangulatePolygon(p->x, p->y, p->nVertices, triangulated); - } - if (nTri < 1) { - //Still no luck? Oh well... - delete[] triangulated; - return -1; - } - int32 nPolys = PolygonizeTriangles(triangulated, nTri, results, maxPolys); - delete[] triangulated; - return nPolys; -} - - /** - * Decomposes a polygon into convex polygons and adds all pieces to a b2BodyDef - * using a prototype b2PolyDef. All fields of the prototype are used for every - * shape except the vertices (friction, restitution, density, etc). - * - * If you want finer control, you'll have to add everything by hand. - * - * This is the simplest method to add a complicated polygon to a body. - * - * Until Box2D's b2BodyDef behavior changes, this method returns a pointer to - * a heap-allocated array of b2PolyDefs, which must be deleted by the user - * after the b2BodyDef is added to the world. - */ -#if 0 -void DecomposeConvexAndAddTo(b2Polygon* p, b2Body* bd, b2FixtureDef* prototype) { - - if (p->nVertices < 3) return; - b2Polygon* decomposed = new b2Polygon[p->nVertices - 2]; //maximum number of polys - int32 nPolys = DecomposeConvex(p, decomposed, p->nVertices - 2); - //printf("npolys: %d",nPolys); - b2FixtureDef* pdarray = new b2FixtureDef[2*p->nVertices];//extra space in case of splits - int32 extra = 0; - for (int32 i = 0; i < nPolys; ++i) { - b2FixtureDef* toAdd = &pdarray[i+extra]; - *toAdd = *prototype; - //Hmm, shouldn't have to do all this... - /* - toAdd->type = prototype->type; - toAdd->friction = prototype->friction; - toAdd->restitution = prototype->restitution; - toAdd->density = prototype->density; - toAdd->userData = prototype->userData; - toAdd->categoryBits = prototype->categoryBits; - toAdd->maskBits = prototype->maskBits; - toAdd->groupIndex = prototype->groupIndex;//*/ - //decomposed[i].print(); - b2Polygon curr = decomposed[i]; - //TODO ewjordan: move this triangle handling to a better place so that - //it happens even if this convenience function is not called. - if (curr.nVertices == 3){ - //Check here for near-parallel edges, since we can't - //handle this in merge routine - for (int j=0; j<3; ++j){ - int32 lower = (j == 0) ? (curr.nVertices - 1) : (j - 1); - int32 middle = j; - int32 upper = (j == curr.nVertices - 1) ? (0) : (j + 1); - float32 dx0 = curr.x[middle] - curr.x[lower]; float32 dy0 = curr.y[middle] - curr.y[lower]; - float32 dx1 = curr.x[upper] - curr.x[middle]; float32 dy1 = curr.y[upper] - curr.y[middle]; - float32 norm0 = sqrtf(dx0*dx0+dy0*dy0); float32 norm1 = sqrtf(dx1*dx1+dy1*dy1); - if ( !(norm0 > 0.0f && norm1 > 0.0f) ) { - //Identical points, don't do anything! - goto Skip; - } - dx0 /= norm0; dy0 /= norm0; - dx1 /= norm1; dy1 /= norm1; - float32 cross = dx0 * dy1 - dx1 * dy0; - float32 dot = dx0*dx1 + dy0*dy1; - if (fabs(cross) < b2_angularSlop && dot > 0) { - //Angle too close, split the triangle across from this point. - //This is guaranteed to result in two triangles that satify - //the tolerance (one of the angles is 90 degrees) - float32 dx2 = curr.x[lower] - curr.x[upper]; float32 dy2 = curr.y[lower] - curr.y[upper]; - float32 norm2 = sqrtf(dx2*dx2+dy2*dy2); - if (norm2 == 0.0f) { - goto Skip; - } - dx2 /= norm2; dy2 /= norm2; - float32 thisArea = curr.GetArea(); - float32 thisHeight = 2.0f * thisArea / norm2; - float32 buffer2 = dx2; - dx2 = dy2; dy2 = -buffer2; - //Make two new polygons - //printf("dx2: %f, dy2: %f, thisHeight: %f, middle: %d\n",dx2,dy2,thisHeight,middle); - float32 newX1[3] = { curr.x[middle]+dx2*thisHeight, curr.x[lower], curr.x[middle] }; - float32 newY1[3] = { curr.y[middle]+dy2*thisHeight, curr.y[lower], curr.y[middle] }; - float32 newX2[3] = { newX1[0], curr.x[middle], curr.x[upper] }; - float32 newY2[3] = { newY1[0], curr.y[middle], curr.y[upper] }; - b2Polygon p1(newX1,newY1,3); - b2Polygon p2(newX2,newY2,3); - if (p1.IsUsable()){ - p1.AddTo(*toAdd); - - - bd->CreateFixture(toAdd); - ++extra; - } else if (B2_POLYGON_REPORT_ERRORS){ - printf("Didn't add unusable polygon. Dumping vertices:\n"); - p1.print(); - } - if (p2.IsUsable()){ - p2.AddTo(pdarray[i+extra]); - - bd->CreateFixture(toAdd); - } else if (B2_POLYGON_REPORT_ERRORS){ - printf("Didn't add unusable polygon. Dumping vertices:\n"); - p2.print(); - } - goto Skip; - } - } - - } - if (decomposed[i].IsUsable()){ - decomposed[i].AddTo(*toAdd); - - bd->CreateFixture((const b2FixtureDef*)toAdd); - } else if (B2_POLYGON_REPORT_ERRORS){ - printf("Didn't add unusable polygon. Dumping vertices:\n"); - decomposed[i].print(); - } -Skip: - ; - } - delete[] pdarray; - delete[] decomposed; - return;// pdarray; //needs to be deleted after body is created -} - -#endif - /** - * Find the convex hull of a point cloud using "Gift-wrap" algorithm - start - * with an extremal point, and walk around the outside edge by testing - * angles. - * - * Runs in O(N*S) time where S is number of sides of resulting polygon. - * Worst case: point cloud is all vertices of convex polygon -> O(N^2). - * - * There may be faster algorithms to do this, should you need one - - * this is just the simplest. You can get O(N log N) expected time if you - * try, I think, and O(N) if you restrict inputs to simple polygons. - * - * Returns null if number of vertices passed is less than 3. - * - * Results should be passed through convex decomposition afterwards - * to ensure that each shape has few enough points to be used in Box2d. - * - * FIXME?: May be buggy with colinear points on hull. Couldn't find a test - * case that resulted in wrong behavior. If one turns up, the solution is to - * supplement angle check with an equality resolver that always picks the - * longer edge. I think the current solution is working, though it sometimes - * creates an extra edge along a line. - */ - -b2Polygon ConvexHull(b2Vec2* v, int nVert) { - float32* cloudX = new float32[nVert]; - float32* cloudY = new float32[nVert]; - for (int32 i = 0; i < nVert; ++i) { - cloudX[i] = v[i].x; - cloudY[i] = v[i].y; - } - b2Polygon result = ConvexHull(cloudX, cloudY, nVert); - delete[] cloudX; - delete[] cloudY; - return result; -} - -b2Polygon ConvexHull(float32* cloudX, float32* cloudY, int32 nVert) { - b2Assert(nVert > 2); - int32* edgeList = new int32[nVert]; - int32 numEdges = 0; - - float32 minY = 1e10; - int32 minYIndex = nVert; - for (int32 i = 0; i < nVert; ++i) { - if (cloudY[i] < minY) { - minY = cloudY[i]; - minYIndex = i; - } - } - - int32 startIndex = minYIndex; - int32 winIndex = -1; - float32 dx = -1.0f; - float32 dy = 0.0f; - while (winIndex != minYIndex) { - float32 newdx = 0.0f; - float32 newdy = 0.0f; - float32 maxDot = -2.0f; - for (int32 i = 0; i < nVert; ++i) { - if (i == startIndex) - continue; - newdx = cloudX[i] - cloudX[startIndex]; - newdy = cloudY[i] - cloudY[startIndex]; - float32 nrm = sqrtf(newdx * newdx + newdy * newdy); - nrm = (nrm == 0.0f) ? 1.0f : nrm; - newdx /= nrm; - newdy /= nrm; - - //Cross and dot products act as proxy for angle - //without requiring inverse trig. - //FIXED: don't need cross test - //float32 newCross = newdx * dy - newdy * dx; - float32 newDot = newdx * dx + newdy * dy; - if (newDot > maxDot) {//newCross >= 0.0f && newDot > maxDot) { - maxDot = newDot; - winIndex = i; - } - } - edgeList[numEdges++] = winIndex; - dx = cloudX[winIndex] - cloudX[startIndex]; - dy = cloudY[winIndex] - cloudY[startIndex]; - float32 nrm = sqrtf(dx * dx + dy * dy); - nrm = (nrm == 0.0f) ? 1.0f : nrm; - dx /= nrm; - dy /= nrm; - startIndex = winIndex; - } - - float32* xres = new float32[numEdges]; - float32* yres = new float32[numEdges]; - for (int32 i = 0; i < numEdges; i++) { - xres[i] = cloudX[edgeList[i]]; - yres[i] = cloudY[edgeList[i]]; - //("%f, %f\n",xres[i],yres[i]); - } - - b2Polygon returnVal(xres, yres, numEdges); - - delete[] xres; - delete[] yres; - delete[] edgeList; - returnVal.MergeParallelEdges(b2_angularSlop); - return returnVal; -} - - -/* - * Given sines and cosines, tells if A's angle is less than B's on -Pi, Pi - * (in other words, is A "righter" than B) - */ -bool IsRighter(float32 sinA, float32 cosA, float32 sinB, float32 cosB){ - if (sinA < 0){ - if (sinB > 0 || cosA <= cosB) return true; - else return false; - } else { - if (sinB < 0 || cosA <= cosB) return false; - else return true; - } -} - -//Fix for obnoxious behavior for the % operator for negative numbers... -int32 remainder(int32 x, int32 modulus){ - int32 rem = x % modulus; - while (rem < 0){ - rem += modulus; - } - return rem; -} - -/* -Method: -Start at vertex with minimum y (pick maximum x one if there are two). -We aim our "lastDir" vector at (1.0, 0) -We look at the two rays going off from our start vertex, and follow whichever -has the smallest angle (in -Pi -> Pi) wrt lastDir ("rightest" turn) - -Loop until we hit starting vertex: - -We add our current vertex to the list. -We check the seg from current vertex to next vertex for intersections - - if no intersections, follow to next vertex and continue - - if intersections, pick one with minimum distance - - if more than one, pick one with "rightest" next point (two possibilities for each) - -*/ - -b2Polygon TraceEdge(b2Polygon* p){ - b2PolyNode* nodes = new b2PolyNode[p->nVertices*p->nVertices];//overkill, but sufficient (order of mag. is right) - int32 nNodes = 0; - - //Add base nodes (raw outline) - for (int32 i=0; i < p->nVertices; ++i){ - b2Vec2 pos(p->x[i],p->y[i]); - nodes[i].position = pos; - ++nNodes; - int32 iplus = (i==p->nVertices-1)?0:i+1; - int32 iminus = (i==0)?p->nVertices-1:i-1; - nodes[i].AddConnection(nodes[iplus]); - nodes[i].AddConnection(nodes[iminus]); - } - - //Process intersection nodes - horribly inefficient - bool dirty = true; - int counter = 0; - while (dirty){ - dirty = false; - for (int32 i=0; i < nNodes; ++i){ - for (int32 j=0; j < nodes[i].nConnected; ++j){ - for (int32 k=0; k < nNodes; ++k){ - if (k==i || &nodes[k] == nodes[i].connected[j]) continue; - for (int32 l=0; l < nodes[k].nConnected; ++l){ - - if ( nodes[k].connected[l] == nodes[i].connected[j] || - nodes[k].connected[l] == &nodes[i]) continue; - //Check intersection - b2Vec2 intersectPt; - //if (counter > 100) printf("checking intersection: %d, %d, %d, %d\n",i,j,k,l); - bool crosses = intersect(nodes[i].position,nodes[i].connected[j]->position, - nodes[k].position,nodes[k].connected[l]->position, - intersectPt); - if (crosses){ - /*if (counter > 100) { - printf("Found crossing at %f, %f\n",intersectPt.x, intersectPt.y); - printf("Locations: %f,%f - %f,%f | %f,%f - %f,%f\n", - nodes[i].position.x, nodes[i].position.y, - nodes[i].connected[j]->position.x, nodes[i].connected[j]->position.y, - nodes[k].position.x,nodes[k].position.y, - nodes[k].connected[l]->position.x,nodes[k].connected[l]->position.y); - printf("Memory addresses: %d, %d, %d, %d\n",(int)&nodes[i],(int)nodes[i].connected[j],(int)&nodes[k],(int)nodes[k].connected[l]); - }*/ - dirty = true; - //Destroy and re-hook connections at crossing point - b2PolyNode* connj = nodes[i].connected[j]; - b2PolyNode* connl = nodes[k].connected[l]; - nodes[i].connected[j]->RemoveConnection(nodes[i]); - nodes[i].RemoveConnection(*connj); - nodes[k].connected[l]->RemoveConnection(nodes[k]); - nodes[k].RemoveConnection(*connl); - nodes[nNodes] = b2PolyNode(intersectPt); - nodes[nNodes].AddConnection(nodes[i]); - nodes[i].AddConnection(nodes[nNodes]); - nodes[nNodes].AddConnection(nodes[k]); - nodes[k].AddConnection(nodes[nNodes]); - nodes[nNodes].AddConnection(*connj); - connj->AddConnection(nodes[nNodes]); - nodes[nNodes].AddConnection(*connl); - connl->AddConnection(nodes[nNodes]); - ++nNodes; - goto SkipOut; - } - } - } - } - } - SkipOut: - ++counter; - //if (counter > 100) printf("Counter: %d\n",counter); - } - - /* - // Debugging: check for connection consistency - for (int32 i=0; i<nNodes; ++i) { - int32 nConn = nodes[i].nConnected; - for (int32 j=0; j<nConn; ++j) { - if (nodes[i].connected[j]->nConnected == 0) b2Assert(false); - b2PolyNode* connect = nodes[i].connected[j]; - bool found = false; - for (int32 k=0; k<connect->nConnected; ++k) { - if (connect->connected[k] == &nodes[i]) found = true; - } - b2Assert(found); - } - }*/ - - //Collapse duplicate points - bool foundDupe = true; - int nActive = nNodes; - while (foundDupe){ - foundDupe = false; - for (int32 i=0; i < nNodes; ++i){ - if (nodes[i].nConnected == 0) continue; - for (int32 j=i+1; j < nNodes; ++j){ - if (nodes[j].nConnected == 0) continue; - b2Vec2 diff = nodes[i].position - nodes[j].position; - if (diff.LengthSquared() <= COLLAPSE_DIST_SQR){ - if (nActive <= 3) { - // -- GODOT start -- - delete[] nodes; - // -- GODOT end -- - return b2Polygon(); - } - //printf("Found dupe, %d left\n",nActive); - --nActive; - foundDupe = true; - b2PolyNode* inode = &nodes[i]; - b2PolyNode* jnode = &nodes[j]; - //Move all of j's connections to i, and orphan j - int32 njConn = jnode->nConnected; - for (int32 k=0; k < njConn; ++k){ - b2PolyNode* knode = jnode->connected[k]; - b2Assert(knode != jnode); - if (knode != inode) { - inode->AddConnection(*knode); - knode->AddConnection(*inode); - } - knode->RemoveConnection(*jnode); - //printf("knode %d on node %d now has %d connections\n",k,j,knode->nConnected); - //printf("Found duplicate point.\n"); - } - /* - printf("Orphaning node at address %d\n",(int)jnode); - for (int32 k=0; k<njConn; ++k) { - if (jnode->connected[k]->IsConnectedTo(*jnode)) printf("Problem!!!\n"); - } - for (int32 k=0; k < njConn; ++k){ - jnode->RemoveConnectionByIndex(k); - } - */ - jnode->nConnected = 0; - } - } - } - } - - /* - // Debugging: check for connection consistency - for (int32 i=0; i<nNodes; ++i) { - int32 nConn = nodes[i].nConnected; - printf("Node %d has %d connections\n",i,nConn); - for (int32 j=0; j<nConn; ++j) { - if (nodes[i].connected[j]->nConnected == 0) { - printf("Problem with node %d connection at address %d\n",i,(int)(nodes[i].connected[j])); - b2Assert(false); - } - b2PolyNode* connect = nodes[i].connected[j]; - bool found = false; - for (int32 k=0; k<connect->nConnected; ++k) { - if (connect->connected[k] == &nodes[i]) found = true; - } - if (!found) printf("Connection %d (of %d) on node %d (of %d) did not have reciprocal connection.\n",j,nConn,i,nNodes); - b2Assert(found); - } - }//*/ - - //Now walk the edge of the list - - //Find node with minimum y value (max x if equal) - float32 minY = 1e10; - float32 maxX = -1e10; - int32 minYIndex = -1; - for (int32 i = 0; i < nNodes; ++i) { - if (nodes[i].position.y < minY && nodes[i].nConnected > 1) { - minY = nodes[i].position.y; - minYIndex = i; - maxX = nodes[i].position.x; - } else if (nodes[i].position.y == minY && nodes[i].position.x > maxX && nodes[i].nConnected > 1) { - minYIndex = i; - maxX = nodes[i].position.x; - } - } - - b2Vec2 origDir(1.0f,0.0f); - b2Vec2* resultVecs = new b2Vec2[4*nNodes];// nodes may be visited more than once, unfortunately - change to growable array! - int32 nResultVecs = 0; - b2PolyNode* currentNode = &nodes[minYIndex]; - b2PolyNode* startNode = currentNode; - b2Assert(currentNode->nConnected > 0); - b2PolyNode* nextNode = currentNode->GetRightestConnection(origDir); - if (!nextNode) goto CleanUp; // Borked, clean up our mess and return - resultVecs[0] = startNode->position; - ++nResultVecs; - while (nextNode != startNode){ - if (nResultVecs > 4*nNodes){ - /* - printf("%d, %d, %d\n",(int)startNode,(int)currentNode,(int)nextNode); - printf("%f, %f -> %f, %f\n",currentNode->position.x,currentNode->position.y, nextNode->position.x, nextNode->position.y); - p->printFormatted(); - printf("Dumping connection graph: \n"); - for (int32 i=0; i<nNodes; ++i) { - printf("nodex[%d] = %f; nodey[%d] = %f;\n",i,nodes[i].position.x,i,nodes[i].position.y); - printf("//connected to\n"); - for (int32 j=0; j<nodes[i].nConnected; ++j) { - printf("connx[%d][%d] = %f; conny[%d][%d] = %f;\n",i,j,nodes[i].connected[j]->position.x, i,j,nodes[i].connected[j]->position.y); - } - } - printf("Dumping results thus far: \n"); - for (int32 i=0; i<nResultVecs; ++i) { - printf("x[%d]=map(%f,-3,3,0,width); y[%d] = map(%f,-3,3,height,0);\n",i,resultVecs[i].x,i,resultVecs[i].y); - } - //*/ - b2Assert(false); //nodes should never be visited four times apiece (proof?), so we've probably hit a loop...crap - } - resultVecs[nResultVecs++] = nextNode->position; - b2PolyNode* oldNode = currentNode; - currentNode = nextNode; - //printf("Old node connections = %d; address %d\n",oldNode->nConnected, (int)oldNode); - //printf("Current node connections = %d; address %d\n",currentNode->nConnected, (int)currentNode); - nextNode = currentNode->GetRightestConnection(oldNode); - if (!nextNode) goto CleanUp; // There was a problem, so jump out of the loop and use whatever garbage we've generated so far - //printf("nextNode address: %d\n",(int)nextNode); - } - - CleanUp: - - float32* xres = new float32[nResultVecs]; - float32* yres = new float32[nResultVecs]; - for (int32 i=0; i<nResultVecs; ++i){ - xres[i] = resultVecs[i].x; - yres[i] = resultVecs[i].y; - } - b2Polygon retval(xres,yres,nResultVecs); - delete[] resultVecs; - delete[] yres; - delete[] xres; - delete[] nodes; - return retval; -} - -b2PolyNode::b2PolyNode(){ - nConnected = 0; - visited = false; -} -b2PolyNode::b2PolyNode(b2Vec2& pos){ - position = pos; - nConnected = 0; - visited = false; -} - -void b2PolyNode::AddConnection(b2PolyNode& toMe){ - b2Assert(nConnected < MAX_CONNECTED); - // Ignore duplicate additions - for (int32 i=0; i<nConnected; ++i) { - if (connected[i] == &toMe) return; - } - connected[nConnected] = &toMe; - ++nConnected; -} - -void b2PolyNode::RemoveConnection(b2PolyNode& fromMe){ - bool isFound = false; - int32 foundIndex = -1; - for (int32 i=0; i<nConnected; ++i){ - if (&fromMe == connected[i]) {//.position == connected[i]->position){ - isFound = true; - foundIndex = i; - break; - } - } - b2Assert(isFound); - --nConnected; - //printf("nConnected: %d\n",nConnected); - for (int32 i=foundIndex; i < nConnected; ++i){ - connected[i] = connected[i+1]; - } -} -void b2PolyNode::RemoveConnectionByIndex(int32 index){ - --nConnected; - //printf("New nConnected = %d\n",nConnected); - for (int32 i=index; i < nConnected; ++i){ - connected[i] = connected[i+1]; - } -} -bool b2PolyNode::IsConnectedTo(b2PolyNode& me){ - bool isFound = false; - for (int32 i=0; i<nConnected; ++i){ - if (&me == connected[i]) {//.position == connected[i]->position){ - isFound = true; - break; - } - } - return isFound; -} -b2PolyNode* b2PolyNode::GetRightestConnection(b2PolyNode* incoming){ - if (nConnected == 0) b2Assert(false); // This means the connection graph is inconsistent - if (nConnected == 1) { - //b2Assert(false); - // Because of the possibility of collapsing nearby points, - // we may end up with "spider legs" dangling off of a region. - // The correct behavior here is to turn around. - return incoming; - } - b2Vec2 inDir = position - incoming->position; - float32 inLength = inDir.Normalize(); - b2Assert(inLength > CMP_EPSILON); - - b2PolyNode* result = NULL; - for (int32 i=0; i<nConnected; ++i){ - if (connected[i] == incoming) continue; - b2Vec2 testDir = connected[i]->position - position; - float32 testLengthSqr = testDir.LengthSquared(); - testDir.Normalize(); - /* - if (testLengthSqr < COLLAPSE_DIST_SQR) { - printf("Problem with connection %d\n",i); - printf("This node has %d connections\n",nConnected); - printf("That one has %d\n",connected[i]->nConnected); - if (this == connected[i]) printf("This points at itself.\n"); - }*/ - b2Assert (testLengthSqr >= COLLAPSE_DIST_SQR); - float32 myCos = b2Dot(inDir,testDir); - float32 mySin = b2Cross(inDir,testDir); - if (result){ - b2Vec2 resultDir = result->position - position; - resultDir.Normalize(); - float32 resCos = b2Dot(inDir,resultDir); - float32 resSin = b2Cross(inDir,resultDir); - if (IsRighter(mySin,myCos,resSin,resCos)){ - result = connected[i]; - } - } else{ - result = connected[i]; - } - } - if (B2_POLYGON_REPORT_ERRORS && !result) { - printf("nConnected = %d\n",nConnected); - for (int32 i=0; i<nConnected; ++i) { - printf("connected[%d] @ %d\n",i,0);//(int)connected[i]); - } - } - b2Assert(result); - - return result; -} - -b2PolyNode* b2PolyNode::GetRightestConnection(b2Vec2& incomingDir){ - b2Vec2 diff = position-incomingDir; - b2PolyNode temp(diff); - b2PolyNode* res = GetRightestConnection(&temp); - b2Assert(res); - return res; -} -} diff --git a/thirdparty/b2d_convexdecomp/b2Polygon.h b/thirdparty/b2d_convexdecomp/b2Polygon.h deleted file mode 100644 index c466e28f7e..0000000000 --- a/thirdparty/b2d_convexdecomp/b2Polygon.h +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright (c) 2007 Eric Jordan - * - * This software is provided 'as-is', without any express or implied - * warranty. In no event will the authors be held liable for any damages - * arising from the use of this software. - * Permission is granted to anyone to use this software for any purpose, - * including commercial applications, and to alter it and redistribute it - * freely, subject to the following restrictions: - * 1. The origin of this software must not be misrepresented; you must not - * claim that you wrote the original software. If you use this software - * in a product, an acknowledgment in the product documentation would be - * appreciated but is not required. - * 2. Altered source versions must be plainly marked as such, and must not be - * misrepresented as being the original software. - * 3. This notice may not be removed or altered from any source distribution. - */ - -#ifndef B2_POLYGON_H -#define B2_POLYGON_H - -#include "b2Triangle.h" -#include "stdio.h" -#include <string.h> -#include <limits.h> -namespace b2ConvexDecomp { - -static bool B2_POLYGON_REPORT_ERRORS = false; - -class b2Polygon; - -int32 remainder(int32 x, int32 modulus); -int32 TriangulatePolygon(float32* xv, float32* yv, int32 vNum, b2Triangle* results); -bool IsEar(int32 i, float32* xv, float32* yv, int32 xvLength); //Not for external use -int32 PolygonizeTriangles(b2Triangle* triangulated, int32 triangulatedLength, b2Polygon* polys, int32 polysLength); -int32 DecomposeConvex(b2Polygon* p, b2Polygon* results, int32 maxPolys); -//void DecomposeConvexAndAddTo(b2Polygon* p, b2Body* bd, b2FixtureDef* prototype); - -void ReversePolygon(float32* x, float32* y, int n); - -b2Polygon TraceEdge(b2Polygon* p); //For use with self-intersecting polygons, finds outline - -class b2Polygon { - -public: - const static int32 maxVerticesPerPolygon = b2_maxPolygonVertices; - - float32* x; //vertex arrays - float32* y; - int32 nVertices; - - float32 area; - bool areaIsSet; - - b2Polygon(float32* _x, float32* _y, int32 nVert); - b2Polygon(b2Vec2* v, int32 nVert); - b2Polygon(); - ~b2Polygon(); - - float32 GetArea(); - - void MergeParallelEdges(float32 tolerance); - b2Vec2* GetVertexVecs(); - b2Polygon(b2Triangle& t); - void Set(const b2Polygon& p); - bool IsConvex(); - bool IsCCW(); - bool IsUsable(bool printError); - bool IsUsable(); - bool IsSimple(); - // void AddTo(b2FixtureDef& pd); - - b2Polygon* Add(b2Triangle& t); - - void print(){ - printFormatted(); - /* - for (int32 i=0; i<nVertices; ++i){ - printf("i: %d, x:%f, y:%f\n",i,x[i],y[i]); - } - */ - } - - void printFormatted(){ - printf("float xv[] = {"); - for (int32 i=0; i<nVertices; ++i){ - printf("%ff,",x[i]); - } - printf("};\nfloat yv[] = {"); - for (int32 i=0; i<nVertices; ++i){ - printf("%ff,",y[i]); - } - printf("};\n"); - } - - b2Polygon(const b2Polygon& p){ - nVertices = p.nVertices; - area = p.area; - areaIsSet = p.areaIsSet; - x = new float32[nVertices]; - y = new float32[nVertices]; - memcpy(x, p.x, nVertices * sizeof(float32)); - memcpy(y, p.y, nVertices * sizeof(float32)); - } - - -}; - -const int32 MAX_CONNECTED = 32; -const float32 COLLAPSE_DIST_SQR = CMP_EPSILON*CMP_EPSILON;//0.1f;//1000*CMP_EPSILON*1000*CMP_EPSILON; - -class b2PolyNode{ -public: - b2Vec2 position; - b2PolyNode* connected[MAX_CONNECTED]; - int32 nConnected; - bool visited; - - b2PolyNode(b2Vec2& pos); - b2PolyNode(); - void AddConnection(b2PolyNode& toMe); - void RemoveConnection(b2PolyNode& fromMe); - void RemoveConnectionByIndex(int32 index); - bool IsConnectedTo(b2PolyNode& me); - b2PolyNode* GetRightestConnection(b2PolyNode* incoming); - b2PolyNode* GetRightestConnection(b2Vec2& incomingDir); -}; - - -b2Polygon ConvexHull(b2Vec2* v, int nVert); -b2Polygon ConvexHull(float32* cloudX, float32* cloudY, int32 nVert); -} -#endif diff --git a/thirdparty/b2d_convexdecomp/b2Triangle.cpp b/thirdparty/b2d_convexdecomp/b2Triangle.cpp deleted file mode 100644 index a0a30b9407..0000000000 --- a/thirdparty/b2d_convexdecomp/b2Triangle.cpp +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (c) 2007 Eric Jordan - * - * This software is provided 'as-is', without any express or implied - * warranty. In no event will the authors be held liable for any damages - * arising from the use of this software. - * Permission is granted to anyone to use this software for any purpose, - * including commercial applications, and to alter it and redistribute it - * freely, subject to the following restrictions: - * 1. The origin of this software must not be misrepresented; you must not - * claim that you wrote the original software. If you use this software - * in a product, an acknowledgment in the product documentation would be - * appreciated but is not required. - * 2. Altered source versions must be plainly marked as such, and must not be - * misrepresented as being the original software. - * 3. This notice may not be removed or altered from any source distribution. - */ - -#include "b2Triangle.h" - -namespace b2ConvexDecomp { - -//Constructor automatically fixes orientation to ccw -b2Triangle::b2Triangle(float32 x1, float32 y1, float32 x2, float32 y2, float32 x3, float32 y3){ - x = new float32[3]; - y = new float32[3]; - float32 dx1 = x2-x1; - float32 dx2 = x3-x1; - float32 dy1 = y2-y1; - float32 dy2 = y3-y1; - float32 cross = dx1*dy2-dx2*dy1; - bool ccw = (cross>0); - if (ccw){ - x[0] = x1; x[1] = x2; x[2] = x3; - y[0] = y1; y[1] = y2; y[2] = y3; - } else{ - x[0] = x1; x[1] = x3; x[2] = x2; - y[0] = y1; y[1] = y3; y[2] = y2; - } -} - -b2Triangle::b2Triangle(){ - x = new float32[3]; - y = new float32[3]; -} - -b2Triangle::~b2Triangle(){ - delete[] x; - delete[] y; -} - -void b2Triangle::Set(const b2Triangle& toMe) { - for (int32 i=0; i<3; ++i) { - x[i] = toMe.x[i]; - y[i] = toMe.y[i]; - } -} - -bool b2Triangle::IsInside(float32 _x, float32 _y){ - if (_x < x[0] && _x < x[1] && _x < x[2]) return false; - if (_x > x[0] && _x > x[1] && _x > x[2]) return false; - if (_y < y[0] && _y < y[1] && _y < y[2]) return false; - if (_y > y[0] && _y > y[1] && _y > y[2]) return false; - - float32 vx2 = _x-x[0]; float32 vy2 = _y-y[0]; - float32 vx1 = x[1]-x[0]; float32 vy1 = y[1]-y[0]; - float32 vx0 = x[2]-x[0]; float32 vy0 = y[2]-y[0]; - - float32 dot00 = vx0*vx0+vy0*vy0; - float32 dot01 = vx0*vx1+vy0*vy1; - float32 dot02 = vx0*vx2+vy0*vy2; - float32 dot11 = vx1*vx1+vy1*vy1; - float32 dot12 = vx1*vx2+vy1*vy2; - float32 invDenom = 1.0f / (dot00*dot11 - dot01*dot01); - float32 u = (dot11*dot02 - dot01*dot12)*invDenom; - float32 v = (dot00*dot12 - dot01*dot02)*invDenom; - - return ((u>=0)&&(v>=0)&&(u+v<=1)); -} - - -} diff --git a/thirdparty/b2d_convexdecomp/b2Triangle.h b/thirdparty/b2d_convexdecomp/b2Triangle.h deleted file mode 100644 index 99ab5cba69..0000000000 --- a/thirdparty/b2d_convexdecomp/b2Triangle.h +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (c) 2007 Eric Jordan - * - * This software is provided 'as-is', without any express or implied - * warranty. In no event will the authors be held liable for any damages - * arising from the use of this software. - * Permission is granted to anyone to use this software for any purpose, - * including commercial applications, and to alter it and redistribute it - * freely, subject to the following restrictions: - * 1. The origin of this software must not be misrepresented; you must not - * claim that you wrote the original software. If you use this software - * in a product, an acknowledgment in the product documentation would be - * appreciated but is not required. - * 2. Altered source versions must be plainly marked as such, and must not be - * misrepresented as being the original software. - * 3. This notice may not be removed or altered from any source distribution. - */ - -#ifndef B2_TRIANGLE_H -#define B2_TRIANGLE_H - -#include "b2Glue.h" - -namespace b2ConvexDecomp { - - - -class b2Triangle{ -public: - float* x; - float* y; - b2Triangle(); - b2Triangle(float32 x1, float32 y1, float32 x2, float32 y2, float32 x3, float32 y3); - ~b2Triangle(); - bool IsInside(float32 _x, float32 _y); - void Set(const b2Triangle& toMe); - -}; - -} -#endif diff --git a/thirdparty/enet/enet/godot.h b/thirdparty/enet/enet/godot.h index 9230af428b..4f25ea9c7b 100644 --- a/thirdparty/enet/enet/godot.h +++ b/thirdparty/enet/enet/godot.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/thirdparty/enet/godot.cpp b/thirdparty/enet/godot.cpp index 822a294781..63580b6d1a 100644 --- a/thirdparty/enet/godot.cpp +++ b/thirdparty/enet/godot.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/thirdparty/misc/ifaddrs-android.h b/thirdparty/misc/ifaddrs-android.h index 6e204af26f..8de94324b8 100644 --- a/thirdparty/misc/ifaddrs-android.h +++ b/thirdparty/misc/ifaddrs-android.h @@ -32,6 +32,11 @@ // Fills out a list of ifaddr structs (see below) which contain information // about every network interface available on the host. // See 'man getifaddrs' on Linux or OS X (nb: it is not a POSIX function). +// -- GODOT start -- +#ifdef __cplusplus +extern "C" { +#endif +// -- GODOT end -- struct ifaddrs { struct ifaddrs* ifa_next; char* ifa_name; @@ -40,7 +45,21 @@ struct ifaddrs { struct sockaddr* ifa_netmask; // Real ifaddrs has broadcast, point to point and data members. // We don't need them (yet?). + // -- GODOT start -- + // We never initialize the following members. We only define them to match the ifaddrs struct. + union + { + struct sockaddr *ifu_broadaddr; + struct sockaddr *ifu_dstaddr; + } ifa_ifu; + void *ifa_data; + // -- GODOT end -- }; +// -- GODOT start -- +#ifdef __cplusplus +} +#endif +// -- GODOT end -- int getifaddrs(struct ifaddrs** result); void freeifaddrs(struct ifaddrs* addrs); #endif // TALK_BASE_IFADDRS_ANDROID_H_ diff --git a/thirdparty/vhacd/0003-fix-musl-build.patch b/thirdparty/vhacd/0003-fix-musl-build.patch new file mode 100644 index 0000000000..67af8546f3 --- /dev/null +++ b/thirdparty/vhacd/0003-fix-musl-build.patch @@ -0,0 +1,15 @@ +diff --git a/thirdparty/vhacd/inc/vhacdMutex.h b/thirdparty/vhacd/inc/vhacdMutex.h +index 6b09259200..d587dd6387 100644 +--- a/thirdparty/vhacd/inc/vhacdMutex.h ++++ b/thirdparty/vhacd/inc/vhacdMutex.h +@@ -71,7 +71,9 @@ + #include <pthread.h> + #endif + +-#if defined(__APPLE__) ++// -- GODOT start -- ++#if defined(__APPLE__) || !defined(__GLIBC__) ++// -- GODOT end -- + #define PTHREAD_MUTEX_RECURSIVE_NP PTHREAD_MUTEX_RECURSIVE + #endif + diff --git a/thirdparty/vhacd/0004-fix-uwp-arm-build.patch b/thirdparty/vhacd/0004-fix-uwp-arm-build.patch new file mode 100644 index 0000000000..a5bba3fd7d --- /dev/null +++ b/thirdparty/vhacd/0004-fix-uwp-arm-build.patch @@ -0,0 +1,16 @@ +diff --git a/thirdparty/vhacd/inc/btScalar.h b/thirdparty/vhacd/inc/btScalar.h +index 3999a71521..4c9e0cf7ab 100644 +--- a/thirdparty/vhacd/inc/btScalar.h ++++ b/thirdparty/vhacd/inc/btScalar.h +@@ -72,7 +72,10 @@ inline int32_t btGetVersion() + #define btFsel(a, b, c) __fsel((a), (b), (c)) + #else + +-#if (defined(_WIN32) && (_MSC_VER) && _MSC_VER >= 1400) && (!defined(BT_USE_DOUBLE_PRECISION)) ++// -- GODOT start -- ++//#if (defined(_WIN32) && (_MSC_VER) && _MSC_VER >= 1400) && (!defined(BT_USE_DOUBLE_PRECISION)) ++#if (defined(_WIN32) && (_MSC_VER) && _MSC_VER >= 1400) && (!defined(BT_USE_DOUBLE_PRECISION)) && (!defined(_M_ARM)) ++// -- GODOT end -- + #define BT_USE_SSE + #include <emmintrin.h> + #endif diff --git a/thirdparty/vhacd/inc/btScalar.h b/thirdparty/vhacd/inc/btScalar.h index 3999a71521..4c9e0cf7ab 100644 --- a/thirdparty/vhacd/inc/btScalar.h +++ b/thirdparty/vhacd/inc/btScalar.h @@ -72,7 +72,10 @@ inline int32_t btGetVersion() #define btFsel(a, b, c) __fsel((a), (b), (c)) #else -#if (defined(_WIN32) && (_MSC_VER) && _MSC_VER >= 1400) && (!defined(BT_USE_DOUBLE_PRECISION)) +// -- GODOT start -- +//#if (defined(_WIN32) && (_MSC_VER) && _MSC_VER >= 1400) && (!defined(BT_USE_DOUBLE_PRECISION)) +#if (defined(_WIN32) && (_MSC_VER) && _MSC_VER >= 1400) && (!defined(BT_USE_DOUBLE_PRECISION)) && (!defined(_M_ARM)) +// -- GODOT end -- #define BT_USE_SSE #include <emmintrin.h> #endif diff --git a/thirdparty/vhacd/inc/vhacdMutex.h b/thirdparty/vhacd/inc/vhacdMutex.h index 6b09259200..d587dd6387 100644 --- a/thirdparty/vhacd/inc/vhacdMutex.h +++ b/thirdparty/vhacd/inc/vhacdMutex.h @@ -71,7 +71,9 @@ #include <pthread.h> #endif -#if defined(__APPLE__) +// -- GODOT start -- +#if defined(__APPLE__) || !defined(__GLIBC__) +// -- GODOT end -- #define PTHREAD_MUTEX_RECURSIVE_NP PTHREAD_MUTEX_RECURSIVE #endif diff --git a/thirdparty/xatlas/LICENSE b/thirdparty/xatlas/LICENSE index 94d0c60485..9e61e15fb7 100644 --- a/thirdparty/xatlas/LICENSE +++ b/thirdparty/xatlas/LICENSE @@ -1,14 +1,21 @@ -xatlas -https://github.com/jpcy/xatlas -Copyright (c) 2018 Jonathan Young +MIT License -thekla_atlas -https://github.com/Thekla/thekla_atlas -Copyright (c) 2013 Thekla, Inc -Copyright NVIDIA Corporation 2006 -- Ignacio Castano <icastano@nvidia.com> +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: +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 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. +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.
\ No newline at end of file diff --git a/thirdparty/xatlas/xatlas.cpp b/thirdparty/xatlas/xatlas.cpp index 56794211a6..80cacf9746 100644 --- a/thirdparty/xatlas/xatlas.cpp +++ b/thirdparty/xatlas/xatlas.cpp @@ -33,7 +33,6 @@ https://github.com/brandonpelfrey/Fast-BVH MIT License Copyright (c) 2012 Brandon Pelfrey */ -#include <algorithm> #include <atomic> #include <condition_variable> #include <mutex> @@ -114,22 +113,25 @@ Copyright (c) 2012 Brandon Pelfrey #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_SKIP_PARAMETERIZATION 0 // Use the orthogonal parameterization from segment::Atlas #define XA_CLOSE_HOLES_CHECK_EDGE_INTERSECTION 0 +#define XA_FIX_INTERNAL_BOUNDARY_LOOPS 1 +#define XA_PRINT_CHART_WARNINGS 0 #define XA_DEBUG_HEAP 0 #define XA_DEBUG_SINGLE_CHART 0 #define XA_DEBUG_EXPORT_ATLAS_IMAGES 0 +#define XA_DEBUG_EXPORT_ATLAS_IMAGES_PER_CHART 0 // Export an atlas image after each chart is added. +#define XA_DEBUG_EXPORT_BOUNDARY_GRID 0 +#define XA_DEBUG_EXPORT_TGA (XA_DEBUG_EXPORT_ATLAS_IMAGES || XA_DEBUG_EXPORT_BOUNDARY_GRID) #define XA_DEBUG_EXPORT_OBJ_SOURCE_MESHES 0 #define XA_DEBUG_EXPORT_OBJ_CHART_GROUPS 0 +#define XA_DEBUG_EXPORT_OBJ_PLANAR_REGIONS 0 #define XA_DEBUG_EXPORT_OBJ_CHARTS 0 #define XA_DEBUG_EXPORT_OBJ_BEFORE_FIX_TJUNCTION 0 #define XA_DEBUG_EXPORT_OBJ_CLOSE_HOLES_ERROR 0 -#define XA_DEBUG_EXPORT_OBJ_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 @@ -137,10 +139,10 @@ Copyright (c) 2012 Brandon Pelfrey #define XA_DEBUG_EXPORT_OBJ (0 \ || XA_DEBUG_EXPORT_OBJ_SOURCE_MESHES \ || XA_DEBUG_EXPORT_OBJ_CHART_GROUPS \ + || XA_DEBUG_EXPORT_OBJ_PLANAR_REGIONS \ || XA_DEBUG_EXPORT_OBJ_CHARTS \ || XA_DEBUG_EXPORT_OBJ_BEFORE_FIX_TJUNCTION \ || XA_DEBUG_EXPORT_OBJ_CLOSE_HOLES_ERROR \ - || XA_DEBUG_EXPORT_OBJ_NOT_DISK \ || XA_DEBUG_EXPORT_OBJ_CHARTS_AFTER_PARAMETERIZATION \ || XA_DEBUG_EXPORT_OBJ_INVALID_PARAMETERIZATION \ || XA_DEBUG_EXPORT_OBJ_RECOMPUTED_CHARTS) @@ -166,6 +168,10 @@ struct MemTag enum { Default, + BitImage, + BVH, + FullVector, + Matrix, Mesh, MeshBoundaries, MeshColocals, @@ -174,6 +180,10 @@ struct MemTag MeshNormals, MeshPositions, MeshTexcoords, + SegmentAtlasChartCandidates, + SegmentAtlasChartFaces, + SegmentAtlasMeshData, + SegmentAtlasPlanarRegions, Count }; }; @@ -192,7 +202,7 @@ struct AllocHeader 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 size_t s_allocTotalCount = 0, s_allocTotalSize = 0, s_allocPeakSize = 0, s_allocCount[MemTag::Count] = { 0 }, s_allocTotalTagSize[MemTag::Count] = { 0 }, s_allocPeakTagSize[MemTag::Count] = { 0 }; static uint32_t s_allocId =0 ; static constexpr uint32_t kAllocRedzone = 0x12345678; @@ -245,9 +255,11 @@ static void *Realloc(void *ptr, size_t size, int tag, const char *file, int line s_allocRoot = header; header->next->prev = header; } + s_allocTotalCount++; s_allocTotalSize += size; if (s_allocTotalSize > s_allocPeakSize) s_allocPeakSize = s_allocTotalSize; + s_allocCount[tag]++; s_allocTotalTagSize[tag] += size; if (s_allocTotalTagSize[tag] > s_allocPeakTagSize[tag]) s_allocPeakTagSize[tag] = s_allocTotalTagSize[tag]; @@ -287,9 +299,14 @@ static void ReportLeaks() static void PrintMemoryUsage() { + XA_PRINT("Total allocations: %zu\n", s_allocTotalCount); XA_PRINT("Memory usage: %0.2fMB current, %0.2fMB peak\n", internal::s_allocTotalSize / 1024.0f / 1024.0f, internal::s_allocPeakSize / 1024.0f / 1024.0f); static const char *labels[] = { // Sync with MemTag "Default", + "BitImage", + "BVH", + "FullVector", + "Matrix", "Mesh", "MeshBoundaries", "MeshColocals", @@ -297,10 +314,14 @@ static void PrintMemoryUsage() "MeshIndices", "MeshNormals", "MeshPositions", - "MeshTexcoords" + "MeshTexcoords", + "SegmentAtlasChartCandidates", + "SegmentAtlasChartFaces", + "SegmentAtlasMeshData", + "SegmentAtlasPlanarRegions" }; 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); + XA_PRINT(" %s: %zu allocations, %0.2fMB current, %0.2fMB peak\n", labels[i], internal::s_allocCount[i], internal::s_allocTotalTagSize[i] / 1024.0f / 1024.0f, internal::s_allocPeakTagSize[i] / 1024.0f / 1024.0f); } } @@ -308,7 +329,9 @@ static void PrintMemoryUsage() #else static void *Realloc(void *ptr, size_t size, int /*tag*/, const char * /*file*/, int /*line*/) { - if (ptr && size == 0 && s_free) { + if (size == 0 && !ptr) + return nullptr; + if (size == 0 && s_free) { s_free(ptr); return nullptr; } @@ -333,7 +356,6 @@ struct ProfileData std::atomic<clock_t> addMeshThread; std::atomic<clock_t> addMeshCreateColocals; std::atomic<clock_t> addMeshCreateFaceGroups; - std::atomic<clock_t> addMeshCreateBoundaries; std::atomic<clock_t> addMeshCreateChartGroupsReal; std::atomic<clock_t> addMeshCreateChartGroupsThread; clock_t computeChartsReal; @@ -362,7 +384,6 @@ struct ProfileData clock_t packChartsRasterize; clock_t packChartsDilate; clock_t packChartsFindLocation; - std::atomic<clock_t> packChartsFindLocationThread; clock_t packChartsBlit; clock_t buildOutputMeshes; }; @@ -386,6 +407,7 @@ static double clockToSeconds(clock_t c) static constexpr float kPi = 3.14159265358979323846f; static constexpr float kPi2 = 6.28318530717958647692f; +static constexpr float kPi4 = 12.56637061435917295384f; static constexpr float kEpsilon = 0.0001f; static constexpr float kAreaEpsilon = FLT_EPSILON; static constexpr float kNormalEpsilon = 0.001f; @@ -430,11 +452,9 @@ static T clamp(const T &x, const T &a, const T &b) template <typename T> static void swap(T &a, T &b) { - T temp; - temp = a; + T temp = a; a = b; b = temp; - temp = T(); } union FloatUint32 @@ -620,6 +640,14 @@ static Vector2 normalize(const Vector2 &v, float epsilon) return n; } +static Vector2 normalizeSafe(const Vector2 &v, const Vector2 &fallback, float epsilon) +{ + float l = length(v); + if (isZero(l, epsilon)) + return fallback; + return v * (1.0f / l); +} + static bool equal(const Vector2 &v1, const Vector2 &v2, float epsilon) { return equal(v1.x, v2.x, epsilon) && equal(v1.y, v2.y, epsilon); @@ -640,23 +668,19 @@ static bool isFinite(const Vector2 &v) return isFinite(v.x) && isFinite(v.y); } -// Note, this is the area scaled by 2! -static float triangleArea(const Vector2 &v0, const Vector2 &v1) -{ - return (v0.x * v1.y - v0.y * v1.x); // * 0.5f; -} - 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; + //return (c.x * a.y + a.x * b.y + b.x * c.y - b.x * a.y - c.x * b.y - a.x * c.y) * 0.5f; // That's actually a terrible idea. Small triangles far from the origin can end up producing fairly large floating point // numbers and the results becomes very unstable and dependent on the order of the factors. // Instead, it's preferable to subtract the vertices first, and multiply the resulting small values together. The result // in this case is always much more accurate (as long as the triangle is small) and less dependent of the location of // the triangle. - //return ((a.x - c.x) * (b.y - c.y) - (a.y - c.y) * (b.x - c.x)); // * 0.5f; - return triangleArea(a - c, b - c); + //return ((a.x - c.x) * (b.y - c.y) - (a.y - c.y) * (b.x - c.x)) * 0.5f; + const Vector2 v0 = a - c; + const Vector2 v1 = b - c; + return (v0.x * v1.y - v0.y * v1.x) * 0.5f; } static bool linesIntersect(const Vector2 &a1, const Vector2 &a2, const Vector2 &b1, const Vector2 &b2, float epsilon) @@ -746,11 +770,6 @@ public: float x, y, z; }; -static bool operator!=(const Vector3 &a, const Vector3 &b) -{ - return a.x != b.x || a.y != b.y || a.z != b.z; -} - static Vector3 operator+(const Vector3 &a, const Vector3 &b) { return Vector3(a.x + b.x, a.y + b.y, a.z + b.z); @@ -837,6 +856,33 @@ bool isFinite(const Vector3 &v) } #endif +struct Extents2 +{ + Vector2 min, max; + + void reset() + { + min.x = min.y = FLT_MAX; + max.x = max.y = -FLT_MAX; + } + + void add(Vector2 p) + { + min = xatlas::internal::min(min, p); + max = xatlas::internal::max(max, p); + } + + Vector2 midpoint() const + { + return Vector2(min.x + (max.x - min.x) * 0.5f, min.y + (max.y - min.y) * 0.5f); + } + + static bool intersect(Extents2 e1, Extents2 e2) + { + return e1.min.x <= e2.max.x && e1.max.x >= e2.min.x && e1.min.y <= e2.max.y && e1.max.y >= e2.min.y; + } +}; + struct Plane { Plane() = default; @@ -979,7 +1025,14 @@ struct AABB struct ArrayBase { - ArrayBase(uint32_t elementSize, int memTag = MemTag::Default) : buffer(nullptr), elementSize(elementSize), size(0), capacity(0), memTag(memTag) {} + ArrayBase(uint32_t elementSize, int memTag = MemTag::Default) : buffer(nullptr), elementSize(elementSize), size(0), capacity(0) + { +#if XA_DEBUG_HEAP + this->memTag = memTag; +#else + XA_UNUSED(memTag); +#endif + } ~ArrayBase() { @@ -991,6 +1044,12 @@ struct ArrayBase size = 0; } + void copyFrom(const uint8_t *data, uint32_t length) + { + resize(length, true); + memcpy(buffer, data, length * elementSize); + } + void copyTo(ArrayBase &other) const { XA_DEBUG_ASSERT(elementSize == other.elementSize); @@ -1025,7 +1084,9 @@ struct ArrayBase other.elementSize = elementSize; other.size = size; other.capacity = capacity; +#if XA_DEBUG_HEAP other.memTag = memTag; +#endif buffer = nullptr; elementSize = size = capacity = 0; } @@ -1043,6 +1104,16 @@ struct ArrayBase memcpy(&buffer[(size - 1) * elementSize], value, elementSize); } + void push_back(const ArrayBase &other) + { + XA_DEBUG_ASSERT(elementSize == other.elementSize); + if (other.size == 0) + return; + const uint32_t oldSize = size; + resize(size + other.size, false); + memcpy(buffer + oldSize * elementSize, other.buffer, other.size * other.elementSize); + } + // Remove the element at the given index. This is an expensive operation! void removeAt(uint32_t index) { @@ -1083,16 +1154,29 @@ struct ArrayBase } } else { // realloc the buffer +#if XA_DEBUG_HEAP buffer = XA_REALLOC_SIZE(memTag, buffer, newCapacity * elementSize); +#else + buffer = XA_REALLOC_SIZE(MemTag::Default, buffer, newCapacity * elementSize); +#endif } capacity = newCapacity; } +#if XA_DEBUG_HEAP + void setMemTag(int memTag) + { + this->memTag = memTag; + } +#endif + uint8_t *buffer; uint32_t elementSize; uint32_t size; uint32_t capacity; +#if XA_DEBUG_HEAP int memTag; +#endif }; template<typename T> @@ -1101,7 +1185,7 @@ class Array public: Array(int memTag = MemTag::Default) : m_base(sizeof(T), memTag) {} Array(const Array&) = delete; - const Array &operator=(const Array &) = delete; + Array &operator=(const Array &) = delete; XA_INLINE const T &operator[](uint32_t index) const { @@ -1123,6 +1207,17 @@ public: XA_INLINE T *begin() { return (T *)m_base.buffer; } XA_INLINE void clear() { m_base.clear(); } + + bool contains(const T &value) const + { + for (uint32_t i = 0; i < m_base.size; i++) { + if (((const T *)m_base.buffer)[i] == value) + return true; + } + return false; + } + + void copyFrom(const T *data, uint32_t length) { m_base.copyFrom((const uint8_t *)data, length); } void copyTo(Array &other) const { m_base.copyTo(other.m_base); } XA_INLINE const T *data() const { return (const T *)m_base.buffer; } XA_INLINE T *data() { return (T *)m_base.buffer; } @@ -1131,11 +1226,24 @@ public: void insertAt(uint32_t index, const T &value) { m_base.insertAt(index, (const uint8_t *)&value); } void moveTo(Array &other) { m_base.moveTo(other.m_base); } void push_back(const T &value) { m_base.push_back((const uint8_t *)&value); } + void push_back(const Array &other) { m_base.push_back(other.m_base); } void pop_back() { m_base.pop_back(); } void removeAt(uint32_t index) { m_base.removeAt(index); } void reserve(uint32_t desiredSize) { m_base.reserve(desiredSize); } void resize(uint32_t newSize) { m_base.resize(newSize, true); } + void runCtors() + { + for (uint32_t i = 0; i < m_base.size; i++) + new (&((T *)m_base.buffer)[i]) T; + } + + void runDtors() + { + for (uint32_t i = 0; i < m_base.size; i++) + ((T *)m_base.buffer)[i].~T(); + } + void setAll(const T &value) { auto buffer = (T *)m_base.buffer; @@ -1143,6 +1251,10 @@ public: buffer[i] = value; } +#if XA_DEBUG_HEAP + void setMemTag(int memTag) { m_base.setMemTag(memTag); } +#endif + XA_INLINE uint32_t size() const { return m_base.size; } XA_INLINE void zeroOutMemory() { memset(m_base.buffer, 0, m_base.elementSize * m_base.size); } @@ -1150,6 +1262,28 @@ private: ArrayBase m_base; }; +template<typename T> +struct ArrayView +{ + ArrayView(Array<T> &a) : data(a.data()), length(a.size()) {} + ArrayView(T *data, uint32_t length) : data(data), length(length) {} + ArrayView &operator=(Array<T> &a) { data = a.data(); length = a.size(); return *this; } + XA_INLINE const T &operator[](uint32_t index) const { XA_DEBUG_ASSERT(index < length); return data[index]; } + T *data; + uint32_t length; +}; + +template<typename T> +struct ConstArrayView +{ + ConstArrayView(const Array<T> &a) : data(a.data()), length(a.size()) {} + ConstArrayView(const T *data, uint32_t length) : data(data), length(length) {} + ConstArrayView &operator=(const Array<T> &a) { data = a.data(); length = a.size(); return *this; } + XA_INLINE const T &operator[](uint32_t index) const { XA_DEBUG_ASSERT(index < length); return data[index]; } + const T *data; + uint32_t length; +}; + /// Basis class to compute tangent space basis, ortogonalizations and to transform vectors from one space to another. struct Basis { @@ -1197,40 +1331,34 @@ public: m_wordArray.resize((m_size + 31) >> 5); } - /// Get bit. - bool bitAt(uint32_t b) const + bool get(uint32_t index) const { - XA_DEBUG_ASSERT( b < m_size ); - return (m_wordArray[b >> 5] & (1 << (b & 31))) != 0; + XA_DEBUG_ASSERT(index < m_size); + return (m_wordArray[index >> 5] & (1 << (index & 31))) != 0; } - // Set a bit. - void setBitAt(uint32_t idx) + void set(uint32_t index) { - XA_DEBUG_ASSERT(idx < m_size); - m_wordArray[idx >> 5] |= (1 << (idx & 31)); + XA_DEBUG_ASSERT(index < m_size); + m_wordArray[index >> 5] |= (1 << (index & 31)); } - // Clear all the bits. - void clearAll() + void zeroOutMemory() { - memset(m_wordArray.data(), 0, m_wordArray.size() * sizeof(uint32_t)); + m_wordArray.zeroOutMemory(); } private: - // Number of bits stored. - uint32_t m_size; - - // Array of bits. + uint32_t m_size; // Number of bits stored. Array<uint32_t> m_wordArray; }; class BitImage { public: - BitImage() : m_width(0), m_height(0), m_rowStride(0) {} + BitImage() : m_width(0), m_height(0), m_rowStride(0), m_data(MemTag::BitImage) {} - BitImage(uint32_t w, uint32_t h) : m_width(w), m_height(h) + BitImage(uint32_t w, uint32_t h) : m_width(w), m_height(h), m_data(MemTag::BitImage) { m_rowStride = (m_width + 63) >> 6; m_data.resize(m_rowStride * m_height); @@ -1238,7 +1366,7 @@ public: } BitImage(const BitImage &other) = delete; - const BitImage &operator=(const BitImage &other) = delete; + BitImage &operator=(const BitImage &other) = delete; uint32_t width() const { return m_width; } uint32_t height() const { return m_height; } @@ -1275,22 +1403,22 @@ public: m_rowStride = rowStride; } - bool bitAt(uint32_t x, uint32_t y) const + bool get(uint32_t x, uint32_t y) const { XA_DEBUG_ASSERT(x < m_width && y < m_height); const uint32_t index = (x >> 6) + y * m_rowStride; return (m_data[index] & (UINT64_C(1) << (uint64_t(x) & UINT64_C(63)))) != 0; } - void setBitAt(uint32_t x, uint32_t y) + void set(uint32_t x, uint32_t y) { XA_DEBUG_ASSERT(x < m_width && y < m_height); const uint32_t index = (x >> 6) + y * m_rowStride; m_data[index] |= UINT64_C(1) << (uint64_t(x) & UINT64_C(63)); - XA_DEBUG_ASSERT(bitAt(x, y)); + XA_DEBUG_ASSERT(get(x, y)); } - void clearAll() + void zeroOutMemory() { m_data.zeroOutMemory(); } @@ -1324,26 +1452,26 @@ public: { BitImage tmp(m_width, m_height); for (uint32_t p = 0; p < padding; p++) { - tmp.clearAll(); + tmp.zeroOutMemory(); for (uint32_t y = 0; y < m_height; y++) { for (uint32_t x = 0; x < m_width; x++) { - bool b = bitAt(x, y); + bool b = get(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); + b |= get(x - 1, y); + if (y > 0) b |= get(x - 1, y - 1); + if (y < m_height - 1) b |= get(x - 1, y + 1); } - if (y > 0) b |= bitAt(x, y - 1); - if (y < m_height - 1) b |= bitAt(x, y + 1); + if (y > 0) b |= get(x, y - 1); + if (y < m_height - 1) b |= get(x, y + 1); if (x < m_width - 1) { - b |= bitAt(x + 1, y); - if (y > 0) b |= bitAt(x + 1, y - 1); - if (y < m_height - 1) b |= bitAt(x + 1, y + 1); + b |= get(x + 1, y); + if (y > 0) b |= get(x + 1, y - 1); + if (y < m_height - 1) b |= get(x + 1, y + 1); } } if (b) - tmp.setBitAt(x, y); + tmp.set(x, y); } } tmp.m_data.copyTo(m_data); @@ -1361,7 +1489,7 @@ private: class BVH { public: - BVH(const Array<AABB> &objectAabbs, uint32_t leafSize = 4) + BVH(const Array<AABB> &objectAabbs, uint32_t leafSize = 4) : m_objectIds(MemTag::BVH), m_nodes(MemTag::BVH) { m_objectAabbs = &objectAabbs; if (m_objectAabbs->isEmpty()) @@ -1494,9 +1622,118 @@ private: Array<Node> m_nodes; }; -class Fit +struct Fit { -public: + static bool computeBasis(const Vector3 *points, uint32_t pointsCount, Basis *basis) + { + if (computeLeastSquaresNormal(points, pointsCount, &basis->normal)) { + basis->tangent = Basis::computeTangent(basis->normal); + basis->bitangent = Basis::computeBitangent(basis->normal, basis->tangent); + return true; + } + return computeEigen(points, pointsCount, basis); + } + +private: + // Fit a plane to a collection of points. + // Fast, and accurate to within a few degrees. + // Returns None if the points do not span a plane. + // https://www.ilikebigbits.com/2015_03_04_plane_from_points.html + static bool computeLeastSquaresNormal(const Vector3 *points, uint32_t pointsCount, Vector3 *normal) + { + XA_DEBUG_ASSERT(pointsCount >= 3); + if (pointsCount == 3) { + *normal = normalize(cross(points[2] - points[0], points[1] - points[0]), kEpsilon); + return true; + } + const float invN = 1.0f / float(pointsCount); + Vector3 centroid(0.0f); + for (uint32_t i = 0; i < pointsCount; i++) + centroid += points[i]; + centroid *= invN; + // Calculate full 3x3 covariance matrix, excluding symmetries: + float xx = 0.0f, xy = 0.0f, xz = 0.0f, yy = 0.0f, yz = 0.0f, zz = 0.0f; + for (uint32_t i = 0; i < pointsCount; i++) { + Vector3 r = points[i] - centroid; + xx += r.x * r.x; + xy += r.x * r.y; + xz += r.x * r.z; + yy += r.y * r.y; + yz += r.y * r.z; + zz += r.z * r.z; + } +#if 0 + xx *= invN; + xy *= invN; + xz *= invN; + yy *= invN; + yz *= invN; + zz *= invN; + Vector3 weighted_dir(0.0f); + { + float det_x = yy * zz - yz * yz; + const Vector3 axis_dir(det_x, xz * yz - xy * zz, xy * yz - xz * yy); + float weight = det_x * det_x; + if (dot(weighted_dir, axis_dir) < 0.0f) + weight = -weight; + weighted_dir += axis_dir * weight; + } + { + float det_y = xx * zz - xz * xz; + const Vector3 axis_dir(xz * yz - xy * zz, det_y, xy * xz - yz * xx); + float weight = det_y * det_y; + if (dot(weighted_dir, axis_dir) < 0.0f) + weight = -weight; + weighted_dir += axis_dir * weight; + } + { + float det_z = xx * yy - xy * xy; + const Vector3 axis_dir(xy * yz - xz * yy, xy * xz - yz * xx, det_z); + float weight = det_z * det_z; + if (dot(weighted_dir, axis_dir) < 0.0f) + weight = -weight; + weighted_dir += axis_dir * weight; + } + *normal = normalize(weighted_dir, kEpsilon); +#else + const float det_x = yy * zz - yz * yz; + const float det_y = xx * zz - xz * xz; + const float det_z = xx * yy - xy * xy; + const float det_max = max(det_x, max(det_y, det_z)); + if (det_max <= 0.0f) + return false; // The points don't span a plane + // Pick path with best conditioning: + Vector3 dir(0.0f); + if (det_max == det_x) + dir = Vector3(det_x,xz * yz - xy * zz,xy * yz - xz * yy); + else if (det_max == det_y) + dir = Vector3(xz * yz - xy * zz, det_y, xy * xz - yz * xx); + else if (det_max == det_z) + dir = Vector3(xy * yz - xz * yy, xy * xz - yz * xx, det_z); + const float len = length(dir); + if (isZero(len, kEpsilon)) + return false; + *normal = dir * (1.0f / len); +#endif + return isNormalized(*normal); + } + + static bool computeEigen(const Vector3 *points, uint32_t pointsCount, Basis *basis) + { + float matrix[6]; + computeCovariance(pointsCount, points, matrix); + if (matrix[0] == 0 && matrix[3] == 0 && matrix[5] == 0) + return false; + float eigenValues[3]; + Vector3 eigenVectors[3]; + if (!eigenSolveSymmetric3(matrix, eigenValues, eigenVectors)) + return false; + basis->normal = normalize(eigenVectors[2], kEpsilon); + basis->tangent = normalize(eigenVectors[0], kEpsilon); + basis->bitangent = normalize(eigenVectors[1], kEpsilon); + return true; + } + static Vector3 computeCentroid(int n, const Vector3 * points) { Vector3 centroid(0.0f); @@ -1694,9 +1931,9 @@ private: class FullVector { public: - FullVector(uint32_t dim) { m_array.resize(dim); } - FullVector(const FullVector &v) { v.m_array.copyTo(m_array); } - const FullVector &operator=(const FullVector &v) = delete; + FullVector(uint32_t dim) : m_array(MemTag::FullVector) { m_array.resize(dim); } + FullVector(const FullVector &v) : m_array(MemTag::FullVector) { v.m_array.copyTo(m_array); } + FullVector &operator=(const FullVector &v) = delete; XA_INLINE uint32_t dimension() const { return m_array.size(); } XA_INLINE const float &operator[](uint32_t index) const { return m_array[index]; } XA_INLINE float &operator[](uint32_t index) { return m_array[index]; } @@ -1767,7 +2004,10 @@ private: void alloc() { XA_DEBUG_ASSERT(m_size > 0); - m_numSlots = (uint32_t)(m_size * 1.3); + m_numSlots = nextPowerOfTwo(m_size); + auto minNumSlots = uint32_t(m_size * 1.3); + if (m_numSlots < minNumSlots) + m_numSlots = nextPowerOfTwo(minNumSlots); 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; @@ -1778,7 +2018,7 @@ private: uint32_t computeHash(const Key &key) const { H hash; - return hash(key) % m_numSlots; + return hash(key) & (m_numSlots - 1); } int m_memTag; @@ -1806,6 +2046,16 @@ static void insertionSort(T *data, uint32_t length) class KISSRng { public: + KISSRng() { reset(); } + + void reset() + { + x = 123456789; + y = 362436000; + z = 521288629; + c = 7654321; + } + uint32_t getRange(uint32_t range) { if (range == 0) @@ -1820,7 +2070,7 @@ public: } private: - uint32_t x = 123456789, y = 362436000, z = 521288629, c = 7654321; + uint32_t x, y, z, c; }; // Based on Pierre Terdiman's and Michael Herf's source code. @@ -2009,15 +2259,27 @@ private: class BoundingBox2D { public: - Vector2 majorAxis() const { return m_majorAxis; } - Vector2 minorAxis() const { return m_minorAxis; } - Vector2 minCorner() const { return m_minCorner; } - Vector2 maxCorner() const { return m_maxCorner; } + Vector2 majorAxis, minorAxis, minCorner, maxCorner; + + void clear() + { + m_boundaryVertices.clear(); + } + + void appendBoundaryVertex(Vector2 v) + { + m_boundaryVertices.push_back(v); + } // This should compute convex hull and use rotating calipers to find the best box. Currently it uses a brute force method. - void compute(const Vector2 *boundaryVertices, uint32_t boundaryVertexCount, const Vector2 *vertices, uint32_t vertexCount) + // If vertices is null or vertexCount is 0, the boundary vertices are used. + void compute(const Vector2 *vertices = nullptr, uint32_t vertexCount = 0) { - convexHull(boundaryVertices, boundaryVertexCount, m_hull, 0.00001f); + if (!vertices || vertexCount == 0) { + vertices = m_boundaryVertices.data(); + vertexCount = m_boundaryVertices.size(); + } + convexHull(m_boundaryVertices.data(), m_boundaryVertices.size(), 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); @@ -2051,11 +2313,11 @@ public: best_axis = axis; } } - 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)); + majorAxis = best_axis; + minorAxis = Vector2(-best_axis.y, best_axis.x); + minCorner = best_min; + maxCorner = best_max; + XA_ASSERT(isFinite(majorAxis) && isFinite(minorAxis) && isFinite(minCorner)); } private: @@ -2088,6 +2350,7 @@ private: } // Filter top list. output.clear(); + XA_DEBUG_ASSERT(m_top.size() >= 2); output.push_back(m_top[0]); output.push_back(m_top[1]); for (uint32_t i = 2; i < m_top.size(); ) { @@ -2103,6 +2366,7 @@ private: } } uint32_t top_count = output.size(); + XA_DEBUG_ASSERT(m_bottom.size() >= 2); output.push_back(m_bottom[1]); // Filter bottom list. for (uint32_t i = 2; i < m_bottom.size(); ) { @@ -2122,9 +2386,9 @@ private: output.pop_back(); } + Array<Vector2> m_boundaryVertices; Array<float> m_coords; Array<Vector2> m_top, m_bottom, m_hull; - Vector2 m_majorAxis, m_minorAxis, m_minCorner, m_maxCorner; }; static uint32_t meshEdgeFace(uint32_t edge) { return edge / 3; } @@ -2152,7 +2416,7 @@ static void meshGetBoundaryLoops(const Mesh &mesh, Array<uint32_t> &boundaryLoop class Mesh { public: - Mesh(float epsilon, uint32_t approxVertexCount, uint32_t approxFaceCount, uint32_t flags = 0, uint32_t id = UINT32_MAX) : m_epsilon(epsilon), m_flags(flags), m_id(id), m_faceIgnore(MemTag::Mesh), m_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) + 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_ignoredFaceCount(0), m_indices(MemTag::MeshIndices), m_positions(MemTag::MeshPositions), m_normals(MemTag::MeshNormals), m_texcoords(MemTag::MeshTexcoords), m_faceGroups(MemTag::Mesh), m_faceGroupFirstFace(MemTag::Mesh), m_faceGroupNextFace(MemTag::Mesh), m_faceGroupFaceCounts(MemTag::Mesh), m_colocalVertexCount(0), m_nextColocalVertex(MemTag::MeshColocals), m_boundaryEdges(MemTag::MeshBoundaries), m_oppositeEdges(MemTag::MeshBoundaries), m_nextBoundaryEdges(MemTag::MeshBoundaries), m_edgeMap(MemTag::MeshEdgeMap, approxFaceCount * 3) { m_indices.reserve(approxFaceCount * 3); m_positions.reserve(approxVertexCount); @@ -2165,6 +2429,7 @@ public: m_normals.reserve(approxVertexCount); } + static constexpr uint16_t kInvalidFaceGroup = UINT16_MAX; uint32_t flags() const { return m_flags; } uint32_t id() const { return m_id; } @@ -2199,9 +2464,12 @@ public: { AddFaceResult::Enum result = AddFaceResult::OK; if (m_flags & MeshFlags::HasFaceGroups) - m_faceGroups.push_back(UINT32_MAX); - if (m_flags & MeshFlags::HasIgnoredFaces) + m_faceGroups.push_back(kInvalidFaceGroup); + if (m_flags & MeshFlags::HasIgnoredFaces) { m_faceIgnore.push_back(ignore); + if (ignore) + m_ignoredFaceCount++; + } const uint32_t firstIndex = m_indices.size(); for (uint32_t i = 0; i < 3; i++) m_indices.push_back(indices[i]); @@ -2221,13 +2489,13 @@ public: void createColocals() { const uint32_t vertexCount = m_positions.size(); - Array<AABB> aabbs; + Array<AABB> aabbs(MemTag::BVH); 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; + Array<uint32_t> colocals(MemTag::MeshColocals); + Array<uint32_t> potential(MemTag::MeshColocals); m_colocalVertexCount = 0; m_nextColocalVertex.resize(vertexCount); for (uint32_t i = 0; i < vertexCount; i++) @@ -2259,7 +2527,7 @@ public: } // Check if the face duplicates any edges of any face already in the group. - bool faceDuplicatesGroupEdge(uint32_t group, uint32_t face) const + bool faceDuplicatesGroupEdge(uint16_t group, uint32_t face) const { for (FaceEdgeIterator edgeIt(this, face); !edgeIt.isDone(); edgeIt.advance()) { for (ColocalEdgeIterator colocalEdgeIt(this, edgeIt.vertex0(), edgeIt.vertex1()); !colocalEdgeIt.isDone(); colocalEdgeIt.advance()) { @@ -2270,55 +2538,31 @@ public: return false; } - // 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; - } - void createFaceGroups() { - uint32_t group = 0; + uint32_t firstUnassignedFace = 0; + uint16_t group = 0; Array<uint32_t> growFaces; + const uint32_t n = faceCount(); + m_faceGroupNextFace.resize(n); 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)) { + for (uint32_t f = firstUnassignedFace; f < n; f++) { + if (m_faceGroups[f] == kInvalidFaceGroup && !isFaceIgnored(f)) { face = f; + firstUnassignedFace = f + 1; break; } } if (face == UINT32_MAX) break; // All faces assigned to a group (except ignored faces). m_faceGroups[face] = group; + m_faceGroupNextFace[face] = UINT32_MAX; + m_faceGroupFirstFace.push_back(face); growFaces.clear(); growFaces.push_back(face); + uint32_t prevFace = face, groupFaceCount = 1; // 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()) @@ -2340,24 +2584,38 @@ public: alreadyAssignedToThisGroup = true; break; } - if (m_faceGroups[oppositeFace] != UINT32_MAX) + if (m_faceGroups[oppositeFace] != kInvalidFaceGroup) 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 0 + else { + // Choose the opposite face with the smallest dihedral angle. + const float d1 = 1.0f - dot(computeFaceNormal(f), computeFaceNormal(bestConnectedFace)); + const float d2 = 1.0f - dot(computeFaceNormal(f), computeFaceNormal(oppositeFace)); + if (d2 < d1) + bestConnectedFace = oppositeFace; + } +#endif } if (!alreadyAssignedToThisGroup && bestConnectedFace != UINT32_MAX) { m_faceGroups[bestConnectedFace] = group; + m_faceGroupNextFace[bestConnectedFace] = UINT32_MAX; + if (prevFace != UINT32_MAX) + m_faceGroupNextFace[prevFace] = bestConnectedFace; + prevFace = bestConnectedFace; + groupFaceCount++; growFaces.push_back(bestConnectedFace); } } } + m_faceGroupFaceCounts.push_back(groupFaceCount); group++; + XA_ASSERT(group < kInvalidFaceGroup); } } @@ -2366,29 +2624,27 @@ public: const uint32_t edgeCount = m_indices.size(); const uint32_t vertexCount = m_positions.size(); m_oppositeEdges.resize(edgeCount); - m_boundaryVertices.resize(vertexCount); + m_boundaryEdges.reserve(uint32_t(edgeCount * 0.1f)); + m_isBoundaryVertex.resize(vertexCount); + m_isBoundaryVertex.zeroOutMemory(); 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++) { + const uint32_t faceCount = m_indices.size() / 3; + 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 edge = i * 3 + j; + const uint32_t vertex0 = m_indices[edge]; 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); + const uint32_t oppositeEdge = findEdge(vertex1, vertex0); if (oppositeEdge != UINT32_MAX) { -#if XA_DEBUG - if (hasFaceGroups) - XA_DEBUG_ASSERT(m_faceGroups[meshEdgeFace(oppositeEdge)] == m_faceGroups[i]); -#endif - XA_DEBUG_ASSERT(!isFaceIgnored(meshEdgeFace(oppositeEdge))); - m_oppositeEdges[i * 3 + j] = oppositeEdge; + m_oppositeEdges[edge] = oppositeEdge; } else { - m_boundaryVertices[vertex0] = m_boundaryVertices[vertex1] = true; + m_boundaryEdges.push_back(edge); + m_isBoundaryVertex.set(vertex0); + m_isBoundaryVertex.set(vertex1); } } } @@ -2407,12 +2663,12 @@ public: m_nextBoundaryEdges[i] = UINT32_MAX; uint32_t numBoundaryLoops = 0, numUnclosedBoundaries = 0; BitArray linkedEdges(edgeCount); - linkedEdges.clearAll(); + linkedEdges.zeroOutMemory(); for (;;) { // Find the first boundary edge that hasn't been linked yet. uint32_t firstEdge = UINT32_MAX; for (uint32_t i = 0; i < edgeCount; i++) { - if (m_oppositeEdges[i] == UINT32_MAX && !linkedEdges.bitAt(i)) { + if (m_oppositeEdges[i] == UINT32_MAX && !linkedEdges.get(i)) { firstEdge = i; break; } @@ -2430,12 +2686,8 @@ public: const uint32_t otherEdge = mapIndex / 2; // Two vertices added per edge. if (m_oppositeEdges[otherEdge] != UINT32_MAX) goto next; // Not a boundary edge. - if (linkedEdges.bitAt(otherEdge)) + if (linkedEdges.get(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. @@ -2449,11 +2701,11 @@ public: if (bestNextEdge == UINT32_MAX) { numUnclosedBoundaries++; if (currentEdge == firstEdge) - linkedEdges.setBitAt(firstEdge); // Only 1 edge in this boundary "loop". + linkedEdges.set(firstEdge); // Only 1 edge in this boundary "loop". break; // Can't find a next edge. } m_nextBoundaryEdges[currentEdge] = bestNextEdge; - linkedEdges.setBitAt(bestNextEdge); + linkedEdges.set(bestNextEdge); currentEdge = bestNextEdge; if (currentEdge == firstEdge) { numBoundaryLoops++; @@ -2461,6 +2713,7 @@ public: } } } +#if XA_FIX_INTERNAL_BOUNDARY_LOOPS // Find internal boundary loops and separate them. // Detect by finding two edges in a boundary loop that have a colocal end vertex. // Fix by swapping their next boundary edge. @@ -2469,28 +2722,29 @@ public: 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()) { + linkedEdges.zeroOutMemory(); + for (Mesh::BoundaryLoopEdgeIterator it1(this, boundaryLoops[loop]); !it1.isDone(); it1.advance()) { const uint32_t e1 = it1.edge(); - if (linkedEdges.bitAt(e1)) + if (linkedEdges.get(e1)) continue; - for (Mesh::BoundaryEdgeIterator it2(this, boundaryLoops[loop]); !it2.isDone(); it2.advance()) { + for (Mesh::BoundaryLoopEdgeIterator it2(this, boundaryLoops[loop]); !it2.isDone(); it2.advance()) { const uint32_t e2 = it2.edge(); - if (e1 == e2 || !isBoundaryEdge(e2) || linkedEdges.bitAt(e2)) + if (e1 == e2 || !isBoundaryEdge(e2) || linkedEdges.get(e2)) continue; if (!areColocal(m_indices[meshEdgeIndex1(e1)], m_indices[meshEdgeIndex1(e2)])) continue; swap(m_nextBoundaryEdges[e1], m_nextBoundaryEdges[e2]); - linkedEdges.setBitAt(e1); - linkedEdges.setBitAt(e2); + linkedEdges.set(e1); + linkedEdges.set(e2); goto fixInternalBoundary; // start over } } } +#endif } /// Find edge, test all colocals. - uint32_t findEdge(uint32_t faceGroup, uint32_t vertex0, uint32_t vertex1) const + uint32_t findEdge(uint32_t vertex0, uint32_t vertex1) const { uint32_t result = UINT32_MAX; if (m_nextColocalVertex.isEmpty()) { @@ -2498,7 +2752,7 @@ public: uint32_t edge = m_edgeMap.get(key); while (edge != UINT32_MAX) { // Don't find edges of ignored faces. - if ((faceGroup == UINT32_MAX || m_faceGroups[meshEdgeFace(edge)] == faceGroup) && !isFaceIgnored(meshEdgeFace(edge))) { + if (!isFaceIgnored(meshEdgeFace(edge))) { //XA_DEBUG_ASSERT(m_id != UINT32_MAX || (m_id == UINT32_MAX && result == UINT32_MAX)); // duplicate edge - ignore on initial meshes result = edge; #if !XA_DEBUG @@ -2514,7 +2768,7 @@ public: uint32_t edge = m_edgeMap.get(key); while (edge != UINT32_MAX) { // Don't find edges of ignored faces. - if ((faceGroup == UINT32_MAX || m_faceGroups[meshEdgeFace(edge)] == faceGroup) && !isFaceIgnored(meshEdgeFace(edge))) { + if (!isFaceIgnored(meshEdgeFace(edge))) { XA_DEBUG_ASSERT(m_id != UINT32_MAX || (m_id == UINT32_MAX && result == UINT32_MAX)); // duplicate edge - ignore on initial meshes result = edge; #if !XA_DEBUG @@ -2607,7 +2861,7 @@ public: { float area = 0; for (uint32_t f = 0; f < faceCount(); f++) - area += faceArea(f); + area += computeFaceArea(f); XA_DEBUG_ASSERT(area >= 0); return area; } @@ -2616,11 +2870,11 @@ public: { float area = 0; for (uint32_t f = 0; f < faceCount(); f++) - area += faceParametricArea(f); + area += computeFaceParametricArea(f); return fabsf(area); // May be negative, depends on texcoord winding. } - float faceArea(uint32_t face) const + float computeFaceArea(uint32_t face) const { const Vector3 &p0 = m_positions[m_indices[face * 3 + 0]]; const Vector3 &p1 = m_positions[m_indices[face * 3 + 1]]; @@ -2628,7 +2882,7 @@ public: return length(cross(p1 - p0, p2 - p0)) * 0.5f; } - Vector3 faceCentroid(uint32_t face) const + Vector3 computeFaceCentroid(uint32_t face) const { Vector3 sum(0.0f); for (uint32_t i = 0; i < 3; i++) @@ -2636,22 +2890,9 @@ public: return sum / 3.0f; } - Vector3 calculateFaceNormal(uint32_t face) const - { - return normalizeSafe(triangleNormalAreaScaled(face), Vector3(0, 0, 1), 0.0f); - } - - float faceParametricArea(uint32_t face) const - { - const Vector2 &t0 = m_texcoords[m_indices[face * 3 + 0]]; - const Vector2 &t1 = m_texcoords[m_indices[face * 3 + 1]]; - const Vector2 &t2 = m_texcoords[m_indices[face * 3 + 2]]; - return triangleArea(t0, t1, t2) * 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 + Vector3 computeFaceCenter(uint32_t face) const { const Vector3 &p0 = m_positions[m_indices[face * 3 + 0]]; const Vector3 &p1 = m_positions[m_indices[face * 3 + 1]]; @@ -2665,22 +2906,25 @@ public: return m0 + m1 + m2; } - // Unnormalized face normal assuming it's a triangle. - Vector3 triangleNormal(uint32_t face) const - { - return normalizeSafe(triangleNormalAreaScaled(face), Vector3(0), 0.0f); - } - - Vector3 triangleNormalAreaScaled(uint32_t face) const + Vector3 computeFaceNormal(uint32_t face) const { const Vector3 &p0 = m_positions[m_indices[face * 3 + 0]]; const Vector3 &p1 = m_positions[m_indices[face * 3 + 1]]; const Vector3 &p2 = m_positions[m_indices[face * 3 + 2]]; const Vector3 e0 = p2 - p0; const Vector3 e1 = p1 - p0; - return cross(e0, e1); + const Vector3 normalAreaScaled = cross(e0, e1); + return normalizeSafe(normalAreaScaled, Vector3(0, 0, 1), 0.0f); } + float computeFaceParametricArea(uint32_t face) const + { + const Vector2 &t0 = m_texcoords[m_indices[face * 3 + 0]]; + const Vector2 &t1 = m_texcoords[m_indices[face * 3 + 1]]; + const Vector2 &t2 = m_texcoords[m_indices[face * 3 + 2]]; + return triangleArea(t0, t1, t2); + } + // @@ This is not exactly accurate, we should compare the texture coordinates... bool isSeam(uint32_t edge) const { @@ -2732,7 +2976,8 @@ public: XA_INLINE uint32_t edgeCount() const { return m_indices.size(); } XA_INLINE uint32_t oppositeEdge(uint32_t edge) const { return m_oppositeEdges[edge]; } XA_INLINE bool isBoundaryEdge(uint32_t edge) const { return m_oppositeEdges[edge] == UINT32_MAX; } - XA_INLINE bool isBoundaryVertex(uint32_t vertex) const { return m_boundaryVertices[vertex]; } + XA_INLINE const Array<uint32_t> &boundaryEdges() const { return m_boundaryEdges; } + XA_INLINE bool isBoundaryVertex(uint32_t vertex) const { return m_isBoundaryVertex.get(vertex); } XA_INLINE uint32_t colocalVertexCount() const { return m_colocalVertexCount; } XA_INLINE uint32_t vertexCount() const { return m_positions.size(); } XA_INLINE uint32_t vertexAt(uint32_t i) const { return m_indices[i]; } @@ -2740,10 +2985,14 @@ public: XA_INLINE const Vector3 &normal(uint32_t vertex) const { XA_DEBUG_ASSERT(m_flags & MeshFlags::HasNormals); return m_normals[vertex]; } XA_INLINE const Vector2 &texcoord(uint32_t vertex) const { return m_texcoords[vertex]; } XA_INLINE Vector2 &texcoord(uint32_t vertex) { return m_texcoords[vertex]; } + XA_INLINE const Vector2 *texcoords() const { return m_texcoords.data(); } XA_INLINE Vector2 *texcoords() { return m_texcoords.data(); } + XA_INLINE uint32_t ignoredFaceCount() const { return m_ignoredFaceCount; } XA_INLINE uint32_t faceCount() const { return m_indices.size() / 3; } - XA_INLINE uint32_t faceGroupCount() const { XA_DEBUG_ASSERT(m_flags & MeshFlags::HasFaceGroups); return m_faceGroups.size(); } - XA_INLINE uint32_t faceGroupAt(uint32_t face) const { XA_DEBUG_ASSERT(m_flags & MeshFlags::HasFaceGroups); return m_faceGroups[face]; } + XA_INLINE uint16_t faceGroupAt(uint32_t face) const { XA_DEBUG_ASSERT(m_flags & MeshFlags::HasFaceGroups); return m_faceGroups[face]; } + XA_INLINE uint32_t faceGroupCount() const { XA_DEBUG_ASSERT(m_flags & MeshFlags::HasFaceGroups); return m_faceGroupFaceCounts.size(); } + XA_INLINE uint32_t faceGroupNextFace(uint32_t face) const { XA_DEBUG_ASSERT(m_flags & MeshFlags::HasFaceGroups); return m_faceGroupNextFace[face]; } + XA_INLINE uint32_t faceGroupFaceCount(uint32_t group) const { XA_DEBUG_ASSERT(m_flags & MeshFlags::HasFaceGroups); return m_faceGroupFaceCounts[group]; } XA_INLINE const uint32_t *indices() const { return m_indices.data(); } XA_INLINE uint32_t indexCount() const { return m_indices.size(); } @@ -2754,18 +3003,25 @@ private: uint32_t m_flags; uint32_t m_id; Array<bool> m_faceIgnore; - Array<uint32_t> m_faceGroups; + uint32_t m_ignoredFaceCount; Array<uint32_t> m_indices; Array<Vector3> m_positions; Array<Vector3> m_normals; Array<Vector2> m_texcoords; + // Populated by createFaceGroups + Array<uint16_t> m_faceGroups; + Array<uint32_t> m_faceGroupFirstFace; + Array<uint32_t> m_faceGroupNextFace; // In: face. Out: the next face in the same group. + Array<uint32_t> m_faceGroupFaceCounts; // In: face group. Out: number of faces in the group. + // 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; + BitArray m_isBoundaryVertex; + Array<uint32_t> m_boundaryEdges; Array<uint32_t> m_oppositeEdges; // In: edge index. Out: the index of the opposite edge (i.e. wound the opposite direction). UINT32_MAX if the input edge is a boundary edge. // Populated by linkBoundaries @@ -2776,28 +3032,24 @@ private: 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; - } + bool operator==(const EdgeKey &k) const { return v0 == k.v0 && v1 == k.v1; } uint32_t v0; uint32_t v1; }; - HashMap<EdgeKey> m_edgeMap; + struct EdgeHash + { + uint32_t operator()(const EdgeKey &k) const { return k.v0 * 32768u + k.v1; } + }; + + HashMap<EdgeKey, EdgeHash> m_edgeMap; public: - class BoundaryEdgeIterator + class BoundaryLoopEdgeIterator { public: - BoundaryEdgeIterator(const Mesh *mesh, uint32_t edge) : m_mesh(mesh), m_first(UINT32_MAX), m_current(edge) {} + BoundaryLoopEdgeIterator(const Mesh *mesh, uint32_t edge) : m_mesh(mesh), m_first(UINT32_MAX), m_current(edge) {} void advance() { @@ -2866,7 +3118,14 @@ public: public: ColocalEdgeIterator(const Mesh *mesh, uint32_t vertex0, uint32_t vertex1) : m_mesh(mesh), m_vertex0It(mesh, vertex0), m_vertex1It(mesh, vertex1), m_vertex1(vertex1) { - resetElement(); + do { + if (!resetElement()) { + advanceVertex1(); + } + else { + break; + } + } while (!isDone()); } void advance() @@ -2885,7 +3144,7 @@ public: } private: - void resetElement() + bool resetElement() { m_edge = m_mesh->m_edgeMap.get(Mesh::EdgeKey(m_vertex0It.vertex(), m_vertex1It.vertex())); while (m_edge != UINT32_MAX) { @@ -2893,8 +3152,10 @@ public: break; m_edge = m_mesh->m_edgeMap.getNext(m_edge); } - if (m_edge == UINT32_MAX) - advanceVertex1(); + if (m_edge == UINT32_MAX) { + return false; + } + return true; } void advanceElement() @@ -2910,22 +3171,22 @@ public: advanceVertex1(); } - void advanceVertex0() - { - m_vertex0It.advance(); - if (m_vertex0It.isDone()) - return; - m_vertex1It = ColocalVertexIterator(m_mesh, m_vertex1); - resetElement(); - } - void advanceVertex1() { - m_vertex1It.advance(); - if (m_vertex1It.isDone()) - advanceVertex0(); - else - resetElement(); + auto successful = false; + while (!successful) { + m_vertex1It.advance(); + if (m_vertex1It.isDone()) { + if (!m_vertex0It.isDone()) { + m_vertex0It.advance(); + m_vertex1It = ColocalVertexIterator(m_mesh, m_vertex1); + } + else { + return; + } + } + successful = resetElement(); + } } bool isIgnoredFace() const @@ -2976,16 +3237,8 @@ public: return meshEdgeFace(oedge); } - uint32_t vertex0() const - { - 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]; - } - + uint32_t vertex0() const { 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()]; } @@ -2999,8 +3252,39 @@ public: uint32_t m_edge; uint32_t m_relativeEdge; }; + + class GroupFaceIterator + { + public: + GroupFaceIterator(const Mesh *mesh, uint32_t group) : m_mesh(mesh) + { + XA_DEBUG_ASSERT(group != UINT32_MAX); + m_current = mesh->m_faceGroupFirstFace[group]; + } + + void advance() + { + m_current = m_mesh->m_faceGroupNextFace[m_current]; + } + + bool isDone() const + { + return m_current == UINT32_MAX; + } + + uint32_t face() const + { + return m_current; + } + + private: + const Mesh *m_mesh; + uint32_t m_current; + }; }; +constexpr uint16_t Mesh::kInvalidFaceGroup; + static bool meshCloseHole(Mesh *mesh, const Array<uint32_t> &holeVertices, const Vector3 &normal) { #if XA_CLOSE_HOLES_CHECK_EDGE_INTERSECTION @@ -3027,15 +3311,15 @@ static bool meshCloseHole(Mesh *mesh, const Array<uint32_t> &holeVertices, const 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))); + frontAngles[i] = atan2f(length(cross(edge1, edge2)), dot(edge1, edge2)); if (frontAngles[i] >= smallestAngle || isNan(frontAngles[i])) continue; // Don't duplicate edges. - if (mesh->findEdge(UINT32_MAX, frontVertices[i1], frontVertices[i2]) != UINT32_MAX) + if (mesh->findEdge(frontVertices[i1], frontVertices[i2]) != UINT32_MAX) continue; - if (mesh->findEdge(UINT32_MAX, frontVertices[i2], frontVertices[i3]) != UINT32_MAX) + if (mesh->findEdge(frontVertices[i2], frontVertices[i3]) != UINT32_MAX) continue; - if (mesh->findEdge(UINT32_MAX, frontVertices[i3], frontVertices[i1]) != UINT32_MAX) + if (mesh->findEdge(frontVertices[i3], frontVertices[i1]) != UINT32_MAX) continue; /* Make sure he new edge that would be formed by (i3, i1) doesn't intersect any vertices. This often happens when fixing t-junctions. @@ -3128,9 +3412,10 @@ static bool meshCloseHole(Mesh *mesh, const Array<uint32_t> &holeVertices, const return true; } -static bool meshCloseHoles(Mesh *mesh, const Array<uint32_t> &boundaryLoops, const Vector3 &normal, Array<uint32_t> &holeFaceCounts) +static bool meshCloseHoles(Mesh *mesh, const Array<uint32_t> &boundaryLoops, const Vector3 &normal, uint32_t *holeCount, Array<uint32_t> *holeFaceCounts) { - holeFaceCounts.clear(); + if (holeFaceCounts) + holeFaceCounts->clear(); // Compute lengths. const uint32_t boundaryCount = boundaryLoops.size(); Array<float> boundaryLengths; @@ -3139,7 +3424,7 @@ static bool meshCloseHoles(Mesh *mesh, const Array<uint32_t> &boundaryLoops, con 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()) { + for (Mesh::BoundaryLoopEdgeIterator it(mesh, boundaryLoops[i]); !it.isDone(); it.advance()) { const Vector3 &t0 = mesh->position(mesh->vertexAt(meshEdgeIndex0(it.edge()))); const Vector3 &t1 = mesh->position(mesh->vertexAt(meshEdgeIndex1(it.edge()))); boundaryLength += length(t1 - t0); @@ -3167,7 +3452,7 @@ static bool meshCloseHoles(Mesh *mesh, const Array<uint32_t> &boundaryLoops, con 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()) { + for (Mesh::BoundaryLoopEdgeIterator it(mesh, boundaryLoops[i]); !it.isDone(); it.advance()) { const uint32_t vertex = mesh->vertexAt(meshEdgeIndex0(it.edge())); holeVertices[boundaryEdgeCounts[i] - 1 - e] = vertex; holePoints[boundaryEdgeCounts[i] - 1 - e] = mesh->position(vertex); @@ -3176,7 +3461,10 @@ static bool meshCloseHoles(Mesh *mesh, const Array<uint32_t> &boundaryLoops, con 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); + if (holeCount) + (*holeCount)++; + if (holeFaceCounts) + holeFaceCounts->push_back(mesh->faceCount() - oldFaceCount); } return result; } @@ -3307,104 +3595,18 @@ static void meshGetBoundaryLoops(const Mesh &mesh, Array<uint32_t> &boundaryLoop { const uint32_t edgeCount = mesh.edgeCount(); BitArray bitFlags(edgeCount); - bitFlags.clearAll(); + bitFlags.zeroOutMemory(); boundaryLoops.clear(); // Search for boundary edges. Mark all the edges that belong to the same boundary. for (uint32_t e = 0; e < edgeCount; e++) { - if (bitFlags.bitAt(e) || !mesh.isBoundaryEdge(e)) + if (bitFlags.get(e) || !mesh.isBoundaryEdge(e)) continue; - for (Mesh::BoundaryEdgeIterator it(&mesh, e); !it.isDone(); it.advance()) - bitFlags.setBitAt(it.edge()); + for (Mesh::BoundaryLoopEdgeIterator it(&mesh, e); !it.isDone(); it.advance()) + bitFlags.set(it.edge()); boundaryLoops.push_back(e); } } -class MeshTopology -{ -public: - MeshTopology(const Mesh *mesh) - { - const uint32_t vertexCount = mesh->colocalVertexCount(); - const uint32_t faceCount = mesh->faceCount(); - const uint32_t edgeCount = mesh->edgeCount(); - Array<uint32_t> stack(MemTag::Default); - stack.reserve(faceCount); - BitArray bitFlags(faceCount); - bitFlags.clearAll(); - // Compute connectivity. - m_connectedCount = 0; - for (uint32_t f = 0; f < faceCount; f++ ) { - if (bitFlags.bitAt(f) == false) { - m_connectedCount++; - stack.push_back(f); - while (!stack.isEmpty()) { - const uint32_t top = stack.back(); - XA_ASSERT(top != uint32_t(~0)); - stack.pop_back(); - if (bitFlags.bitAt(top) == false) { - bitFlags.setBitAt(top); - for (Mesh::FaceEdgeIterator it(mesh, top); !it.isDone(); it.advance()) { - const uint32_t oppositeFace = it.oppositeFace(); - if (oppositeFace != UINT32_MAX) - stack.push_back(oppositeFace); - } - } - } - } - } - XA_ASSERT(stack.isEmpty()); - // Count boundary loops. - 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++) { - 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; - // Compute genus. (only valid on closed connected surfaces) - m_genus = -1; - if (isClosed() && isConnected()) - m_genus = (2 - m_eulerNumber) / 2; - } - - /// 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: - ///< Number of boundary loops. - int m_boundaryCount; - - ///< Number of connected components. - int m_connectedCount; - - ///< Euler number. - int m_eulerNumber; - - /// Mesh genus. - int m_genus; -}; - struct Progress { Progress(ProgressCategory::Enum category, ProgressFunc func, void *userData, uint32_t maxValue) : value(0), cancel(false), m_category(category), m_func(func), m_userData(userData), m_maxValue(maxValue), m_progress(0) @@ -3482,6 +3684,7 @@ class TaskScheduler public: TaskScheduler() : m_shutdown(false) { + m_threadIndex = 0; // Max with current task scheduler usage is 1 per thread + 1 deep nesting, but allow for some slop. m_maxGroups = std::thread::hardware_concurrency() * 4; m_groups = XA_ALLOC_ARRAY(MemTag::Default, TaskGroup, m_maxGroups); @@ -3494,7 +3697,7 @@ public: for (uint32_t i = 0; i < m_workers.size(); i++) { new (&m_workers[i]) Worker(); m_workers[i].wakeup = false; - m_workers[i].thread = XA_NEW_ARGS(MemTag::Default, std::thread, workerThread, this, &m_workers[i]); + m_workers[i].thread = XA_NEW_ARGS(MemTag::Default, std::thread, workerThread, this, &m_workers[i], i + 1); } } @@ -3517,6 +3720,11 @@ public: XA_FREE(m_groups); } + uint32_t threadCount() const + { + return max(1u, std::thread::hardware_concurrency()); // Including the main thread. + } + TaskGroupHandle createTaskGroup(uint32_t reserveSize = 0) { // Claim the first free group. @@ -3581,6 +3789,8 @@ public: handle->value = UINT32_MAX; } + static uint32_t currentThreadIndex() { return m_threadIndex; } + private: struct TaskGroup { @@ -3603,9 +3813,11 @@ private: uint32_t m_maxGroups; Array<Worker> m_workers; std::atomic<bool> m_shutdown; + static thread_local uint32_t m_threadIndex; - static void workerThread(TaskScheduler *scheduler, Worker *worker) + static void workerThread(TaskScheduler *scheduler, Worker *worker, uint32_t threadIndex) { + m_threadIndex = threadIndex; std::unique_lock<std::mutex> lock(worker->mutex); for (;;) { worker->cv.wait(lock, [=]{ return worker->wakeup.load(); }); @@ -3636,6 +3848,8 @@ private: } } }; + +thread_local uint32_t TaskScheduler::m_threadIndex; #else class TaskScheduler { @@ -3646,6 +3860,11 @@ public: destroyGroup({ i }); } + uint32_t threadCount() const + { + return 1; + } + TaskGroupHandle createTaskGroup(uint32_t reserveSize = 0) { TaskGroup *group = XA_NEW(MemTag::Default, TaskGroup); @@ -3675,6 +3894,8 @@ public: handle->value = UINT32_MAX; } + static uint32_t currentThreadIndex() { return 0; } + private: void destroyGroup(TaskGroupHandle handle) { @@ -3695,6 +3916,369 @@ private: }; #endif +#if XA_DEBUG_EXPORT_TGA +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 + +template<typename T> +class ThreadLocal +{ +public: + ThreadLocal() + { +#if XA_MULTITHREADED + const uint32_t n = std::thread::hardware_concurrency(); +#else + const uint32_t n = 1; +#endif + m_array = XA_ALLOC_ARRAY(MemTag::Default, T, n); + for (uint32_t i = 0; i < n; i++) + new (&m_array[i]) T; + } + + ~ThreadLocal() + { +#if XA_MULTITHREADED + const uint32_t n = std::thread::hardware_concurrency(); +#else + const uint32_t n = 1; +#endif + for (uint32_t i = 0; i < n; i++) + m_array[i].~T(); + XA_FREE(m_array); + } + + T &get() const + { + return m_array[TaskScheduler::currentThreadIndex()]; + } + +private: + T *m_array; +}; + +class UniformGrid2 +{ +public: + void reset(const Vector2 *positions, const uint32_t *indices = nullptr, uint32_t reserveEdgeCount = 0) + { + m_edges.clear(); + if (reserveEdgeCount > 0) + m_edges.reserve(reserveEdgeCount); + m_positions = positions; + m_indices = indices; + m_cellDataOffsets.clear(); + } + + void append(uint32_t edge) + { + XA_DEBUG_ASSERT(m_cellDataOffsets.isEmpty()); + m_edges.push_back(edge); + } + + bool intersect(Vector2 v1, Vector2 v2, float epsilon) + { + const uint32_t edgeCount = m_edges.size(); + bool bruteForce = edgeCount <= 64; + if (!bruteForce && m_cellDataOffsets.isEmpty()) + bruteForce = !createGrid(); + if (bruteForce) { + for (uint32_t j = 0; j < edgeCount; j++) { + const uint32_t edge = m_edges[j]; + if (linesIntersect(v1, v2, edgePosition0(edge), edgePosition1(edge), epsilon)) + return true; + } + } else { + computePotentialEdges(v1, v2); + uint32_t prevEdge = UINT32_MAX; + for (uint32_t j = 0; j < m_potentialEdges.size(); j++) { + const uint32_t edge = m_potentialEdges[j]; + if (edge == prevEdge) + continue; + if (linesIntersect(v1, v2, edgePosition0(edge), edgePosition1(edge), epsilon)) + return true; + prevEdge = edge; + } + } + return false; + } + + bool intersectSelf(float epsilon) + { + const uint32_t edgeCount = m_edges.size(); + bool bruteForce = edgeCount <= 64; + if (!bruteForce && m_cellDataOffsets.isEmpty()) + bruteForce = !createGrid(); + for (uint32_t i = 0; i < edgeCount; i++) { + const uint32_t edge1 = m_edges[i]; + if (bruteForce) { + for (uint32_t j = 0; j < edgeCount; j++) { + const uint32_t edge2 = m_edges[j]; + if (edgesIntersect(edge1, edge2, epsilon)) + return true; + } + } else { + computePotentialEdges(edgePosition0(edge1), edgePosition1(edge1)); + uint32_t prevEdge = UINT32_MAX; + for (uint32_t j = 0; j < m_potentialEdges.size(); j++) { + const uint32_t edge2 = m_potentialEdges[j]; + if (edge2 == prevEdge) + continue; + if (edgesIntersect(edge1, edge2, epsilon)) + return true; + prevEdge = edge2; + } + } + } + return false; + } + +#if XA_DEBUG_EXPORT_BOUNDARY_GRID + void debugExport(const char *filename) + { + Array<uint8_t> image; + image.resize(m_gridWidth * m_gridHeight * 3); + for (uint32_t y = 0; y < m_gridHeight; y++) { + for (uint32_t x = 0; x < m_gridWidth; x++) { + uint8_t *bgr = &image[(x + y * m_gridWidth) * 3]; + bgr[0] = bgr[1] = bgr[2] = 32; + uint32_t offset = m_cellDataOffsets[x + y * m_gridWidth]; + while (offset != UINT32_MAX) { + const uint32_t edge2 = m_cellData[offset]; + srand(edge2); + for (uint32_t i = 0; i < 3; i++) + bgr[i] = uint8_t(bgr[i] * 0.5f + (rand() % 255) * 0.5f); + offset = m_cellData[offset + 1]; + } + } + } + WriteTga(filename, image.data(), m_gridWidth, m_gridHeight); + } +#endif + +private: + bool createGrid() + { + // Compute edge extents. Min will be the grid origin. + const uint32_t edgeCount = m_edges.size(); + Extents2 edgeExtents; + edgeExtents.reset(); + for (uint32_t i = 0; i < edgeCount; i++) { + const uint32_t edge = m_edges[i]; + edgeExtents.add(edgePosition0(edge)); + edgeExtents.add(edgePosition1(edge)); + } + m_gridOrigin = edgeExtents.min; + // Size grid to approximately one edge per cell. + const Vector2 extentsSize(edgeExtents.max - edgeExtents.min); + m_cellSize = min(extentsSize.x, extentsSize.y) / sqrtf((float)edgeCount); + if (m_cellSize <= 0.0f) + return false; + m_gridWidth = uint32_t(ceilf(extentsSize.x / m_cellSize)); + m_gridHeight = uint32_t(ceilf(extentsSize.y / m_cellSize)); + if (m_gridWidth == 0 || m_gridHeight == 0) + return false; + // Insert edges into cells. + m_cellDataOffsets.resize(m_gridWidth * m_gridHeight); + for (uint32_t i = 0; i < m_cellDataOffsets.size(); i++) + m_cellDataOffsets[i] = UINT32_MAX; + m_cellData.clear(); + m_cellData.reserve(edgeCount * 2); + for (uint32_t i = 0; i < edgeCount; i++) { + const uint32_t edge = m_edges[i]; + traverse(edgePosition0(edge), edgePosition1(edge)); + XA_DEBUG_ASSERT(!m_traversedCellOffsets.isEmpty()); + for (uint32_t j = 0; j < m_traversedCellOffsets.size(); j++) { + const uint32_t cell = m_traversedCellOffsets[j]; + uint32_t offset = m_cellDataOffsets[cell]; + if (offset == UINT32_MAX) + m_cellDataOffsets[cell] = m_cellData.size(); + else { + for (;;) { + uint32_t &nextOffset = m_cellData[offset + 1]; + if (nextOffset == UINT32_MAX) { + nextOffset = m_cellData.size(); + break; + } + offset = nextOffset; + } + } + m_cellData.push_back(edge); + m_cellData.push_back(UINT32_MAX); + } + } + return true; + } + + void computePotentialEdges(Vector2 p1, Vector2 p2) + { + m_potentialEdges.clear(); + traverse(p1, p2); + for (uint32_t j = 0; j < m_traversedCellOffsets.size(); j++) { + const uint32_t cell = m_traversedCellOffsets[j]; + uint32_t offset = m_cellDataOffsets[cell]; + while (offset != UINT32_MAX) { + const uint32_t edge2 = m_cellData[offset]; + m_potentialEdges.push_back(edge2); + offset = m_cellData[offset + 1]; + } + } + if (m_potentialEdges.isEmpty()) + return; + insertionSort(m_potentialEdges.data(), m_potentialEdges.size()); + } + + // "A Fast Voxel Traversal Algorithm for Ray Tracing" + void traverse(Vector2 p1, Vector2 p2) + { + const Vector2 dir = p2 - p1; + const Vector2 normal = normalizeSafe(dir, Vector2(0.0f), kEpsilon); + const int stepX = dir.x >= 0 ? 1 : -1; + const int stepY = dir.y >= 0 ? 1 : -1; + const uint32_t firstCell[2] = { cellX(p1.x), cellY(p1.y) }; + const uint32_t lastCell[2] = { cellX(p2.x), cellY(p2.y) }; + float distToNextCellX; + if (stepX == 1) + distToNextCellX = (firstCell[0] + 1) * m_cellSize - (p1.x - m_gridOrigin.x); + else + distToNextCellX = (p1.x - m_gridOrigin.x) - firstCell[0] * m_cellSize; + float distToNextCellY; + if (stepY == 1) + distToNextCellY = (firstCell[1] + 1) * m_cellSize - (p1.y - m_gridOrigin.y); + else + distToNextCellY = (p1.y - m_gridOrigin.y) - firstCell[1] * m_cellSize; + float tMaxX, tMaxY, tDeltaX, tDeltaY; + if (normal.x > kEpsilon || normal.x < -kEpsilon) { + tMaxX = (distToNextCellX * stepX) / normal.x; + tDeltaX = (m_cellSize * stepX) / normal.x; + } + else + tMaxX = tDeltaX = FLT_MAX; + if (normal.y > kEpsilon || normal.y < -kEpsilon) { + tMaxY = (distToNextCellY * stepY) / normal.y; + tDeltaY = (m_cellSize * stepY) / normal.y; + } + else + tMaxY = tDeltaY = FLT_MAX; + m_traversedCellOffsets.clear(); + m_traversedCellOffsets.push_back(firstCell[0] + firstCell[1] * m_gridWidth); + uint32_t currentCell[2] = { firstCell[0], firstCell[1] }; + while (!(currentCell[0] == lastCell[0] && currentCell[1] == lastCell[1])) { + if (tMaxX < tMaxY) { + tMaxX += tDeltaX; + currentCell[0] += stepX; + } else { + tMaxY += tDeltaY; + currentCell[1] += stepY; + } + if (currentCell[0] >= m_gridWidth || currentCell[1] >= m_gridHeight) + break; + if (stepX == 0 && currentCell[0] < lastCell[0]) + break; + if (stepX == 1 && currentCell[0] > lastCell[0]) + break; + if (stepY == 0 && currentCell[1] < lastCell[1]) + break; + if (stepY == 1 && currentCell[1] > lastCell[1]) + break; + m_traversedCellOffsets.push_back(currentCell[0] + currentCell[1] * m_gridWidth); + } + } + + bool edgesIntersect(uint32_t edge1, uint32_t edge2, float epsilon) const + { + if (edge1 == edge2) + return false; + const uint32_t ai[2] = { vertexAt(meshEdgeIndex0(edge1)), vertexAt(meshEdgeIndex1(edge1)) }; + const uint32_t bi[2] = { vertexAt(meshEdgeIndex0(edge2)), vertexAt(meshEdgeIndex1(edge2)) }; + // Ignore connected edges, since they can't intersect (only overlap), and may be detected as false positives. + if (ai[0] == bi[0] || ai[0] == bi[1] || ai[1] == bi[0] || ai[1] == bi[1]) + return false; + return linesIntersect(m_positions[ai[0]], m_positions[ai[1]], m_positions[bi[0]], m_positions[bi[1]], epsilon); + } + + uint32_t cellX(float x) const + { + return min((uint32_t)max(0.0f, (x - m_gridOrigin.x) / m_cellSize), m_gridWidth - 1u); + } + + uint32_t cellY(float y) const + { + return min((uint32_t)max(0.0f, (y - m_gridOrigin.y) / m_cellSize), m_gridHeight - 1u); + } + + Vector2 edgePosition0(uint32_t edge) const + { + return m_positions[vertexAt(meshEdgeIndex0(edge))]; + } + + Vector2 edgePosition1(uint32_t edge) const + { + return m_positions[vertexAt(meshEdgeIndex1(edge))]; + } + + uint32_t vertexAt(uint32_t index) const + { + return m_indices ? m_indices[index] : index; + } + + Array<uint32_t> m_edges; + const Vector2 *m_positions; + const uint32_t *m_indices; // Optional + float m_cellSize; + Vector2 m_gridOrigin; + uint32_t m_gridWidth, m_gridHeight; // in cells + Array<uint32_t> m_cellDataOffsets; + Array<uint32_t> m_cellData; + Array<uint32_t> m_potentialEdges; + Array<uint32_t> m_traversedCellOffsets; +}; + struct UvMeshChart { Array<uint32_t> faces; @@ -3834,15 +4418,16 @@ struct Triangle // make sure every triangle is front facing. flipBackface(); // Compute deltas. - computeUnitInwardNormals(); + if (isValid()) + computeUnitInwardNormals(); } bool isValid() { 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); + const float area = e0.y * e1.x - e1.y * e0.x; + return area != 0.0f; } // extents has to be multiple of BK_SIZE!! @@ -3926,6 +4511,7 @@ struct Triangle return true; } +private: void flipBackface() { // check if triangle is backfacing, if so, swap two vertices @@ -3941,13 +4527,13 @@ struct Triangle { n1 = v1 - v2; n1 = Vector2(-n1.y, n1.x); - n1 = n1 * (1.0f / sqrtf(n1.x * n1.x + n1.y * n1.y)); + n1 = n1 * (1.0f / sqrtf(dot(n1, n1))); n2 = v2 - v3; n2 = Vector2(-n2.y, n2.x); - n2 = n2 * (1.0f / sqrtf(n2.x * n2.x + n2.y * n2.y)); + n2 = n2 * (1.0f / sqrtf(dot(n2, n2))); n3 = v3 - v1; n3 = Vector2(-n3.y, n3.x); - n3 = n3 * (1.0f / sqrtf(n3.x * n3.x + n3.y * n3.y)); + n3 = n3 * (1.0f / sqrtf(dot(n3, n3))); } // Vertices. @@ -3990,28 +4576,33 @@ public: float v; // value }; - Matrix(uint32_t d) : m_width(d) + Matrix(uint32_t d) : m_width(d), m_array(MemTag::Matrix) { m_array.resize(d); - for (uint32_t i = 0; i < m_array.size(); i++) - new (&m_array[i]) Array<Coefficient>(); + m_array.runCtors(); +#if XA_DEBUG_HEAP + for (uint32_t i = 0; i < d; i++) + m_array[i].setMemTag(MemTag::Matrix); +#endif } - Matrix(uint32_t w, uint32_t h) : m_width(w) + Matrix(uint32_t w, uint32_t h) : m_width(w), m_array(MemTag::Matrix) { m_array.resize(h); - for (uint32_t i = 0; i < m_array.size(); i++) - new (&m_array[i]) Array<Coefficient>(); + m_array.runCtors(); +#if XA_DEBUG_HEAP + for (uint32_t i = 0; i < h; i++) + m_array[i].setMemTag(MemTag::Matrix); +#endif } ~Matrix() { - for (uint32_t i = 0; i < m_array.size(); i++) - m_array[i].~Array(); + m_array.runDtors(); } Matrix(const Matrix &m) = delete; - const Matrix &operator=(const Matrix &m) = delete; + Matrix &operator=(const Matrix &m) = delete; uint32_t width() const { return m_width; } uint32_t height() const { return m_array.size(); } bool isSquare() const { return width() == height(); } @@ -4211,164 +4802,164 @@ static void mult(const Matrix &A, const Matrix &B, Matrix &C) namespace segment { -// 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). -// - Resorting is n*log(n) -// @@ Number of elements in the queue is usually small, and we'd have to rebalance often. I'm not sure it's worth implementing a heap. -// @@ Searcing at removal would remove the need for sorting when priorities change. -struct PriorityQueue +struct CostQueue { - PriorityQueue(uint32_t size = UINT32_MAX) : maxSize(size) {} + CostQueue(uint32_t size = UINT32_MAX) : m_maxSize(size), m_pairs(MemTag::SegmentAtlasChartCandidates) {} - void push(float priority, uint32_t face) + float peekCost() const { - uint32_t i = 0; - const uint32_t count = pairs.size(); - for (; i < count; i++) { - if (pairs[i].priority > priority) break; - } - Pair p = { priority, face }; - pairs.insertAt(i, p); - if (pairs.size() > maxSize) - pairs.removeAt(0); + return m_pairs.back().cost; } - // push face out of order, to be sorted later. - void push(uint32_t face) + uint32_t peekFace() const { - Pair p = { 0.0f, face }; - pairs.push_back(p); + return m_pairs.back().face; } - uint32_t pop() + void push(float cost, uint32_t face) { - XA_DEBUG_ASSERT(!pairs.isEmpty()); - uint32_t f = pairs.back().face; - pairs.pop_back(); - return f; + const Pair p = { cost, face }; + if (m_pairs.isEmpty() || cost < peekCost()) + m_pairs.push_back(p); + else { + uint32_t i = 0; + const uint32_t count = m_pairs.size(); + for (; i < count; i++) { + if (m_pairs[i].cost < cost) + break; + } + m_pairs.insertAt(i, p); + if (m_pairs.size() > m_maxSize) + m_pairs.removeAt(0); + } } - void sort() + uint32_t pop() { - //sort(pairs); // @@ My intro sort appears to be much slower than it should! - std::sort(pairs.begin(), pairs.end()); + XA_DEBUG_ASSERT(!m_pairs.isEmpty()); + uint32_t f = m_pairs.back().face; + m_pairs.pop_back(); + return f; } XA_INLINE void clear() { - pairs.clear(); + m_pairs.clear(); } XA_INLINE uint32_t count() const { - return pairs.size(); + return m_pairs.size(); } - float firstPriority() const - { - return pairs.back().priority; - } - - const uint32_t maxSize; +private: + const uint32_t m_maxSize; struct Pair { - bool operator<(const Pair &p) const - { - return priority > p.priority; // !! Sort in inverse priority order! - } - - float priority; + float cost; uint32_t face; }; - Array<Pair> pairs; + Array<Pair> m_pairs; }; struct Chart { + Chart() : faces(MemTag::SegmentAtlasChartFaces) {} + int id = -1; - Vector3 averageNormal = Vector3(0.0f); + Basis basis; // Best fit normal. 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. + Array<uint32_t> failedPlanarRegions; + CostQueue candidates; }; struct Atlas { - // @@ Hardcoded to 10? - Atlas(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) + Atlas() : m_edgeLengths(MemTag::SegmentAtlasMeshData), m_faceAreas(MemTag::SegmentAtlasMeshData), m_faceNormals(MemTag::SegmentAtlasMeshData), m_texcoords(MemTag::SegmentAtlasMeshData), m_bestTriangles(10), m_nextPlanarRegionFace(MemTag::SegmentAtlasPlanarRegions), m_facePlanarRegionId(MemTag::SegmentAtlasPlanarRegions) {} + + ~Atlas() + { + const uint32_t chartCount = m_charts.size(); + for (uint32_t i = 0; i < chartCount; i++) { + m_charts[i]->~Chart(); + XA_FREE(m_charts[i]); + } + } + + uint32_t facesLeft() const { return m_facesLeft; } + uint32_t chartCount() const { return m_charts.size(); } + const Array<uint32_t> &chartFaces(uint32_t i) const { return m_charts[i]->faces; } + const Basis &chartBasis(uint32_t chartIndex) const { return m_charts[chartIndex]->basis; } + + void reset(uint32_t meshId, uint32_t chartGroupId, const Mesh *mesh, const ChartOptions &options) { + XA_UNUSED(meshId); + XA_UNUSED(chartGroupId); XA_PROFILE_START(buildAtlasInit) + m_mesh = mesh; const uint32_t faceCount = m_mesh->faceCount(); - if (meshFaces) { - m_ignoreFaces.resize(faceCount); - m_ignoreFaces.setAll(true); - for (uint32_t f = 0; f < meshFaces->size(); f++) - m_ignoreFaces[(*meshFaces)[f]] = false; - m_facesLeft = meshFaces->size(); - } else { - m_ignoreFaces.resize(faceCount); - m_ignoreFaces.setAll(false); + m_facesLeft = faceCount; + m_options = options; + m_rand.reset(); + const uint32_t chartCount = m_charts.size(); + for (uint32_t i = 0; i < chartCount; i++) { + m_charts[i]->~Chart(); + XA_FREE(m_charts[i]); } - m_faceChartArray.resize(faceCount); - m_faceChartArray.setAll(-1); - m_faceCandidateCharts.resize(faceCount); - m_faceCandidateCosts.resize(faceCount); + m_charts.clear(); + m_faceCharts.resize(faceCount); + m_faceCharts.setAll(-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. const uint32_t edgeCount = m_mesh->edgeCount(); m_edgeLengths.resize(edgeCount); - m_edgeLengths.zeroOutMemory(); m_faceAreas.resize(faceCount); - m_faceAreas.zeroOutMemory(); m_faceNormals.resize(faceCount); - m_faceTangents.resize(faceCount); - m_faceBitangents.resize(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); + for (uint32_t i = 0; i < 3; i++) { + const uint32_t edge = f * 3 + i; + const Vector3 &p0 = mesh->position(m_mesh->vertexAt(meshEdgeIndex0(edge))); + const Vector3 &p1 = mesh->position(m_mesh->vertexAt(meshEdgeIndex1(edge))); + m_edgeLengths[edge] = length(p1 - p0); + XA_DEBUG_ASSERT(m_edgeLengths[edge] > 0.0f); } - m_faceAreas[f] = mesh->faceArea(f); + m_faceAreas[f] = m_mesh->computeFaceArea(f); XA_DEBUG_ASSERT(m_faceAreas[f] > 0.0f); - m_faceNormals[f] = m_mesh->triangleNormal(f); - m_faceTangents[f] = Basis::computeTangent(m_faceNormals[f]); - m_faceBitangents[f] = Basis::computeBitangent(m_faceNormals[f], m_faceTangents[f]); + m_faceNormals[f] = m_mesh->computeFaceNormal(f); } -#if XA_GROW_CHARTS_COPLANAR // Precompute regions of coplanar incident faces. m_nextPlanarRegionFace.resize(faceCount); - for (uint32_t f = 0; f < faceCount; f++) + m_facePlanarRegionId.resize(faceCount); + for (uint32_t f = 0; f < faceCount; f++) { m_nextPlanarRegionFace[f] = f; + m_facePlanarRegionId[f] = UINT32_MAX; + } Array<uint32_t> faceStack; faceStack.reserve(min(faceCount, 16u)); + uint32_t planarRegionCount = 0; for (uint32_t f = 0; f < faceCount; f++) { if (m_nextPlanarRegionFace[f] != f) continue; // Already assigned. - if (m_ignoreFaces[f]) - continue; faceStack.clear(); faceStack.push_back(f); for (;;) { if (faceStack.isEmpty()) break; const uint32_t face = faceStack.back(); + m_facePlanarRegionId[face] = planarRegionCount; faceStack.pop_back(); for (Mesh::FaceEdgeIterator it(m_mesh, face); !it.isDone(); it.advance()) { const uint32_t oface = it.oppositeFace(); - if (it.isBoundary() || m_ignoreFaces[oface]) + if (it.isBoundary()) continue; if (m_nextPlanarRegionFace[oface] != oface) continue; // Already assigned. @@ -4377,29 +4968,33 @@ struct Atlas const uint32_t next = m_nextPlanarRegionFace[face]; m_nextPlanarRegionFace[face] = oface; m_nextPlanarRegionFace[oface] = next; + m_facePlanarRegionId[oface] = planarRegionCount; faceStack.push_back(oface); } } + planarRegionCount++; + } +#if XA_DEBUG_EXPORT_OBJ_PLANAR_REGIONS + char filename[256]; + XA_SPRINTF(filename, sizeof(filename), "debug_mesh_%03u_chartgroup_%03u_planar_regions.obj", meshId, chartGroupId); + FILE *file; + XA_FOPEN(file, filename, "w"); + if (file) { + m_mesh->writeObjVertices(file); + fprintf(file, "s off\n"); + for (uint32_t i = 0; i < planarRegionCount; i++) { + fprintf(file, "o region%u\n", i); + for (uint32_t j = 0; j < faceCount; j++) { + if (m_facePlanarRegionId[j] == i) + m_mesh->writeObjFace(file, j); + } + } + fclose(file); } #endif XA_PROFILE_END(buildAtlasInit) } - ~Atlas() - { - const uint32_t chartCount = m_chartArray.size(); - for (uint32_t i = 0; i < chartCount; i++) { - m_chartArray[i]->~Chart(); - XA_FREE(m_chartArray[i]); - } - } - - 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; } - const Vector2 *faceTexcoords(uint32_t face) const { return &m_texcoords[face * 3]; } - void placeSeeds(float threshold) { XA_PROFILE_START(buildAtlasPlaceSeeds) @@ -4415,28 +5010,51 @@ struct Atlas } // Returns true if any of the charts can grow more. - bool growCharts(float threshold) + void growCharts(float threshold) { XA_PROFILE_START(buildAtlasGrowCharts) - // Build global candidate list. - m_faceCandidateCharts.zeroOutMemory(); - for (uint32_t i = 0; i < m_chartArray.size(); i++) - addChartCandidateToGlobalCandidates(m_chartArray[i]); - // Add one candidate face per chart (threshold permitting). - const uint32_t faceCount = m_mesh->faceCount(); - bool canAddAny = false; - for (uint32_t f = 0; f < faceCount; f++) { - Chart *chart = m_faceCandidateCharts[f]; - if (!chart || m_faceCandidateCosts[f] > threshold) - continue; - createFaceTexcoords(chart, f); - if (!canAddFaceToChart(chart, f)) - continue; - addFaceToChart(chart, f); - canAddAny = true; + for (;;) { + if (m_facesLeft == 0) + break; + // Get the single best candidate out of the chart best candidates. + uint32_t bestFace = UINT32_MAX, bestChart = UINT32_MAX; + float lowestCost = FLT_MAX; + for (uint32_t i = 0; i < m_charts.size(); i++) { + Chart *chart = m_charts[i]; + // Get the best candidate from the chart. + // Cleanup any best candidates that have been claimed by another chart. + uint32_t face = UINT32_MAX; + float cost = FLT_MAX; + for (;;) { + if (chart->candidates.count() == 0) + break; + cost = chart->candidates.peekCost(); + face = chart->candidates.peekFace(); + if (m_faceCharts[face] == -1) + break; + else { + // Face belongs to another chart. Pop from queue so the next best candidate can be retrieved. + chart->candidates.pop(); + face = UINT32_MAX; + } + } + if (face == UINT32_MAX) + continue; // No candidates for this chart. + // See if best candidate overall. + if (cost < lowestCost) { + lowestCost = cost; + bestFace = face; + bestChart = i; + } + } + if (bestFace == UINT32_MAX || lowestCost > threshold) + break; + Chart *chart = m_charts[bestChart]; + chart->candidates.pop(); // Pop the selected candidate from the queue. + if (!addFaceToChart(chart, bestFace)) + chart->failedPlanarRegions.push_back(m_facePlanarRegionId[bestFace]); } XA_PROFILE_END(buildAtlasGrowCharts) - return canAddAny && m_facesLeft != 0; // Can continue growing. } void resetCharts() @@ -4444,53 +5062,34 @@ struct Atlas XA_PROFILE_START(buildAtlasResetCharts) const uint32_t faceCount = m_mesh->faceCount(); for (uint32_t i = 0; i < faceCount; i++) - m_faceChartArray[i] = -1; - m_facesLeft = m_meshFaces ? m_meshFaces->size() : faceCount; - const uint32_t chartCount = m_chartArray.size(); + m_faceCharts[i] = -1; + m_facesLeft = faceCount; + const uint32_t chartCount = m_charts.size(); for (uint32_t i = 0; i < chartCount; i++) { - Chart *chart = m_chartArray[i]; + Chart *chart = m_charts[i]; const uint32_t seed = chart->seeds.back(); chart->area = 0.0f; chart->boundaryLength = 0.0f; - chart->normalSum = Vector3(0.0f); + chart->basis.normal = Vector3(0.0f); + chart->basis.tangent = Vector3(0.0f); + chart->basis.bitangent = Vector3(0.0f); chart->centroidSum = Vector3(0.0f); chart->centroid = Vector3(0.0f); chart->faces.clear(); chart->candidates.clear(); + chart->failedPlanarRegions.clear(); addFaceToChart(chart, seed); } -#if XA_GROW_CHARTS_COPLANAR - for (uint32_t i = 0; i < chartCount; i++) { - Chart *chart = m_chartArray[i]; - growChartCoplanar(chart); - } -#endif XA_PROFILE_END(buildAtlasResetCharts) } - void updateChartCandidates(Chart *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()); - } - // Re-evaluate all candidate priorities. - uint32_t candidateCount = chart->candidates.count(); - for (uint32_t i = 0; i < candidateCount; i++) { - PriorityQueue::Pair &pair = chart->candidates.pairs[i]; - pair.priority = evaluateCost(chart, pair.face); - } - chart->candidates.sort(); - } - bool relocateSeeds() { XA_PROFILE_START(buildAtlasRelocateSeeds) bool anySeedChanged = false; - const uint32_t chartCount = m_chartArray.size(); + const uint32_t chartCount = m_charts.size(); for (uint32_t i = 0; i < chartCount; i++) { - if (relocateSeed(m_chartArray[i])) { + if (relocateSeed(m_charts[i])) { anySeedChanged = true; } } @@ -4510,45 +5109,38 @@ struct Atlas void mergeCharts() { XA_PROFILE_START(buildAtlasMergeCharts) - Array<float> sharedBoundaryLengths; - Array<float> sharedBoundaryLengthsNoSeams; - Array<uint32_t> sharedBoundaryEdgeCountNoSeams; - Array<Vector2> tempTexcoords; - const uint32_t chartCount = m_chartArray.size(); + const uint32_t chartCount = m_charts.size(); // Merge charts progressively until there's none left to merge. for (;;) { bool merged = false; for (int c = chartCount - 1; c >= 0; c--) { - Chart *chart = m_chartArray[c]; + Chart *chart = m_charts[c]; if (chart == nullptr) continue; float externalBoundaryLength = 0.0f; - sharedBoundaryLengths.clear(); - sharedBoundaryLengths.resize(chartCount); - sharedBoundaryLengths.zeroOutMemory(); - sharedBoundaryLengthsNoSeams.clear(); - sharedBoundaryLengthsNoSeams.resize(chartCount); - sharedBoundaryLengthsNoSeams.zeroOutMemory(); - sharedBoundaryEdgeCountNoSeams.clear(); - sharedBoundaryEdgeCountNoSeams.resize(chartCount); - sharedBoundaryEdgeCountNoSeams.zeroOutMemory(); + m_sharedBoundaryLengths.resize(chartCount); + m_sharedBoundaryLengths.zeroOutMemory(); + m_sharedBoundaryLengthsNoSeams.resize(chartCount); + m_sharedBoundaryLengthsNoSeams.zeroOutMemory(); + m_sharedBoundaryEdgeCountNoSeams.resize(chartCount); + m_sharedBoundaryEdgeCountNoSeams.zeroOutMemory(); 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()]) { + if (it.isBoundary()) { externalBoundaryLength += l; } else { - const int neighborChart = m_faceChartArray[it.oppositeFace()]; - if (m_chartArray[neighborChart] != chart) { + const int neighborChart = m_faceCharts[it.oppositeFace()]; + if (m_charts[neighborChart] != chart) { if ((it.isSeam() && (isNormalSeam(it.edge()) || it.isTextureSeam()))) { externalBoundaryLength += l; } else { - sharedBoundaryLengths[neighborChart] += l; + m_sharedBoundaryLengths[neighborChart] += l; } - sharedBoundaryLengthsNoSeams[neighborChart] += l; - sharedBoundaryEdgeCountNoSeams[neighborChart]++; + m_sharedBoundaryLengthsNoSeams[neighborChart] += l; + m_sharedBoundaryEdgeCountNoSeams[neighborChart]++; } } } @@ -4556,50 +5148,38 @@ struct Atlas for (int cc = chartCount - 1; cc >= 0; cc--) { if (cc == c) continue; - Chart *chart2 = m_chartArray[cc]; + Chart *chart2 = m_charts[cc]; if (chart2 == nullptr) continue; + // Must share a boundary. + if (m_sharedBoundaryLengths[cc] <= 0.0f) + continue; // Compare proxies. - if (dot(chart2->averageNormal, chart->averageNormal) < XA_MERGE_CHARTS_MIN_NORMAL_DEVIATION) + if (dot(chart2->basis.normal, chart->basis.normal) < 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) + if (m_options.maxBoundaryLength > 0.0f && chart->boundaryLength + chart2->boundaryLength - m_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) + if (m_sharedBoundaryLengthsNoSeams[cc] > 0.0f && chart->faces.size() > 1 && chart2->faces.size() == 1 && chart2->area <= chart->area * 0.1f) goto merge; // Merge if chart2 has two faces (probably a quad), and chart1 bounds at least 2 of its edges. - if (chart2->faces.size() == 2 && sharedBoundaryEdgeCountNoSeams[cc] >= 2) + if (chart2->faces.size() == 2 && m_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)) + if (m_sharedBoundaryLengthsNoSeams[cc] > 0.0f && equal(m_sharedBoundaryLengthsNoSeams[cc], chart2->boundaryLength, kEpsilon)) goto merge; - if (sharedBoundaryLengths[cc] > 0.2f * max(0.0f, chart->boundaryLength - externalBoundaryLength) || - sharedBoundaryLengths[cc] > 0.75f * chart2->boundaryLength) + if (m_sharedBoundaryLengths[cc] > 0.2f * max(0.0f, chart->boundaryLength - externalBoundaryLength) || + m_sharedBoundaryLengths[cc] > 0.75f * chart2->boundaryLength) goto merge; continue; merge: - // 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() * 3); - for (uint32_t i = 0; i < chart2->faces.size(); i++) { - const uint32_t face = chart2->faces[i]; - for (uint32_t j = 0; j < 3; j++) - tempTexcoords[i * 3 + j] = m_texcoords[face * 3 + j]; - createFaceTexcoords(chart, face); - } - if (!canMergeCharts(chart, chart2)) { - // Restore chart 2 texcoords. - for (uint32_t i = 0; i < chart2->faces.size(); i++) { - for (uint32_t j = 0; j < 3; j++) - m_texcoords[chart2->faces[i] * 3 + j] = tempTexcoords[i * 3 + j]; - } + if (!mergeChart(chart, chart2, m_sharedBoundaryLengthsNoSeams[cc])) continue; - } - mergeChart(chart, chart2, sharedBoundaryLengthsNoSeams[cc]); merged = true; break; } @@ -4610,20 +5190,20 @@ struct Atlas 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 (int c = 0; c < int32_t(m_charts.size()); /*do not increment if removed*/) { + if (m_charts[c] == nullptr) { + m_charts.removeAt(c); + // Update m_faceCharts. + const uint32_t faceCount = m_faceCharts.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]--; + XA_DEBUG_ASSERT(m_faceCharts[i] != c); + XA_DEBUG_ASSERT(m_faceCharts[i] <= int32_t(m_charts.size())); + if (m_faceCharts[i] > c) { + m_faceCharts[i]--; } } } else { - m_chartArray[c]->id = c; + m_charts[c]->id = c; c++; } } @@ -4635,264 +5215,204 @@ private: void createRandomChart(float threshold) { Chart *chart = XA_NEW(MemTag::Default, Chart); - chart->id = (int)m_chartArray.size(); - m_chartArray.push_back(chart); + chart->id = (int)m_charts.size(); + m_charts.push_back(chart); // Pick random face that is not used by any chart yet. uint32_t face = m_rand.getRange(m_mesh->faceCount() - 1); - while (m_ignoreFaces[face] || m_faceChartArray[face] != -1) { + while (m_faceCharts[face] != -1) { if (++face >= m_mesh->faceCount()) face = 0; } chart->seeds.push_back(face); addFaceToChart(chart, face); -#if XA_GROW_CHARTS_COPLANAR - growChartCoplanar(chart); -#endif // Grow the chart as much as possible within the given threshold. - for (uint32_t i = 0; i < m_facesLeft; ) { - if (chart->candidates.count() == 0 || chart->candidates.firstPriority() > threshold) + for (;;) { + if (chart->candidates.count() == 0 || chart->candidates.peekCost() > threshold) break; const uint32_t f = chart->candidates.pop(); - if (m_faceChartArray[f] != -1) + if (m_faceCharts[f] != -1) continue; - createFaceTexcoords(chart, f); - if (!canAddFaceToChart(chart, f)) + if (!addFaceToChart(chart, f)) { + chart->failedPlanarRegions.push_back(m_facePlanarRegionId[f]); continue; - addFaceToChart(chart, f); - i++; - } - } - - void addChartCandidateToGlobalCandidates(Chart *chart) - { - if (chart->candidates.count() == 0) - return; - const float cost = chart->candidates.firstPriority(); - const uint32_t face = chart->candidates.pop(); - if (m_faceChartArray[face] != -1) { - addChartCandidateToGlobalCandidates(chart); - } else if (!m_faceCandidateCharts[face]) { - // No candidate assigned to this face yet. - m_faceCandidateCharts[face] = chart; - m_faceCandidateCosts[face] = cost; - } else { - if (cost < m_faceCandidateCosts[face]) { - // This is a better candidate for this face (lower cost). The other chart can choose another candidate. - Chart *otherChart = m_faceCandidateCharts[face]; - m_faceCandidateCharts[face] = chart; - m_faceCandidateCosts[face] = cost; - addChartCandidateToGlobalCandidates(otherChart); - } else { - // Existing candidate is better. This chart can choose another candidate. - addChartCandidateToGlobalCandidates(chart); } } } - void createFaceTexcoords(Chart *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(const Chart *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; + return oppositeEdge == UINT32_MAX || m_faceCharts[oppositeFace] != chart->id; } - bool edgeArraysIntersect(const uint32_t *edges1, uint32_t edges1Count, const uint32_t *edges2, uint32_t edges2Count) + bool computeChartBasis(Chart *chart, Basis *basis) { - for (uint32_t i = 0; i < edges1Count; i++) { - const uint32_t edge1 = edges1[i]; - for (uint32_t j = 0; j < edges2Count; j++) { - const uint32_t edge2 = edges2[j]; - const Vector2 &a1 = m_texcoords[meshEdgeIndex0(edge1)]; - const Vector2 &a2 = m_texcoords[meshEdgeIndex1(edge1)]; - const Vector2 &b1 = m_texcoords[meshEdgeIndex0(edge2)]; - const Vector2 &b2 = m_texcoords[meshEdgeIndex1(edge2)]; - if (linesIntersect(a1, a2, b1, b2, m_mesh->epsilon())) - return true; - } + const uint32_t faceCount = chart->faces.size(); + m_tempPoints.resize(chart->faces.size() * 3); + for (uint32_t i = 0; i < faceCount; i++) { + const uint32_t f = chart->faces[i]; + for (uint32_t j = 0; j < 3; j++) + m_tempPoints[i * 3 + j] = m_mesh->position(m_mesh->vertexAt(f * 3 + j)); } - return false; + return Fit::computeBasis(m_tempPoints.data(), m_tempPoints.size(), basis); } bool isFaceFlipped(uint32_t face) const { - const float t1 = m_texcoords[face * 3 + 0].x; - const float s1 = m_texcoords[face * 3 + 0].y; - const float t2 = m_texcoords[face * 3 + 1].x; - const float s2 = m_texcoords[face * 3 + 1].y; - const float t3 = m_texcoords[face * 3 + 2].x; - const float s3 = m_texcoords[face * 3 + 2].y; - const float parametricArea = ((s2 - s1) * (t3 - t1) - (s3 - s1) * (t2 - t1)) / 2; + const Vector2 &v1 = m_texcoords[face * 3 + 0]; + const Vector2 &v2 = m_texcoords[face * 3 + 1]; + const Vector2 &v3 = m_texcoords[face * 3 + 2]; + const float parametricArea = ((v2.x - v1.x) * (v3.y - v1.y) - (v3.x - v1.x) * (v2.y - v1.y)) * 0.5f; return parametricArea < 0.0f; } - void computeChartBoundaryEdges(const Chart *chart, Array<uint32_t> *dest) const + void parameterizeChart(const Chart *chart) { - dest->clear(); - for (uint32_t f = 0; f < chart->faces.size(); f++) { - const uint32_t face = chart->faces[f]; - for (uint32_t i = 0; i < 3; i++) { - const uint32_t edge = face * 3 + i; - if (isChartBoundaryEdge(chart, edge)) - dest->push_back(edge); + const uint32_t faceCount = chart->faces.size(); + for (uint32_t i = 0; i < faceCount; i++) { + const uint32_t face = chart->faces[i]; + for (uint32_t j = 0; j < 3; j++) { + const uint32_t offset = face * 3 + j; + const Vector3 &pos = m_mesh->position(m_mesh->vertexAt(offset)); + m_texcoords[offset] = Vector2(dot(chart->basis.tangent, pos), dot(chart->basis.bitangent, pos)); } } } - bool canAddFaceToChart(Chart *chart, uint32_t face) + // m_faceCharts for the chart faces must be set to the chart ID. Needed to compute boundary edges. + bool isChartParameterizationValid(const Chart *chart) { - // Check for flipped triangles. - if (isFaceFlipped(face)) + const uint32_t faceCount = chart->faces.size(); + // Check for flipped faces in the parameterization. OK if all are flipped. + uint32_t flippedFaceCount = 0; + for (uint32_t i = 0; i < faceCount; i++) { + if (isFaceFlipped(chart->faces[i])) + flippedFaceCount++; + } + if (flippedFaceCount != 0 && flippedFaceCount != faceCount) return false; - // Find face edges that don't border this chart. - m_tempEdges1.clear(); - for (uint32_t i = 0; i < 3; i++) { - const uint32_t edge = face * 3 + i; - if (isChartBoundaryEdge(chart, edge)) - m_tempEdges1.push_back(edge); - } - if (m_tempEdges1.isEmpty()) - return true; // This can happen if the face is surrounded by the chart. - // Get chart boundary edges, except those that border the face. - m_tempEdges2.clear(); - for (uint32_t i = 0; i < chart->faces.size(); i++) { - const uint32_t chartFace = chart->faces[i]; + // Check for boundary intersection in the parameterization. + m_boundaryGrid.reset(m_texcoords.data()); + for (uint32_t i = 0; i < faceCount; i++) { + const uint32_t f = 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; - m_tempEdges2.push_back(chartEdge); - } - } - const bool intersect = edgeArraysIntersect(m_tempEdges1.data(), m_tempEdges1.size(), m_tempEdges2.data(), m_tempEdges2.size()); -#if 0 - if (intersect) { - static std::atomic<uint32_t> count = 0; - char filename[256]; - XA_SPRINTF(filename, sizeof(filename), "intersect%04u.obj", count.fetch_add(1)); - FILE *file; - XA_FOPEN(file, filename, "w"); - if (file) { - for (uint32_t i = 0; i < m_texcoords.size(); i++) - fprintf(file, "v %g %g 0.0\n", m_texcoords[i].x, m_texcoords[i].y); - fprintf(file, "s off\n"); - fprintf(file, "o face\n"); - { - fprintf(file, "f "); - for (uint32_t j = 0; j < 3; j++) { - const uint32_t index = face * 3 + j + 1; // 1-indexed - fprintf(file, "%d/%d/%d%c", index, index, index, j == 2 ? '\n' : ' '); - } - } - fprintf(file, "s off\n"); - fprintf(file, "o chart\n"); - for (uint32_t i = 0; i < chart->faces.size(); i++) { - const uint32_t chartFace = chart->faces[i]; - fprintf(file, "f "); - for (uint32_t j = 0; j < 3; j++) { - const uint32_t index = chartFace * 3 + j + 1; // 1-indexed - fprintf(file, "%d/%d/%d%c", index, index, index, j == 2 ? '\n' : ' '); - } - } - fclose(file); + const uint32_t edge = f * 3 + j; + if (isChartBoundaryEdge(chart, edge)) + m_boundaryGrid.append(edge); } } -#endif - return !intersect; + if (m_boundaryGrid.intersectSelf(m_mesh->epsilon())) + return false; + return true; } - bool canMergeCharts(Chart *chart1, Chart *chart2) + bool addFaceToChart(Chart *chart, uint32_t face) { - for (uint32_t i = 0; i < chart2->faces.size(); i++) { - if (isFaceFlipped(chart2->faces[i])) - return false; + XA_DEBUG_ASSERT(m_faceCharts[face] == -1); + const uint32_t oldFaceCount = chart->faces.size(); + const bool firstFace = oldFaceCount == 0; + // Append the face and any coplanar connected faces to the chart faces array. + chart->faces.push_back(face); + uint32_t coplanarFace = m_nextPlanarRegionFace[face]; + while (coplanarFace != face) { + XA_DEBUG_ASSERT(m_faceCharts[coplanarFace] == -1); + chart->faces.push_back(coplanarFace); + coplanarFace = m_nextPlanarRegionFace[coplanarFace]; } - computeChartBoundaryEdges(chart1, &m_tempEdges1); - computeChartBoundaryEdges(chart2, &m_tempEdges2); - return !edgeArraysIntersect(m_tempEdges1.data(), m_tempEdges1.size(), m_tempEdges2.data(), m_tempEdges2.size()); - } - - void addFaceToChart(Chart *chart, uint32_t f) - { - const bool firstFace = chart->faces.isEmpty(); - // Use the first face normal as the chart basis. + const uint32_t faceCount = chart->faces.size(); + // Compute basis. + Basis basis; if (firstFace) { - chart->basis.normal = m_faceNormals[f]; - chart->basis.tangent = m_faceTangents[f]; - chart->basis.bitangent = m_faceBitangents[f]; - createFaceTexcoords(chart, f); - } - // Add face to chart. - chart->faces.push_back(f); - XA_DEBUG_ASSERT(m_faceChartArray[f] == -1); - m_faceChartArray[f] = chart->id; - m_facesLeft--; - // Update area and boundary length. - chart->area = chart->area + m_faceAreas[f]; - chart->boundaryLength = computeBoundaryLength(chart, f); - chart->normalSum += m_mesh->triangleNormalAreaScaled(f); - chart->averageNormal = normalizeSafe(chart->normalSum, Vector3(0), 0.0f); - chart->centroidSum += m_mesh->triangleCenter(f); + // Use the first face normal. + // Use any edge as the tangent vector. + basis.normal = m_faceNormals[face]; + basis.tangent = normalize(m_mesh->position(m_mesh->vertexAt(face * 3 + 0)) - m_mesh->position(m_mesh->vertexAt(face * 3 + 1)), kEpsilon); + basis.bitangent = cross(basis.normal, basis.tangent); + } else { + // Use best fit normal. + if (!computeChartBasis(chart, &basis)) { + chart->faces.resize(oldFaceCount); + return false; + } + if (dot(basis.normal, m_faceNormals[face]) < 0.0f) // Flip normal if oriented in the wrong direction. + basis.normal = -basis.normal; + } + if (!firstFace) { + // Compute orthogonal parameterization and check that it is valid. + parameterizeChart(chart); + for (uint32_t i = oldFaceCount; i < faceCount; i++) + m_faceCharts[chart->faces[i]] = chart->id; + if (!isChartParameterizationValid(chart)) { + for (uint32_t i = oldFaceCount; i < faceCount; i++) + m_faceCharts[chart->faces[i]] = -1; + chart->faces.resize(oldFaceCount); + return false; + } + } + // Add face(s) to chart. + chart->basis = basis; + chart->area = computeArea(chart, face); + chart->boundaryLength = computeBoundaryLength(chart, face); + for (uint32_t i = oldFaceCount; i < faceCount; i++) { + const uint32_t f = chart->faces[i]; + m_faceCharts[f] = chart->id; + m_facesLeft--; + chart->centroidSum += m_mesh->computeFaceCenter(f); + } chart->centroid = chart->centroidSum / float(chart->faces.size()); - // Update candidates. - updateChartCandidates(chart, f); - } - -#if XA_GROW_CHARTS_COPLANAR - void growChartCoplanar(Chart *chart) - { - XA_DEBUG_ASSERT(!chart->faces.isEmpty()); - for (uint32_t i = 0; i < chart->faces.size(); i++) { - const uint32_t chartFace = chart->faces[i]; - uint32_t face = m_nextPlanarRegionFace[chartFace]; - while (face != chartFace) { - // Not assigned to a chart? - if (m_faceChartArray[face] == -1) { - createFaceTexcoords(chart, face); - addFaceToChart(chart, face); - } - face = m_nextPlanarRegionFace[face]; + // Refresh candidates. + chart->candidates.clear(); + for (uint32_t i = 0; i < faceCount; i++) { + // Traverse neighboring faces, add the ones that do not belong to any chart yet. + const uint32_t f = chart->faces[i]; + for (uint32_t j = 0; j < 3; j++) { + const uint32_t edge = f * 3 + j; + const uint32_t oedge = m_mesh->oppositeEdge(edge); + if (oedge == UINT32_MAX) + continue; // Boundary edge. + const uint32_t oface = meshEdgeFace(oedge); + if (m_faceCharts[oface] != -1) + continue; // Face belongs to another chart. + if (chart->failedPlanarRegions.contains(m_facePlanarRegionId[oface])) + continue; // Failed to add this faces planar region to the chart before. + const float cost = evaluateCost(chart, oface); + if (cost < FLT_MAX) + chart->candidates.push(cost, oface); } } + return true; } -#endif + // Returns true if the seed has changed. bool relocateSeed(Chart *chart) { // 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]); - m_bestTriangles.push(priority, chart->faces[i]); + const float cost = evaluateProxyFitMetric(chart, chart->faces[i]); + m_bestTriangles.push(cost, chart->faces[i]); } // Of those, choose the least central triangle. uint32_t leastCentral = 0; float maxDistance = -1; - const uint32_t bestCount = m_bestTriangles.count(); - for (uint32_t i = 0; i < bestCount; i++) { - Vector3 faceCentroid = m_mesh->triangleCenter(m_bestTriangles.pairs[i].face); - float distance = length(chart->centroid - faceCentroid); + for (;;) { + if (m_bestTriangles.count() == 0) + break; + const uint32_t face = m_bestTriangles.pop(); + Vector3 faceCentroid = m_mesh->computeFaceCenter(face); + const float distance = length(chart->centroid - faceCentroid); if (distance > maxDistance) { maxDistance = distance; - leastCentral = m_bestTriangles.pairs[i].face; + leastCentral = face; } } XA_DEBUG_ASSERT(maxDistance >= 0); // In order to prevent k-means cyles we record all the previously chosen seeds. for (uint32_t i = 0; i < chart->seeds.size(); i++) { - if (chart->seeds[i] == leastCentral) { + // Treat seeds belong to the same planar region as equal. + if (chart->seeds[i] == leastCentral || m_facePlanarRegionId[chart->seeds[i]] == m_facePlanarRegionId[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]); @@ -4907,26 +5427,32 @@ private: // Evaluate combined metric. float evaluateCost(Chart *chart, uint32_t face) const { + if (dot(m_faceNormals[face], chart->basis.normal) <= 0.26f) // ~75 degrees + return FLT_MAX; // Estimate boundary length and area: - const float newChartArea = chart->area + m_faceAreas[face]; - const float newBoundaryLength = computeBoundaryLength(chart, face); + float newChartArea = 0.0f, newBoundaryLength = 0.0f; + if (m_options.maxChartArea > 0.0f || m_options.roundnessMetricWeight > 0.0f) + newChartArea = computeArea(chart, face); + if (m_options.maxBoundaryLength > 0.0f || m_options.roundnessMetricWeight > 0.0f) + newBoundaryLength = computeBoundaryLength(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. - // 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; + float cost = 0.0f; + if (m_options.normalSeamMetricWeight > 0.0f) { + // Penalize faces that cross seams, reward faces that close seams or reach boundaries. + // 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; + 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); + cost += m_options.roundnessMetricWeight * evaluateRoundnessMetric(chart, newBoundaryLength, newChartArea); if (m_options.straightnessMetricWeight > 0.0f) cost += m_options.straightnessMetricWeight * evaluateStraightnessMetric(chart, face); if (m_options.textureSeamMetricWeight > 0.0f) @@ -4941,40 +5467,45 @@ private: } // Returns a value in [0-1]. - float evaluateProxyFitMetric(Chart *chart, uint32_t f) const + float evaluateProxyFitMetric(Chart *chart, uint32_t face) const { - const Vector3 faceNormal = m_faceNormals[f]; + // All faces in coplanar regions have the same normal, can use any face. + const Vector3 faceNormal = m_faceNormals[face]; // Use plane fitting metric for now: - return 1 - dot(faceNormal, chart->averageNormal); // @@ normal deviations should be weighted by face area + return 1 - dot(faceNormal, chart->basis.normal); // @@ normal deviations should be weighted by face area } - float evaluateRoundnessMetric(Chart *chart, uint32_t /*face*/, float newBoundaryLength, float newChartArea) const + float evaluateRoundnessMetric(Chart *chart, 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.0f * kPi); - } else { - // Offer no impedance to faces that improve roundness. - return 0; - } + const float roundness = square(chart->boundaryLength) / chart->area; + const float newBoundaryLengthSq = square(newBoundaryLength); + const float newRoundness = newBoundaryLengthSq / newChartArea; + if (newRoundness > roundness) + return newBoundaryLengthSq / (newChartArea * kPi4); + // Offer no impedance to faces that improve roundness. + return 0; } - float evaluateStraightnessMetric(Chart *chart, uint32_t f) const + float evaluateStraightnessMetric(Chart *chart, uint32_t firstFace) const { - float l_out = 0.0f; - float l_in = 0.0f; - 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 { - if (m_faceChartArray[it.oppositeFace()] != chart->id) { + float l_out = 0.0f, l_in = 0.0f; + const uint32_t planarRegionId = m_facePlanarRegionId[firstFace]; + uint32_t face = firstFace; + for (;;) { + for (Mesh::FaceEdgeIterator it(m_mesh, face); !it.isDone(); it.advance()) { + const float l = m_edgeLengths[it.edge()]; + if (it.isBoundary()) { l_out += l; - } else { - l_in += l; + } else if (m_facePlanarRegionId[it.oppositeFace()] != planarRegionId) { + if (m_faceCharts[it.oppositeFace()] != chart->id) + l_out += l; + else + l_in += l; } } + face = m_nextPlanarRegionFace[face]; + if (face == firstFace) + break; } 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); @@ -4991,128 +5522,184 @@ private: 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); + if (v0 == ov1 && v1 == ov0) + return false; + return !equal(m_mesh->normal(v0), m_mesh->normal(ov1), kNormalEpsilon) || !equal(m_mesh->normal(v1), m_mesh->normal(ov0), kNormalEpsilon); } - return m_faceNormals[meshEdgeFace(edge)] != m_faceNormals[meshEdgeFace(oppositeEdge)]; + const uint32_t f0 = meshEdgeFace(edge); + const uint32_t f1 = meshEdgeFace(oppositeEdge); + if (m_facePlanarRegionId[f0] == m_facePlanarRegionId[f1]) + return false; + return !equal(m_faceNormals[f0], m_faceNormals[f1], kNormalEpsilon); } - float evaluateNormalSeamMetric(Chart *chart, uint32_t f) const + float evaluateNormalSeamMetric(Chart *chart, uint32_t firstFace) const { - float seamFactor = 0.0f; - float totalLength = 0.0f; - for (Mesh::FaceEdgeIterator it(m_mesh, f); !it.isDone(); it.advance()) { - if (it.isBoundary() || m_ignoreFaces[it.oppositeFace()]) - continue; - if (m_faceChartArray[it.oppositeFace()] != chart->id) - continue; - float l = m_edgeLengths[it.edge()]; - totalLength += l; - if (!it.isSeam()) - continue; - // Make sure it's a normal seam. - 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); + float seamFactor = 0.0f, totalLength = 0.0f; + uint32_t face = firstFace; + for (;;) { + for (Mesh::FaceEdgeIterator it(m_mesh, face); !it.isDone(); it.advance()) { + if (it.isBoundary()) + continue; + if (m_faceCharts[it.oppositeFace()] != chart->id) + continue; + float l = m_edgeLengths[it.edge()]; + totalLength += l; + if (!it.isSeam()) + continue; + // Make sure it's a normal seam. + 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[face], m_faceNormals[meshEdgeFace(it.oppositeEdge())]), 0.0f, 1.0f); + } + l *= 1 - d; + seamFactor += l; } - l *= 1 - d; - seamFactor += l; } + face = m_nextPlanarRegionFace[face]; + if (face == firstFace) + break; } if (seamFactor <= 0.0f) return 0.0f; return seamFactor / totalLength; } - float evaluateTextureSeamMetric(Chart *chart, uint32_t f) const + float evaluateTextureSeamMetric(Chart *chart, uint32_t firstFace) const { - float seamLength = 0.0f; - float totalLength = 0.0f; - for (Mesh::FaceEdgeIterator it(m_mesh, f); !it.isDone(); it.advance()) { - if (it.isBoundary() || m_ignoreFaces[it.oppositeFace()]) - continue; - if (m_faceChartArray[it.oppositeFace()] != chart->id) - continue; - float l = m_edgeLengths[it.edge()]; - totalLength += l; - if (!it.isSeam()) - continue; - // Make sure it's a texture seam. - if (it.isTextureSeam()) - seamLength += l; + float seamLength = 0.0f, totalLength = 0.0f; + uint32_t face = firstFace; + for (;;) { + for (Mesh::FaceEdgeIterator it(m_mesh, face); !it.isDone(); it.advance()) { + if (it.isBoundary()) + continue; + if (m_faceCharts[it.oppositeFace()] != chart->id) + continue; + float l = m_edgeLengths[it.edge()]; + totalLength += l; + if (!it.isSeam()) + continue; + // Make sure it's a texture seam. + if (it.isTextureSeam()) + seamLength += l; + } + face = m_nextPlanarRegionFace[face]; + if (face == firstFace) + break; } - if (seamLength == 0.0f) + if (seamLength <= 0.0f) return 0.0f; // Avoid division by zero. return seamLength / totalLength; } - float computeBoundaryLength(Chart *chart, uint32_t f) const + float computeArea(Chart *chart, uint32_t firstFace) const + { + float area = chart->area; + uint32_t face = firstFace; + for (;;) { + area += m_faceAreas[face]; + face = m_nextPlanarRegionFace[face]; + if (face == firstFace) + break; + } + return area; + } + + float computeBoundaryLength(Chart *chart, uint32_t firstFace) const { float boundaryLength = chart->boundaryLength; // Add new edges, subtract edges shared with the chart. - 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 { - if (m_faceChartArray[it.oppositeFace()] != chart->id) + const uint32_t planarRegionId = m_facePlanarRegionId[firstFace]; + uint32_t face = firstFace; + for (;;) { + for (Mesh::FaceEdgeIterator it(m_mesh, face); !it.isDone(); it.advance()) { + const float edgeLength = m_edgeLengths[it.edge()]; + if (it.isBoundary()) { boundaryLength += edgeLength; - else - boundaryLength -= edgeLength; + } else if (m_facePlanarRegionId[it.oppositeFace()] != planarRegionId) { + if (m_faceCharts[it.oppositeFace()] != chart->id) + boundaryLength += edgeLength; + else + boundaryLength -= edgeLength; + } } + face = m_nextPlanarRegionFace[face]; + if (face == firstFace) + break; } return max(0.0f, boundaryLength); // @@ Hack! } - void mergeChart(Chart *owner, Chart *chart, float sharedBoundaryLength) + bool mergeChart(Chart *owner, Chart *chart, float sharedBoundaryLength) { - const uint32_t faceCount = chart->faces.size(); - for (uint32_t i = 0; i < faceCount; i++) { - uint32_t f = chart->faces[i]; - XA_DEBUG_ASSERT(m_faceChartArray[f] == chart->id); - m_faceChartArray[f] = owner->id; - owner->faces.push_back(f); + const uint32_t oldOwnerFaceCount = owner->faces.size(); + const uint32_t chartFaceCount = chart->faces.size(); + owner->faces.push_back(chart->faces); + for (uint32_t i = 0; i < chartFaceCount; i++) { + XA_DEBUG_ASSERT(m_faceCharts[chart->faces[i]] == chart->id); + m_faceCharts[chart->faces[i]] = owner->id; + } + // Compute basis using best fit normal. + Basis basis; + if (!computeChartBasis(owner, &basis)) { + owner->faces.resize(oldOwnerFaceCount); + for (uint32_t i = 0; i < chartFaceCount; i++) + m_faceCharts[chart->faces[i]] = chart->id; + return false; + } + if (dot(basis.normal, m_faceNormals[owner->faces[0]]) < 0.0f) // Flip normal if oriented in the wrong direction. + basis.normal = -basis.normal; + // Compute orthogonal parameterization and check that it is valid. + parameterizeChart(owner); + if (!isChartParameterizationValid(owner)) { + owner->faces.resize(oldOwnerFaceCount); + for (uint32_t i = 0; i < chartFaceCount; i++) + m_faceCharts[chart->faces[i]] = chart->id; + return false; } + // Merge chart. + owner->basis = basis; + owner->failedPlanarRegions.push_back(chart->failedPlanarRegions); // Update adjacencies? owner->area += chart->area; owner->boundaryLength += chart->boundaryLength - sharedBoundaryLength; - owner->normalSum += chart->normalSum; - owner->averageNormal = normalizeSafe(owner->normalSum, Vector3(0), 0.0f); // Delete chart. - m_chartArray[chart->id] = nullptr; + m_charts[chart->id] = nullptr; chart->~Chart(); XA_FREE(chart); + return true; } 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<Vector3> m_faceTangents; - Array<Vector3> m_faceBitangents; Array<Vector2> m_texcoords; uint32_t m_facesLeft; - Array<int> m_faceChartArray; - Array<Chart *> m_chartArray; - PriorityQueue m_bestTriangles; + Array<int> m_faceCharts; + Array<Chart *> m_charts; + CostQueue m_bestTriangles; KISSRng m_rand; ChartOptions m_options; - Array<Chart *> m_faceCandidateCharts; - Array<float> m_faceCandidateCosts; -#if XA_GROW_CHARTS_COPLANAR Array<uint32_t> m_nextPlanarRegionFace; + Array<uint32_t> m_facePlanarRegionId; + Array<Vector3> m_tempPoints; + UniformGrid2 m_boundaryGrid; +#if XA_MERGE_CHARTS + // mergeCharts + Array<float> m_sharedBoundaryLengths; + Array<float> m_sharedBoundaryLengthsNoSeams; + Array<uint32_t> m_sharedBoundaryEdgeCountNoSeams; #endif - Array<uint32_t> m_tempEdges1, m_tempEdges2; }; } // namespace segment @@ -5301,7 +5888,11 @@ private: // q = A·p sparse::mult(A, p, q); // alpha = delta_new / p·q - alpha = delta_new / sparse::dot(p, q); + const float pdotq = sparse::dot(p, q); + if (!isFinite(pdotq) || isNan(pdotq)) + alpha = 0.0f; + else + alpha = delta_new / pdotq; // x = alfa·p + x sparse::saxpy(alpha, p, x); if ((i & 31) == 0) { // recompute r after 32 steps @@ -5514,186 +6105,518 @@ static bool computeLeastSquaresConformalMap(Mesh *mesh) // Solve Solver::LeastSquaresSolver(A, b, x, lockedParameters, 4, 0.000001f); // Map x back to texcoords: - for (uint32_t v = 0; v < vertexCount; v++) + for (uint32_t v = 0; v < vertexCount; v++) { mesh->texcoord(v) = Vector2(x[2 * v + 0], x[2 * v + 1]); + XA_DEBUG_ASSERT(!isNan(mesh->texcoord(v).x)); + XA_DEBUG_ASSERT(!isNan(mesh->texcoord(v).y)); + } return true; } -static bool computeOrthogonalProjectionMap(Mesh *mesh) +#if XA_RECOMPUTE_CHARTS +struct PiecewiseParam { - uint32_t vertexCount = mesh->vertexCount(); - // Avoid redundant computations. - float matrix[6]; - 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)) - return false; - Vector3 axis[2]; - axis[0] = normalize(eigenVectors[0], kEpsilon); - axis[1] = normalize(eigenVectors[1], kEpsilon); - // Project vertices to plane. - 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 reset(const Mesh *mesh, uint32_t faceCount) + { + m_mesh = mesh; + m_faceCount = faceCount; + const uint32_t vertexCount = m_mesh->vertexCount(); + m_texcoords.resize(vertexCount); + m_patch.reserve(m_faceCount); + m_faceAssigned.resize(m_faceCount); + m_faceAssigned.zeroOutMemory(); + m_faceInvalid.resize(m_faceCount); + m_faceInPatch.resize(m_faceCount); + m_vertexInPatch.resize(vertexCount); + m_faceInCandidates.resize(m_faceCount); + } + + ConstArrayView<uint32_t> chartFaces() const { return m_patch; } + const Vector2 *texcoords() const { return m_texcoords.data(); } + + bool computeChart() + { + m_patch.clear(); + m_faceInvalid.zeroOutMemory(); + m_faceInPatch.zeroOutMemory(); + m_vertexInPatch.zeroOutMemory(); + // Add the seed face (first unassigned face) to the patch. + uint32_t seed = UINT32_MAX; + for (uint32_t f = 0; f < m_faceCount; f++) { + if (m_faceAssigned.get(f)) + continue; + seed = f; + m_patch.push_back(seed); + m_faceInPatch.set(seed); + m_faceAssigned.set(seed); + Vector2 texcoords[3]; + orthoProjectFace(seed, texcoords); + for (uint32_t i = 0; i < 3; i++) { + const uint32_t vertex = m_mesh->vertexAt(seed * 3 + i); + m_vertexInPatch.set(vertex); + m_texcoords[vertex] = texcoords[i]; + } + break; + } + if (seed == UINT32_MAX) + return false; + for (;;) { + findCandidates(); + if (m_candidates.isEmpty()) + break; + for (;;) { + // Find the candidate with the lowest cost. + float lowestCost = FLT_MAX; + uint32_t bestCandidate = UINT32_MAX; + for (uint32_t i = 0; i < m_candidates.size(); i++) { + const Candidate &candidate = m_candidates[i]; + if (m_faceInvalid.get(candidate.face)) // A candidate face may be invalidated after is was added. + continue; + if (candidate.maxCost < lowestCost) { + lowestCost = candidate.maxCost; + bestCandidate = i; + } + } + if (bestCandidate == UINT32_MAX) + break; + // Compute the position by averaging linked candidates (candidates that share the same free vertex). + Vector2 position(0.0f); + uint32_t n = 0; + for (CandidateIterator it(m_candidates, bestCandidate); !it.isDone(); it.advance()) { + position += it.current().position; + n++; + } + position *= 1.0f / (float)n; + const uint32_t freeVertex = m_candidates[bestCandidate].vertex; + XA_DEBUG_ASSERT(!isNan(position.x)); + XA_DEBUG_ASSERT(!isNan(position.y)); + m_texcoords[freeVertex] = position; + // Check for flipped faces. This is also done when candidates are first added, but the averaged position of the free vertex is different now, so check again. + bool invalid = false; + for (CandidateIterator it(m_candidates, bestCandidate); !it.isDone(); it.advance()) { + const uint32_t vertex0 = m_mesh->vertexAt(meshEdgeIndex0(it.current().patchEdge)); + const uint32_t vertex1 = m_mesh->vertexAt(meshEdgeIndex1(it.current().patchEdge)); + const float freeVertexOrient = orientToEdge(m_texcoords[vertex0], m_texcoords[vertex1], position); + if ((it.current().patchVertexOrient < 0.0f && freeVertexOrient < 0.0f) || (it.current().patchVertexOrient > 0.0f && freeVertexOrient > 0.0f)) { + invalid = true; + break; + } + } + // Check for boundary intersection. + if (!invalid) { + m_boundaryGrid.reset(m_texcoords.data(), m_mesh->indices()); + // Add edges on the patch boundary to the grid. + // Temporarily adding candidate faces to the patch makes it simpler to detect which edges are on the boundary. + const uint32_t oldPatchSize = m_patch.size(); + for (CandidateIterator it(m_candidates, bestCandidate); !it.isDone(); it.advance()) + m_patch.push_back(it.current().face); + for (uint32_t i = 0; i < m_patch.size(); i++) { + for (Mesh::FaceEdgeIterator it(m_mesh, m_patch[i]); !it.isDone(); it.advance()) { + const uint32_t oface = it.oppositeFace(); + if (oface == UINT32_MAX || oface >= m_faceCount || !m_faceInPatch.get(oface)) + m_boundaryGrid.append(it.edge()); + } + } + invalid = m_boundaryGrid.intersectSelf(m_mesh->epsilon()); + m_patch.resize(oldPatchSize); + } + if (invalid) { + // Mark all faces of linked candidates as invalid. + for (CandidateIterator it(m_candidates, bestCandidate); !it.isDone(); it.advance()) + m_faceInvalid.set(it.current().face); + continue; + } + // Add faces to the patch. + for (CandidateIterator it(m_candidates, bestCandidate); !it.isDone(); it.advance()) { + m_patch.push_back(it.current().face); + m_faceInPatch.set(it.current().face); + m_faceAssigned.set(it.current().face); + } + // Add vertex to the patch. + m_vertexInPatch.set(freeVertex); + // Successfully added candidate face(s) to patch. + break; + } + } + return true; + } + +private: + struct Candidate + { + uint32_t face, vertex; + uint32_t next; // The next candidate with the same vertex. + Vector2 position; + float cost; + float maxCost; // Of all linked candidates. + uint32_t patchEdge; + float patchVertexOrient; + }; + + struct CandidateIterator + { + CandidateIterator(Array<Candidate> &candidates, uint32_t first) : m_candidates(candidates), m_current(first) {} + void advance() { if (m_current != UINT32_MAX) m_current = m_candidates[m_current].next; } + bool isDone() const { return m_current == UINT32_MAX; } + Candidate ¤t() { return m_candidates[m_current]; } + + private: + Array<Candidate> &m_candidates; + uint32_t m_current; + }; + + const Mesh *m_mesh; + uint32_t m_faceCount; + Array<Vector2> m_texcoords; + Array<Candidate> m_candidates; + BitArray m_faceInCandidates; + Array<uint32_t> m_patch; + BitArray m_faceAssigned; // Face is assigned to a previous chart or the current patch. + BitArray m_faceInPatch, m_vertexInPatch; + BitArray m_faceInvalid; // Face cannot be added to the patch - flipped, cost too high or causes boundary intersection. + UniformGrid2 m_boundaryGrid; + + // Find candidate faces on the patch front. + void findCandidates() + { + m_candidates.clear(); + m_faceInCandidates.zeroOutMemory(); + for (uint32_t i = 0; i < m_patch.size(); i++) { + for (Mesh::FaceEdgeIterator it(m_mesh, m_patch[i]); !it.isDone(); it.advance()) { + const uint32_t oface = it.oppositeFace(); + if (oface == UINT32_MAX || oface >= m_faceCount || m_faceAssigned.get(oface) || m_faceInCandidates.get(oface)) + continue; + // Found an active edge on the patch front. + // Find the free vertex (the vertex that isn't on the active edge). + // Compute the orientation of the other patch face vertex to the active edge. + uint32_t freeVertex = UINT32_MAX; + float orient = 0.0f; + for (uint32_t j = 0; j < 3; j++) { + const uint32_t vertex = m_mesh->vertexAt(oface * 3 + j); + if (vertex != it.vertex0() && vertex != it.vertex1()) { + freeVertex = vertex; + orient = orientToEdge(m_texcoords[it.vertex0()], m_texcoords[it.vertex1()], m_texcoords[m_mesh->vertexAt(m_patch[i] * 3 + j)]); + break; + } + } + XA_DEBUG_ASSERT(freeVertex != UINT32_MAX); + // If the free vertex is already in the patch, the face is enclosed by the patch. Add the face to the patch - don't need to assign texcoords. + if (m_vertexInPatch.get(freeVertex)) { + freeVertex = UINT32_MAX; + m_patch.push_back(oface); + m_faceAssigned.set(oface); + continue; + } + // Check this here rather than above so faces enclosed by the patch are always added. + if (m_faceInvalid.get(oface)) + continue; + addCandidateFace(it.edge(), orient, oface, it.oppositeEdge(), freeVertex); + } + } + // Link candidates that share the same vertex. + for (uint32_t i = 0; i < m_candidates.size(); i++) { + if (m_candidates[i].next != UINT32_MAX) + continue; + uint32_t current = i; + for (uint32_t j = i + 1; j < m_candidates.size(); j++) { + if (m_candidates[j].vertex == m_candidates[current].vertex) { + m_candidates[current].next = j; + current = j; + } + } + } + // Set max cost for linked candidates. + for (uint32_t i = 0; i < m_candidates.size(); i++) { + float maxCost = 0.0f; + for (CandidateIterator it(m_candidates, i); !it.isDone(); it.advance()) + maxCost = max(maxCost, it.current().cost); + for (CandidateIterator it(m_candidates, i); !it.isDone(); it.advance()) + it.current().maxCost = maxCost; + } + } + + void addCandidateFace(uint32_t patchEdge, float patchVertexOrient, uint32_t face, uint32_t edge, uint32_t freeVertex) + { + Vector2 texcoords[3]; + orthoProjectFace(face, texcoords); + // Find corresponding vertices between the patch edge and candidate edge. + const uint32_t vertex0 = m_mesh->vertexAt(meshEdgeIndex0(patchEdge)); + const uint32_t vertex1 = m_mesh->vertexAt(meshEdgeIndex1(patchEdge)); + uint32_t localVertex0 = UINT32_MAX, localVertex1 = UINT32_MAX, localFreeVertex = UINT32_MAX; + for (uint32_t i = 0; i < 3; i++) { + const uint32_t vertex = m_mesh->vertexAt(face * 3 + i); + if (vertex == m_mesh->vertexAt(meshEdgeIndex1(edge))) + localVertex0 = i; + else if (vertex == m_mesh->vertexAt(meshEdgeIndex0(edge))) + localVertex1 = i; + else + localFreeVertex = i; + } + // Scale orthogonal projection to match the patch edge. + const Vector2 patchEdgeVec = m_texcoords[vertex1] - m_texcoords[vertex0]; + const Vector2 localEdgeVec = texcoords[localVertex1] - texcoords[localVertex0]; + const float len1 = length(patchEdgeVec); + const float len2 = length(localEdgeVec); + const float scale = len1 / len2; + XA_ASSERT(scale > 0.0f); + for (uint32_t i = 0; i < 3; i++) + texcoords[i] *= scale; + // Translate to the first vertex on the patch edge. + const Vector2 translate = m_texcoords[vertex0] - texcoords[localVertex0]; + for (uint32_t i = 0; i < 3; i++) + texcoords[i] += translate; + // Compute the angle between the patch edge and the corresponding local edge. + const float angle = atan2f(patchEdgeVec.y, patchEdgeVec.x) - atan2f(localEdgeVec.y, localEdgeVec.x); + // Rotate so the patch edge and the corresponding local edge occupy the same space. + for (uint32_t i = 0; i < 3; i++) { + if (i == localVertex0) + continue; + Vector2 &uv = texcoords[i]; + uv -= texcoords[localVertex0]; // Rotate around the first vertex. + const float c = cosf(angle); + const float s = sinf(angle); + const float x = uv.x * c - uv.y * s; + const float y = uv.y * c + uv.x * s; + uv.x = x + texcoords[localVertex0].x; + uv.y = y + texcoords[localVertex0].y; + } + // Check for local overlap (flipped triangle). + // The patch face vertex that isn't on the active edge and the free vertex should be oriented on opposite sides to the active edge. + const float freeVertexOrient = orientToEdge(m_texcoords[vertex0], m_texcoords[vertex1], texcoords[localFreeVertex]); + if ((patchVertexOrient < 0.0f && freeVertexOrient < 0.0f) || (patchVertexOrient > 0.0f && freeVertexOrient > 0.0f)) { + m_faceInvalid.set(face); + return; + } + const float stretch = computeStretch(m_mesh->position(vertex0), m_mesh->position(vertex1), m_mesh->position(freeVertex), texcoords[0], texcoords[1], texcoords[2]); + if (stretch >= FLT_MAX) { + m_faceInvalid.set(face); + return; + } + const float cost = fabsf(stretch - 1.0f); +#if 0 + if (cost > 0.25f) { + m_faceInvalid.set(face); + return; + } +#endif + // Add the candidate. + Candidate candidate; + candidate.face = face; + candidate.vertex = freeVertex; + candidate.position = texcoords[localFreeVertex]; + candidate.next = UINT32_MAX; + candidate.cost = cost; + candidate.patchEdge = patchEdge; + candidate.patchVertexOrient = patchVertexOrient; + m_candidates.push_back(candidate); + m_faceInCandidates.set(face); + } + + void orthoProjectFace(uint32_t face, Vector2 *texcoords) const + { + const Vector3 normal = m_mesh->computeFaceNormal(face); + const Vector3 tangent = normalize(m_mesh->position(m_mesh->vertexAt(face * 3 + 1)) - m_mesh->position(m_mesh->vertexAt(face * 3 + 0)), kEpsilon); + const Vector3 bitangent = cross(normal, tangent); + for (uint32_t i = 0; i < 3; i++) { + const Vector3 &pos = m_mesh->position(m_mesh->vertexAt(face * 3 + i)); + texcoords[i] = Vector2(dot(tangent, pos), dot(bitangent, pos)); + } + } + + float parametricArea(const Vector2 *texcoords) const + { + const Vector2 &v1 = texcoords[0]; + const Vector2 &v2 = texcoords[1]; + const Vector2 &v3 = texcoords[2]; + return ((v2.x - v1.x) * (v3.y - v1.y) - (v3.x - v1.x) * (v2.y - v1.y)) * 0.5f; + } + + float computeStretch(Vector3 p1, Vector3 p2, Vector3 p3, Vector2 t1, Vector2 t2, Vector2 t3) const + { + float parametricArea = ((t2.y - t1.y) * (t3.x - t1.x) - (t3.y - t1.y) * (t2.x - t1.x)) * 0.5f; + if (isZero(parametricArea, kAreaEpsilon)) + return FLT_MAX; + if (parametricArea < 0.0f) + parametricArea = fabsf(parametricArea); + const float geometricArea = length(cross(p2 - p1, p3 - p1)) * 0.5f; + if (parametricArea <= geometricArea) + return parametricArea / geometricArea; + else + return geometricArea / parametricArea; + } + + // Return value is positive if the point is one side of the edge, negative if on the other side. + float orientToEdge(Vector2 edgeVertex0, Vector2 edgeVertex1, Vector2 point) const + { + return (edgeVertex0.x - point.x) * (edgeVertex1.y - point.y) - (edgeVertex0.y - point.y) * (edgeVertex1.x - point.x); + } +}; +#endif // Estimate quality of existing parameterization. -struct ParameterizationQuality +struct Quality { + // computeBoundaryIntersection + bool boundaryIntersection = false; + + // computeFlippedFaces uint32_t totalTriangleCount = 0; uint32_t flippedTriangleCount = 0; uint32_t zeroAreaTriangleCount = 0; - float parametricArea = 0.0f; - float geometricArea = 0.0f; + + // computeMetrics + float totalParametricArea = 0.0f; + float totalGeometricArea = 0.0f; float stretchMetric = 0.0f; float maxStretchMetric = 0.0f; float conformalMetric = 0.0f; float authalicMetric = 0.0f; - bool boundaryIntersection = false; -}; -static ParameterizationQuality calculateParameterizationQuality(const Mesh *mesh, uint32_t faceCount, Array<uint32_t> *flippedFaces) -{ - XA_DEBUG_ASSERT(mesh != nullptr); - ParameterizationQuality quality; - uint32_t firstBoundaryEdge = UINT32_MAX; - for (uint32_t e = 0; e < mesh->edgeCount(); e++) { - if (mesh->isBoundaryEdge(e)) { - firstBoundaryEdge = e; - break; - } + void computeBoundaryIntersection(const Mesh *mesh, UniformGrid2 &boundaryGrid) + { + const Array<uint32_t> &boundaryEdges = mesh->boundaryEdges(); + const uint32_t boundaryEdgeCount = boundaryEdges.size(); + boundaryGrid.reset(mesh->texcoords(), mesh->indices(), boundaryEdgeCount); + for (uint32_t i = 0; i < boundaryEdgeCount; i++) + boundaryGrid.append(boundaryEdges[i]); + boundaryIntersection = boundaryGrid.intersectSelf(mesh->epsilon()); +#if XA_DEBUG_EXPORT_BOUNDARY_GRID + static int exportIndex = 0; + char filename[256]; + XA_SPRINTF(filename, sizeof(filename), "debug_boundary_grid_%03d.tga", exportIndex); + boundaryGrid.debugExport(filename); + exportIndex++; +#endif } - 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) + + void computeFlippedFaces(const Mesh *mesh, uint32_t faceCount, Array<uint32_t> *flippedFaces) + { + totalTriangleCount = flippedTriangleCount = zeroAreaTriangleCount = 0; + if (flippedFaces) + flippedFaces->clear(); + for (uint32_t f = 0; f < faceCount; f++) { + Vector2 texcoord[3]; + for (int i = 0; i < 3; i++) { + const uint32_t v = mesh->vertexAt(f * 3 + i); + texcoord[i] = mesh->texcoord(v); + } + totalTriangleCount++; + 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; + const float parametricArea = ((s2 - s1) * (t3 - t1) - (s3 - s1) * (t2 - t1)) * 0.5f; + if (isZero(parametricArea, kAreaEpsilon)) { + zeroAreaTriangleCount++; 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 (parametricArea < 0.0f) { + // Count flipped triangles. + flippedTriangleCount++; + if (flippedFaces) + flippedFaces->push_back(f); } } - 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 (flippedTriangleCount + zeroAreaTriangleCount == totalTriangleCount) { + // If all triangles are flipped, then none are. if (flippedFaces) - flippedFaces->push_back(f); - parametricArea = fabsf(parametricArea); + flippedFaces->clear(); + flippedTriangleCount = 0; } - 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->copyTo(temp); - 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 (flippedTriangleCount > totalTriangleCount / 2) + { + // If more than half the triangles are flipped, reverse the flipped / not flipped classification. + flippedTriangleCount = totalTriangleCount - flippedTriangleCount; + if (flippedFaces) { + Array<uint32_t> temp; + flippedFaces->copyTo(temp); + 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); } - 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); + + void computeMetrics(const Mesh *mesh, uint32_t faceCount) + { + totalGeometricArea = totalParametricArea = 0.0f; + stretchMetric = maxStretchMetric = conformalMetric = authalicMetric = 0.0f; + 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); + } + // 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)) * 0.5f; + if (isZero(parametricArea, kAreaEpsilon)) + continue; + if (parametricArea < 0.0f) + 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); + stretchMetric += square(rmsStretch) * geometricArea; + maxStretchMetric = max(maxStretchMetric, sigma2); + if (!isZero(sigma1, 0.000001f)) { + // sigma1 is zero when geometricArea is zero. + conformalMetric += (sigma2 / sigma1) * geometricArea; + } + authalicMetric += (sigma1 * sigma2) * geometricArea; + // Accumulate total areas. + totalGeometricArea += geometricArea; + totalParametricArea += parametricArea; + } + XA_DEBUG_ASSERT(isFinite(totalParametricArea) && totalParametricArea >= 0); + XA_DEBUG_ASSERT(isFinite(totalGeometricArea) && totalGeometricArea >= 0); + XA_DEBUG_ASSERT(isFinite(stretchMetric)); + XA_DEBUG_ASSERT(isFinite(maxStretchMetric)); + XA_DEBUG_ASSERT(isFinite(conformalMetric)); + XA_DEBUG_ASSERT(isFinite(authalicMetric)); + if (totalGeometricArea > 0.0f) { + const float normFactor = sqrtf(totalParametricArea / totalGeometricArea); + stretchMetric = sqrtf(stretchMetric / totalGeometricArea) * normFactor; + maxStretchMetric *= normFactor; + conformalMetric = sqrtf(conformalMetric / totalGeometricArea); + authalicMetric = sqrtf(authalicMetric / totalGeometricArea); + } } - return quality; -} +}; struct ChartWarningFlags { @@ -5706,24 +6629,30 @@ struct ChartWarningFlags }; }; +struct ChartCtorBuffers +{ + Array<uint32_t> chartMeshIndices; + Array<uint32_t> unifiedMeshIndices; + Array<uint32_t> boundaryLoops; +}; + /// A chart is a connected set of faces with a certain topology (usually a disk). class Chart { public: - Chart(const segment::Atlas *atlas, const Mesh *originalMesh, uint32_t chartIndex, uint32_t meshId, uint32_t chartGroupId, uint32_t chartId) : m_mesh(nullptr), m_unifiedMesh(nullptr), m_isDisk(false), m_isOrtho(false), m_isPlanar(false), m_warningFlags(0), m_closedHolesCount(0), m_fixedTJunctionsCount(0) + Chart(ChartCtorBuffers &buffers, const Basis &basis, ConstArrayView<uint32_t> faces, const Mesh *originalMesh, uint32_t meshId, uint32_t chartGroupId, uint32_t chartId) : m_basis(basis), m_mesh(nullptr), m_unifiedMesh(nullptr), m_unmodifiedUnifiedMesh(nullptr), m_type(ChartType::LSCM), m_warningFlags(0), m_closedHolesCount(0), m_fixedTJunctionsCount(0) { XA_UNUSED(meshId); XA_UNUSED(chartGroupId); XA_UNUSED(chartId); - m_basis = atlas->chartBasis(chartIndex); - atlas->chartFaces(chartIndex).copyTo(m_faceArray); + m_faceArray.copyFrom(faces.data, faces.length); // Copy face indices. m_mesh = XA_NEW_ARGS(MemTag::Mesh, Mesh, originalMesh->epsilon(), m_faceArray.size() * 3, m_faceArray.size()); m_unifiedMesh = XA_NEW_ARGS(MemTag::Mesh, Mesh, originalMesh->epsilon(), m_faceArray.size() * 3, m_faceArray.size()); - Array<uint32_t> chartMeshIndices; + Array<uint32_t> &chartMeshIndices = buffers.chartMeshIndices; chartMeshIndices.resize(originalMesh->vertexCount()); chartMeshIndices.setAll(UINT32_MAX); - Array<uint32_t> unifiedMeshIndices; + Array<uint32_t> &unifiedMeshIndices = buffers.unifiedMeshIndices; unifiedMeshIndices.resize(originalMesh->vertexCount()); unifiedMeshIndices.setAll(UINT32_MAX); // Add vertices. @@ -5735,11 +6664,7 @@ public: if (unifiedMeshIndices[unifiedVertex] == (uint32_t)~0) { unifiedMeshIndices[unifiedVertex] = m_unifiedMesh->vertexCount(); XA_DEBUG_ASSERT(equal(originalMesh->position(vertex), originalMesh->position(unifiedVertex), originalMesh->epsilon())); -#if XA_SKIP_PARAMETERIZATION - m_unifiedMesh->addVertex(originalMesh->position(vertex), Vector3(0.0f), atlas->faceTexcoords(m_faceArray[f])[i]); -#else m_unifiedMesh->addVertex(originalMesh->position(vertex)); -#endif } if (chartMeshIndices[vertex] == (uint32_t)~0) { chartMeshIndices[vertex] = m_mesh->vertexCount(); @@ -5774,11 +6699,10 @@ public: } 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 (meshIsPlanar(*m_unifiedMesh)) + m_type = ChartType::Planar; + else { + m_unifiedMesh->linkBoundaries(); #if XA_DEBUG_EXPORT_OBJ_BEFORE_FIX_TJUNCTION m_unifiedMesh->writeObjFile("debug_before_fix_tjunction.obj"); #endif @@ -5791,15 +6715,14 @@ public: m_warningFlags |= ChartWarningFlags::FixTJunctionsDuplicatedEdge; if (failed) m_warningFlags |= ChartWarningFlags::FixTJunctionsFailed; - m_unifiedMesh->~Mesh(); - XA_FREE(m_unifiedMesh); + m_unmodifiedUnifiedMesh = m_unifiedMesh; m_unifiedMesh = fixedUnifiedMesh; m_unifiedMesh->createBoundaries(); m_unifiedMesh->linkBoundaries(); m_initialFaceCount = m_unifiedMesh->faceCount(); // Fixing t-junctions rewrites faces. } // See if there are any holes that need closing. - Array<uint32_t> boundaryLoops; + Array<uint32_t> &boundaryLoops = buffers.boundaryLoops; meshGetBoundaryLoops(*m_unifiedMesh, boundaryLoops); if (boundaryLoops.size() > 1) { #if XA_DEBUG_EXPORT_OBJ_CLOSE_HOLES_ERROR @@ -5810,16 +6733,21 @@ public: // - 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, m_basis.normal, holeFaceCounts); + uint32_t holeCount = 0; +#if XA_DEBUG_EXPORT_OBJ_CLOSE_HOLES_ERROR + Array<uint32_t> holeFaceCounts; + failed = !meshCloseHoles(m_unifiedMesh, boundaryLoops, m_basis.normal, &holeFaceCounts); +#else + failed = !meshCloseHoles(m_unifiedMesh, boundaryLoops, m_basis.normal, &holeCount, nullptr); +#endif XA_PROFILE_END(closeChartMeshHoles) m_unifiedMesh->createBoundaries(); m_unifiedMesh->linkBoundaries(); meshGetBoundaryLoops(*m_unifiedMesh, boundaryLoops); if (failed || boundaryLoops.size() > 1) m_warningFlags |= ChartWarningFlags::CloseHolesFailed; - m_closedHolesCount = holeFaceCounts.size(); + m_closedHolesCount = holeCount; #if XA_DEBUG_EXPORT_OBJ_CLOSE_HOLES_ERROR if (m_warningFlags & ChartWarningFlags::CloseHolesFailed) { char filename[256]; @@ -5848,18 +6776,75 @@ public: } #endif } - // 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); + } + } + +#if XA_RECOMPUTE_CHARTS + Chart(ChartCtorBuffers &buffers, const Chart *parent, const Mesh *parentMesh, ConstArrayView<uint32_t> faces, const Vector2 *texcoords, const Mesh *originalMesh, uint32_t meshId, uint32_t chartGroupId, uint32_t chartId) : m_mesh(nullptr), m_unifiedMesh(nullptr), m_unmodifiedUnifiedMesh(nullptr), m_type(ChartType::Piecewise), m_warningFlags(0), m_closedHolesCount(0), m_fixedTJunctionsCount(0) + { + XA_UNUSED(meshId); + XA_UNUSED(chartGroupId); + XA_UNUSED(chartId); + const uint32_t faceCount = m_initialFaceCount = faces.length; + m_faceArray.resize(faceCount); + for (uint32_t i = 0; i < faceCount; i++) + m_faceArray[i] = parent->m_faceArray[faces[i]]; // Map faces to parent chart original mesh. + // Copy face indices. + m_mesh = XA_NEW_ARGS(MemTag::Mesh, Mesh, originalMesh->epsilon(), m_faceArray.size() * 3, m_faceArray.size()); + m_unifiedMesh = XA_NEW_ARGS(MemTag::Mesh, Mesh, originalMesh->epsilon(), m_faceArray.size() * 3, m_faceArray.size()); + Array<uint32_t> &chartMeshIndices = buffers.chartMeshIndices; + chartMeshIndices.resize(originalMesh->vertexCount()); + chartMeshIndices.setAll(UINT32_MAX); + Array<uint32_t> &unifiedMeshIndices = buffers.unifiedMeshIndices; + unifiedMeshIndices.resize(originalMesh->vertexCount()); + unifiedMeshIndices.setAll(UINT32_MAX); + // Add vertices. + for (uint32_t f = 0; f < faceCount; f++) { + for (uint32_t i = 0; i < 3; i++) { + const uint32_t vertex = originalMesh->vertexAt(m_faceArray[f] * 3 + i); + const uint32_t unifiedVertex = originalMesh->firstColocal(vertex); + const uint32_t parentVertex = parentMesh->vertexAt(faces[f] * 3 + i); + 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), Vector3(0.0f), texcoords[parentVertex]); + } + 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), texcoords[parentVertex]); + } + } + } + // Add faces. + for (uint32_t f = 0; f < faceCount; f++) { + uint32_t indices[3], unifiedIndices[3]; + for (uint32_t i = 0; i < 3; i++) { + const uint32_t vertex = originalMesh->vertexAt(m_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); } #endif + 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(); } +#endif ~Chart() { @@ -5871,16 +6856,19 @@ public: m_unifiedMesh->~Mesh(); XA_FREE(m_unifiedMesh); } + if (m_unmodifiedUnifiedMesh) { + m_unmodifiedUnifiedMesh->~Mesh(); + XA_FREE(m_unmodifiedUnifiedMesh); + } } 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; } + ChartType::Enum type() const { return m_type; } uint32_t warningFlags() const { return m_warningFlags; } uint32_t closedHolesCount() const { return m_closedHolesCount; } uint32_t fixedTJunctionsCount() const { return m_fixedTJunctionsCount; } - const ParameterizationQuality ¶mQuality() const { return m_paramQuality; } + const Quality &quality() const { return m_quality; } + uint32_t initialFaceCount() const { return m_initialFaceCount; } #if XA_DEBUG_EXPORT_OBJ_INVALID_PARAMETERIZATION const Array<uint32_t> ¶mFlippedFaces() const { return m_paramFlippedFaces; } #endif @@ -5889,26 +6877,31 @@ public: Mesh *mesh() { return m_mesh; } const Mesh *unifiedMesh() const { return m_unifiedMesh; } Mesh *unifiedMesh() { return m_unifiedMesh; } + const Mesh *unmodifiedUnifiedMesh() const { return m_unmodifiedUnifiedMesh; } uint32_t mapChartVertexToOriginalVertex(uint32_t i) const { return m_chartToOriginalMap[i]; } - void evaluateOrthoParameterizationQuality() + void evaluateOrthoQuality(UniformGrid2 &boundaryGrid) { XA_PROFILE_START(parameterizeChartsEvaluateQuality) - m_paramQuality = calculateParameterizationQuality(m_unifiedMesh, m_initialFaceCount, nullptr); + m_quality.computeBoundaryIntersection(m_unifiedMesh, boundaryGrid); + m_quality.computeFlippedFaces(m_unifiedMesh, m_initialFaceCount, nullptr); + m_quality.computeMetrics(m_unifiedMesh, m_initialFaceCount); XA_PROFILE_END(parameterizeChartsEvaluateQuality) // Use orthogonal parameterization if quality is acceptable. - if (!m_paramQuality.boundaryIntersection && m_paramQuality.geometricArea > 0.0f && m_paramQuality.stretchMetric <= 1.1f && m_paramQuality.maxStretchMetric <= 1.25f) - m_isOrtho = true; + if (!m_quality.boundaryIntersection && m_quality.totalGeometricArea > 0.0f && m_quality.stretchMetric <= 1.1f && m_quality.maxStretchMetric <= 1.25f) + m_type = ChartType::Ortho; } - void evaluateParameterizationQuality() + void evaluateQuality(UniformGrid2 &boundaryGrid) { XA_PROFILE_START(parameterizeChartsEvaluateQuality) + m_quality.computeBoundaryIntersection(m_unifiedMesh, boundaryGrid); #if XA_DEBUG_EXPORT_OBJ_INVALID_PARAMETERIZATION - m_paramQuality = calculateParameterizationQuality(m_unifiedMesh, m_initialFaceCount, &m_paramFlippedFaces); + m_quality.computeFlippedFaces(m_unifiedMesh, m_initialFaceCount, &m_paramFlippedFaces); #else - m_paramQuality = calculateParameterizationQuality(m_unifiedMesh, m_initialFaceCount, nullptr); + m_quality.computeFlippedFaces(m_unifiedMesh, m_initialFaceCount, nullptr); #endif + // Don't need to call computeMetrics here, that's only used in evaluateOrthoQuality to determine if quality is acceptable enough to use ortho projection. XA_PROFILE_END(parameterizeChartsEvaluateQuality) } @@ -5920,16 +6913,6 @@ public: m_mesh->texcoord(v) = m_unifiedMesh->texcoord(m_chartToUnifiedMap[v]); } - float computeSurfaceArea() const - { - return m_mesh->computeSurfaceArea(); - } - - float computeParametricArea() const - { - return m_mesh->computeParametricArea(); - } - Vector2 computeParametricBounds() const { Vector2 minCorner(FLT_MAX, FLT_MAX); @@ -5946,7 +6929,8 @@ private: Basis m_basis; Mesh *m_mesh; Mesh *m_unifiedMesh; - bool m_isDisk, m_isOrtho, m_isPlanar; + Mesh *m_unmodifiedUnifiedMesh; // Unified mesh before fixing t-junctions. Null if no t-junctions were fixed + ChartType::Enum m_type; uint32_t m_warningFlags; uint32_t m_initialFaceCount; // Before fixing T-junctions and/or closing holes. uint32_t m_closedHolesCount, m_fixedTJunctionsCount; @@ -5959,7 +6943,7 @@ private: Array<uint32_t> m_chartToUnifiedMap; - ParameterizationQuality m_paramQuality; + Quality m_quality; #if XA_DEBUG_EXPORT_OBJ_INVALID_PARAMETERIZATION Array<uint32_t> m_paramFlippedFaces; #endif @@ -5967,12 +6951,13 @@ private: struct CreateChartTaskArgs { - const segment::Atlas *atlas; const Mesh *mesh; - uint32_t chartIndex; // In the atlas. + const Basis *basis; + ConstArrayView<uint32_t> faces; uint32_t meshId; uint32_t chartGroupId; uint32_t chartId; + ThreadLocal<ChartCtorBuffers> *chartBuffers; Chart **chart; }; @@ -5980,7 +6965,7 @@ static void runCreateChartTask(void *userData) { XA_PROFILE_START(createChartMeshesThread) auto args = (CreateChartTaskArgs *)userData; - *(args->chart) = XA_NEW_ARGS(MemTag::Default, Chart, args->atlas, args->mesh, args->chartIndex, args->meshId, args->chartGroupId, args->chartId); + *(args->chart) = XA_NEW_ARGS(MemTag::Default, Chart, args->chartBuffers->get(), *(args->basis), args->faces, args->mesh, args->meshId, args->chartGroupId, args->chartId); XA_PROFILE_END(createChartMeshesThread) } @@ -5988,6 +6973,7 @@ struct ParameterizeChartTaskArgs { Chart *chart; ParameterizeFunc func; + ThreadLocal<UniformGrid2> *boundaryGrid; }; static void runParameterizeChartTask(void *userData) @@ -5995,24 +6981,26 @@ static void runParameterizeChartTask(void *userData) auto args = (ParameterizeChartTaskArgs *)userData; Mesh *mesh = args->chart->unifiedMesh(); XA_PROFILE_START(parameterizeChartsOrthogonal) -#if 1 - computeOrthogonalProjectionMap(mesh); -#else - for (uint32_t i = 0; i < vertexCount; i++) - mesh->texcoord(i) = Vector2(dot(args->chart->basis().tangent, mesh->position(i)), dot(args->chart->basis().bitangent, mesh->position(i))); -#endif + { + // Project vertices to plane. + const uint32_t vertexCount = mesh->vertexCount(); + const Basis &basis = args->chart->basis(); + for (uint32_t i = 0; i < vertexCount; i++) + mesh->texcoord(i) = Vector2(dot(basis.tangent, mesh->position(i)), dot(basis.bitangent, mesh->position(i))); + } XA_PROFILE_END(parameterizeChartsOrthogonal) - args->chart->evaluateOrthoParameterizationQuality(); - if (!args->chart->isOrtho() && !args->chart->isPlanar()) { + // Computing charts checks for flipped triangles and boundary intersection. Don't need to do that again here if chart is planar. + if (args->chart->type() != ChartType::Planar) + args->chart->evaluateOrthoQuality(args->boundaryGrid->get()); + if (args->chart->type() == ChartType::LSCM) { XA_PROFILE_START(parameterizeChartsLSCM) if (args->func) args->func(&mesh->position(0).x, &mesh->texcoord(0).x, mesh->vertexCount(), mesh->indices(), mesh->indexCount()); - else if (args->chart->isDisk()) + else computeLeastSquaresConformalMap(mesh); XA_PROFILE_END(parameterizeChartsLSCM) - args->chart->evaluateParameterizationQuality(); + args->chart->evaluateQuality(args->boundaryGrid->get()); } - // @@ Check that parameterization quality is above a certain threshold. // Transfer parameterization from unified mesh to chart mesh. args->chart->transferParameterization(); } @@ -6021,27 +7009,33 @@ static void runParameterizeChartTask(void *userData) class ChartGroup { public: - 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) + ChartGroup(uint32_t id, const Mesh *sourceMesh, uint16_t faceGroup) : m_sourceId(sourceMesh->id()), m_id(id), m_isVertexMap(faceGroup == Mesh::kInvalidFaceGroup), 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); + if (!m_isVertexMap) { + m_faceToSourceFaceMap.reserve(sourceMesh->faceGroupFaceCount(faceGroup)); + for (Mesh::GroupFaceIterator it(sourceMesh, faceGroup); !it.isDone(); it.advance()) + m_faceToSourceFaceMap.push_back(it.face()); + } else { + 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_ARGS(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()); - meshIndices.setAll((uint32_t)~0); + const uint32_t approxVertexCount = faceCount * 3; + m_mesh = XA_NEW_ARGS(MemTag::Mesh, Mesh, sourceMesh->epsilon(), approxVertexCount, faceCount, sourceMesh->flags() & MeshFlags::HasNormals); + m_vertexToSourceVertexMap.reserve(approxVertexCount); + HashMap<uint32_t> sourceVertexToVertexMap(MemTag::Mesh, approxVertexCount); for (uint32_t f = 0; f < faceCount; f++) { 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(); + if (sourceVertexToVertexMap.get(vertex) == UINT32_MAX) { + sourceVertexToVertexMap.add(vertex); m_vertexToSourceVertexMap.push_back(vertex); Vector3 normal(0.0f); if (sourceMesh->flags() & MeshFlags::HasNormals) @@ -6056,8 +7050,8 @@ public: 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]; + indices[i] = sourceVertexToVertexMap.get(vertex); + XA_DEBUG_ASSERT(indices[i] != UINT32_MAX); } // 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. @@ -6068,7 +7062,6 @@ public: if (!m_isVertexMap) { m_mesh->createColocals(); m_mesh->createBoundaries(); - m_mesh->linkBoundaries(); } #if XA_DEBUG_EXPORT_OBJ_CHART_GROUPS char filename[256]; @@ -6083,14 +7076,14 @@ public: { 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]); + for (uint32_t i = 0; i < m_charts.size(); i++) { + m_charts[i]->~Chart(); + XA_FREE(m_charts[i]); } } - uint32_t chartCount() const { return m_chartArray.size(); } - Chart *chartAt(uint32_t i) const { return m_chartArray[i]; } + uint32_t chartCount() const { return m_charts.size(); } + Chart *chartAt(uint32_t i) const { return m_charts[i]; } uint32_t paramAddedChartsCount() const { return m_paramAddedChartsCount; } uint32_t paramDeletedChartsCount() const { return m_paramDeletedChartsCount; } bool isVertexMap() const { return m_isVertexMap; } @@ -6158,40 +7151,41 @@ public: - emphasize roundness metrics to prevent those cases. - If interior self-overlaps: preserve boundary parameterization and use mean-value map. */ - void computeCharts(TaskScheduler *taskScheduler, const ChartOptions &options) + void computeCharts(TaskScheduler *taskScheduler, const ChartOptions &options, segment::Atlas &atlas, ThreadLocal<ChartCtorBuffers> *chartBuffers) { 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]); + for (uint32_t i = 0; i < m_charts.size(); i++) { + m_charts[i]->~Chart(); + XA_FREE(m_charts[i]); } - m_chartArray.clear(); + m_charts.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_ARGS(MemTag::Default, Chart, m_mesh, chartFaces, m_sourceId, m_id, 0); - m_chartArray.push_back(chart); + m_charts.push_back(chart); #else XA_PROFILE_START(buildAtlas) - segment::Atlas atlas(m_mesh, nullptr, options); + atlas.reset(m_sourceId, m_id, m_mesh, options); buildAtlas(atlas, options); XA_PROFILE_END(buildAtlas) const uint32_t chartCount = atlas.chartCount(); - m_chartArray.resize(chartCount); + m_charts.resize(chartCount); Array<CreateChartTaskArgs> taskArgs; taskArgs.resize(chartCount); for (uint32_t i = 0; i < chartCount; i++) { CreateChartTaskArgs &args = taskArgs[i]; - args.atlas = &atlas; + args.basis = &atlas.chartBasis(i); + args.faces = atlas.chartFaces(i); args.mesh = m_mesh; - args.chartIndex = i; args.meshId = m_sourceId; args.chartGroupId = m_id; args.chartId = i; - args.chart = &m_chartArray[i]; + args.chartBuffers = chartBuffers; + args.chart = &m_charts[i]; } XA_PROFILE_START(createChartMeshesReal) TaskGroupHandle taskGroup = taskScheduler->createTaskGroup(chartCount); @@ -6225,26 +7219,22 @@ public: #endif } - void parameterizeCharts(TaskScheduler *taskScheduler, ParameterizeFunc func) - { - const uint32_t chartCount = m_chartArray.size(); -#if XA_SKIP_PARAMETERIZATION - XA_UNUSED(taskScheduler); - XA_UNUSED(func); - for (uint32_t i = 0; i < chartCount; i++) { - Chart *chart = m_chartArray[i]; - chart->evaluateOrthoParameterizationQuality(); - chart->evaluateParameterizationQuality(); - chart->transferParameterization(); - } +#if XA_RECOMPUTE_CHARTS + void parameterizeCharts(TaskScheduler *taskScheduler, ParameterizeFunc func, ThreadLocal<UniformGrid2> *boundaryGrid, ThreadLocal<ChartCtorBuffers> *chartBuffers, ThreadLocal<PiecewiseParam> *piecewiseParam) #else + void parameterizeCharts(TaskScheduler* taskScheduler, ParameterizeFunc func, ThreadLocal<UniformGrid2>* boundaryGrid, ThreadLocal<ChartCtorBuffers>* /*chartBuffers*/) +#endif + { + m_paramAddedChartsCount = 0; + const uint32_t chartCount = m_charts.size(); Array<ParameterizeChartTaskArgs> taskArgs; taskArgs.resize(chartCount); TaskGroupHandle taskGroup = taskScheduler->createTaskGroup(chartCount); for (uint32_t i = 0; i < chartCount; i++) { ParameterizeChartTaskArgs &args = taskArgs[i]; - args.chart = m_chartArray[i]; + args.chart = m_charts[i]; args.func = func; + args.boundaryGrid = boundaryGrid; Task task; task.userData = &args; task.func = runParameterizeChartTask; @@ -6255,67 +7245,65 @@ public: // Find charts with invalid parameterizations. Array<Chart *> invalidCharts; for (uint32_t i = 0; i < chartCount; i++) { - Chart *chart = m_chartArray[i]; - const ParameterizationQuality &quality = chart->paramQuality(); + Chart *chart = m_charts[i]; + const Quality &quality = chart->quality(); if (quality.boundaryIntersection || quality.flippedTriangleCount > 0) invalidCharts.push_back(chart); } if (invalidCharts.isEmpty()) return; // Recompute charts with invalid parameterizations. - Array<uint32_t> meshFaces; + PiecewiseParam &pp = piecewiseParam->get(); 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; - segment::Atlas atlas(m_mesh, &meshFaces, options); - buildAtlas(atlas, options); - for (uint32_t j = 0; j < atlas.chartCount(); j++) { - Chart *chart = XA_NEW_ARGS(MemTag::Default, Chart, &atlas, m_mesh, j, m_sourceId, m_id, m_chartArray.size()); - m_chartArray.push_back(chart); - m_paramAddedChartsCount++; + // Fixing t-junctions rewrites unified mesh faces, and we need to map faces back to input mesh. So use the unmodified unified mesh. + const Mesh *invalidMesh = invalidChart->unmodifiedUnifiedMesh(); + uint32_t faceCount = 0; + if (invalidMesh) { + faceCount = invalidMesh->faceCount(); + } else { + invalidMesh = invalidChart->unifiedMesh(); + faceCount = invalidChart->initialFaceCount(); // Not invalidMesh->faceCount(). Don't want faces added by hole closing. } + pp.reset(invalidMesh, faceCount); #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); + XA_SPRINTF(filename, sizeof(filename), "debug_mesh_%03u_chartgroup_%03u_recomputed_chart_%03u.obj", m_sourceId, m_id, m_paramAddedChartsCount); 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); + uint32_t subChartIndex = 0; +#endif + for (;;) { + if (!pp.computeChart()) + break; + Chart *chart = XA_NEW_ARGS(MemTag::Default, Chart, chartBuffers->get(), invalidChart, invalidMesh, pp.chartFaces(), pp.texcoords(), m_mesh, m_sourceId, m_id, m_charts.size()); + m_charts.push_back(chart); +#if XA_DEBUG_EXPORT_OBJ_RECOMPUTED_CHARTS + if (file) { + for (uint32_t j = 0; j < invalidMesh->vertexCount(); j++) { + fprintf(file, "v %g %g %g\n", invalidMesh->position(j).x, invalidMesh->position(j).y, invalidMesh->position(j).z); + fprintf(file, "vt %g %g\n", pp.texcoords()[j].x, pp.texcoords()[j].y); + } + fprintf(file, "o chart%03u\n", subChartIndex); 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]); + for (uint32_t f = 0; f < pp.chartFaces().length; f++) { + fprintf(file, "f "); + const uint32_t face = pp.chartFaces()[f]; + for (uint32_t j = 0; j < 3; j++) { + const uint32_t index = invalidMesh->vertexCount() * subChartIndex + invalidMesh->vertexAt(face * 3 + j) + 1; // 1-indexed + fprintf(file, "%d/%d/%c", index, index, j == 2 ? '\n' : ' '); + } + } } - fclose(file); + subChartIndex++; +#endif + m_paramAddedChartsCount++; } +#if XA_DEBUG_EXPORT_OBJ_RECOMPUTED_CHARTS + if (file) + fclose(file); #endif } - // Parameterize the new charts. - taskGroup = taskScheduler->createTaskGroup(m_chartArray.size() - chartCount); - taskArgs.resize(m_chartArray.size() - chartCount); - for (uint32_t i = chartCount; i < m_chartArray.size(); i++) { - ParameterizeChartTaskArgs &args = taskArgs[i - chartCount]; - args.chart = m_chartArray[i]; - args.func = func; - Task task; - task.userData = &args; - task.func = runParameterizeChartTask; - taskScheduler->run(taskGroup, task); - } - taskScheduler->wait(&taskGroup); // Remove and delete the invalid charts. for (uint32_t i = 0; i < invalidCharts.size(); i++) { Chart *chart = invalidCharts[i]; @@ -6325,7 +7313,6 @@ public: m_paramDeletedChartsCount++; } #endif // XA_RECOMPUTE_CHARTS -#endif // XA_SKIP_PARAMETERIZATION } private: @@ -6343,19 +7330,18 @@ private: atlas.resetCharts(); // Restart process growing charts in parallel. uint32_t iteration = 0; - while (true) { - if (!atlas.growCharts(options.maxThreshold)) { - // If charts cannot grow more: fill holes, merge charts, relocate seeds and start new iteration. - atlas.fillHoles(options.maxThreshold * 0.5f); + for (;;) { + atlas.growCharts(options.maxThreshold); + // When charts cannot grow more: fill holes, merge charts, relocate seeds and start new iteration. + atlas.fillHoles(options.maxThreshold * 0.5f); #if XA_MERGE_CHARTS - atlas.mergeCharts(); + atlas.mergeCharts(); #endif - if (++iteration == options.maxIterations) - break; - if (!atlas.relocateSeeds()) - break; - atlas.resetCharts(); - } + if (++iteration == options.maxIterations) + break; + if (!atlas.relocateSeeds()) + break; + atlas.resetCharts(); } // Make sure no holes are left! XA_DEBUG_ASSERT(atlas.facesLeft() == 0); @@ -6363,9 +7349,9 @@ private: void removeChart(const Chart *chart) { - for (uint32_t i = 0; i < m_chartArray.size(); i++) { - if (m_chartArray[i] == chart) { - m_chartArray.removeAt(i); + for (uint32_t i = 0; i < m_charts.size(); i++) { + if (m_charts[i] == chart) { + m_charts.removeAt(i); return; } } @@ -6376,7 +7362,7 @@ private: 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; + Array<Chart *> m_charts; 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. @@ -6384,7 +7370,7 @@ private: struct CreateChartGroupTaskArgs { - uint32_t faceGroup; + uint16_t faceGroup; uint32_t groupId; const Mesh *mesh; ChartGroup **chartGroup; @@ -6402,6 +7388,8 @@ struct ComputeChartsTaskArgs { TaskScheduler *taskScheduler; ChartGroup *chartGroup; + ThreadLocal<segment::Atlas> *atlas; + ThreadLocal<ChartCtorBuffers> *chartBuffers; const ChartOptions *options; Progress *progress; }; @@ -6412,7 +7400,7 @@ static void runComputeChartsJob(void *userData) if (args->progress->cancel) return; XA_PROFILE_START(computeChartsThread) - args->chartGroup->computeCharts(args->taskScheduler, *args->options); + args->chartGroup->computeCharts(args->taskScheduler, *args->options, args->atlas->get(), args->chartBuffers); XA_PROFILE_END(computeChartsThread) args->progress->value++; args->progress->update(); @@ -6423,6 +7411,11 @@ struct ParameterizeChartsTaskArgs TaskScheduler *taskScheduler; ChartGroup *chartGroup; ParameterizeFunc func; + ThreadLocal<UniformGrid2> *boundaryGrid; + ThreadLocal<ChartCtorBuffers> *chartBuffers; +#if XA_RECOMPUTE_CHARTS + ThreadLocal<PiecewiseParam> *piecewiseParam; +#endif Progress *progress; }; @@ -6432,7 +7425,11 @@ static void runParameterizeChartsJob(void *userData) if (args->progress->cancel) return; XA_PROFILE_START(parameterizeChartsThread) - args->chartGroup->parameterizeCharts(args->taskScheduler, args->func); +#if XA_RECOMPUTE_CHARTS + args->chartGroup->parameterizeCharts(args->taskScheduler, args->func, args->boundaryGrid, args->chartBuffers, args->piecewiseParam); +#else + args->chartGroup->parameterizeCharts(args->taskScheduler, args->func, args->boundaryGrid, args->chartBuffers); +#endif XA_PROFILE_END(parameterizeChartsThread) args->progress->value++; args->progress->update(); @@ -6482,31 +7479,17 @@ public: // This function is thread safe. void addMesh(TaskScheduler *taskScheduler, const Mesh *mesh) { - // 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. + // If there's any ignored faces in the mesh, create an extra face group for that (vertex map). // Chart group creation is slow since it copies a chunk of the source mesh, so use tasks. Array<ChartGroup *> chartGroups; - chartGroups.resize(faceGroups.size()); + chartGroups.resize(mesh->faceGroupCount() + (mesh->ignoredFaceCount() > 0 ? 1 : 0)); 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.faceGroup = uint16_t(g < mesh->faceGroupCount() ? g : Mesh::kInvalidFaceGroup); args.groupId = g; args.mesh = mesh; } @@ -6561,6 +7544,8 @@ public: chartGroupCount++; } Progress progress(ProgressCategory::ComputeCharts, progressFunc, progressUserData, chartGroupCount); + ThreadLocal<segment::Atlas> atlas; + ThreadLocal<ChartCtorBuffers> chartBuffers; Array<ComputeChartsTaskArgs> taskArgs; taskArgs.reserve(chartGroupCount); for (uint32_t i = 0; i < m_chartGroups.size(); i++) { @@ -6568,6 +7553,8 @@ public: ComputeChartsTaskArgs args; args.taskScheduler = taskScheduler; args.chartGroup = m_chartGroups[i]; + args.atlas = &atlas; + args.chartBuffers = &chartBuffers; args.options = &options; args.progress = &progress; taskArgs.push_back(args); @@ -6605,6 +7592,11 @@ public: chartGroupCount++; } Progress progress(ProgressCategory::ParameterizeCharts, progressFunc, progressUserData, chartGroupCount); + ThreadLocal<UniformGrid2> boundaryGrid; // For Quality boundary intersection. + ThreadLocal<ChartCtorBuffers> chartBuffers; +#if XA_RECOMPUTE_CHARTS + ThreadLocal<PiecewiseParam> piecewiseParam; +#endif Array<ParameterizeChartsTaskArgs> taskArgs; taskArgs.reserve(chartGroupCount); for (uint32_t i = 0; i < m_chartGroups.size(); i++) { @@ -6613,6 +7605,11 @@ public: args.taskScheduler = taskScheduler; args.chartGroup = m_chartGroups[i]; args.func = func; + args.boundaryGrid = &boundaryGrid; + args.chartBuffers = &chartBuffers; +#if XA_RECOMPUTE_CHARTS + args.piecewiseParam = &piecewiseParam; +#endif args.progress = &progress; taskArgs.push_back(args); } @@ -6646,55 +7643,6 @@ private: 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: @@ -6728,13 +7676,13 @@ public: const int xx = x + offset_x; if (xx >= 0 && xx < atlas_w && yy < atlas_h) { const uint32_t dataOffset = xx + yy * m_width; - if (image->bitAt(x, y)) { + if (image->get(x, y)) { XA_DEBUG_ASSERT(m_data[dataOffset] == 0); m_data[dataOffset] = chartIndex | kImageHasChartIndexBit; - } else if (imageBilinear && imageBilinear->bitAt(x, y)) { + } else if (imageBilinear && imageBilinear->get(x, y)) { XA_DEBUG_ASSERT(m_data[dataOffset] == 0); m_data[dataOffset] = chartIndex | kImageHasChartIndexBit | kImageIsBilinearBit; - } else if (imagePadding && imagePadding->bitAt(x, y)) { + } else if (imagePadding && imagePadding->get(x, y)) { XA_DEBUG_ASSERT(m_data[dataOffset] == 0); m_data[dataOffset] = chartIndex | kImageHasChartIndexBit | kImageIsPaddingBit; } @@ -6807,6 +7755,8 @@ struct Chart bool allowRotate; // bounding box Vector2 majorAxis, minorAxis, minCorner, maxCorner; + // Mesh only + const Array<uint32_t> *boundaryEdges; // UvMeshChart only Array<uint32_t> faces; @@ -6816,6 +7766,7 @@ struct Chart struct AddChartTaskArgs { + ThreadLocal<BoundingBox2D> *boundingBox; param::Chart *paramChart; Chart *chart; // out }; @@ -6834,102 +7785,32 @@ static void runAddChartTask(void *userData) chart->material = 0; chart->indexCount = mesh->indexCount(); chart->indices = mesh->indices(); - chart->parametricArea = paramChart->computeParametricArea(); + chart->parametricArea = mesh->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->surfaceArea = mesh->computeSurfaceArea(); chart->vertices = mesh->texcoords(); chart->vertexCount = mesh->vertexCount(); chart->allowRotate = true; - // Compute list of boundary vertices. - Array<Vector2> boundary; - boundary.reserve(16); + chart->boundaryEdges = &mesh->boundaryEdges(); + // Compute bounding box of chart. + BoundingBox2D &bb = args->boundingBox->get(); + bb.clear(); for (uint32_t v = 0; v < chart->vertexCount; v++) { if (mesh->isBoundaryVertex(v)) - boundary.push_back(mesh->texcoord(v)); + bb.appendBoundaryVertex(mesh->texcoord(v)); } - XA_DEBUG_ASSERT(boundary.size() > 0); - // Compute bounding box of chart. - static thread_local BoundingBox2D boundingBox; - boundingBox.compute(boundary.data(), boundary.size(), mesh->texcoords(), mesh->vertexCount()); - chart->majorAxis = boundingBox.majorAxis(); - chart->minorAxis = boundingBox.minorAxis(); - chart->minCorner = boundingBox.minCorner(); - chart->maxCorner = boundingBox.maxCorner(); + bb.compute(mesh->texcoords(), mesh->vertexCount()); + chart->majorAxis = bb.majorAxis; + chart->minorAxis = bb.minorAxis; + chart->minCorner = bb.minCorner; + chart->maxCorner = bb.maxCorner; XA_PROFILE_END(packChartsAddChartsThread) } -struct FindChartLocationBruteForceTaskArgs -{ - std::atomic<bool> *finished; // One of the tasks found a location that doesn't expand the atlas. - Vector2i startPosition; - const BitImage *atlasBitImage; - const BitImage *chartBitImage; - const BitImage *chartBitImageRotated; - int w, h; - bool blockAligned, allowRotate; - uint32_t maxResolution; - // out - bool best_insideAtlas; - int best_metric, best_x, best_y, best_w, best_h, best_r; -}; - -static void runFindChartLocationBruteForceTask(void *userData) -{ - XA_PROFILE_START(packChartsFindLocationThread) - auto args = (FindChartLocationBruteForceTaskArgs *)userData; - args->best_metric = INT_MAX; - if (args->finished->load()) - return; - // Try two different orientations. - for (int r = 0; r < 2; r++) { - if (args->finished->load()) - break; - int cw = args->chartBitImage->width(); - int ch = args->chartBitImage->height(); - if (r == 1) { - if (args->allowRotate) - swap(cw, ch); - else - break; - } - const int y = args->startPosition.y; - const int stepSize = args->blockAligned ? 4 : 1; - for (int x = args->startPosition.x; x <= args->w + stepSize; x += stepSize) { - if (args->maxResolution > 0 && (x > (int)args->maxResolution - cw || y > (int)args->maxResolution - ch)) - continue; - if (args->finished->load()) - break; - // Early out if metric not better. - const int area = max(args->w, x + cw) * max(args->h, y + ch); - const int extents = max(max(args->w, x + cw), max(args->h, y + ch)); - const int metric = extents * extents + area; - if (metric > args->best_metric) - continue; - // If metric is the same, pick the one closest to the origin. - if (metric == args->best_metric && max(x, y) >= max(args->best_x, args->best_y)) - continue; - if (!args->atlasBitImage->canBlit(r == 1 ? *(args->chartBitImageRotated) : *(args->chartBitImage), x, y)) - continue; - args->best_metric = metric; - args->best_insideAtlas = area == args->w * args->h; - args->best_x = x; - args->best_y = y; - args->best_w = cw; - args->best_h = ch; - args->best_r = r; - if (args->best_insideAtlas) { - args->finished->store(true); - break; - } - } - } - XA_PROFILE_END(packChartsFindLocationThread) -} - struct Atlas { ~Atlas() @@ -6975,6 +7856,7 @@ struct Atlas taskArgs.resize(chartCount); TaskGroupHandle taskGroup = taskScheduler->createTaskGroup(chartCount); uint32_t chartIndex = 0; + ThreadLocal<BoundingBox2D> boundingBox; for (uint32_t i = 0; i < chartGroupsCount; i++) { const param::ChartGroup *chartGroup = paramAtlas->chartGroupAt(i); if (chartGroup->isVertexMap()) @@ -6982,6 +7864,7 @@ struct Atlas const uint32_t count = chartGroup->chartCount(); for (uint32_t j = 0; j < count; j++) { AddChartTaskArgs &args = taskArgs[chartIndex]; + args.boundingBox = &boundingBox; args.paramChart = chartGroup->chartAt(j); Task task; task.userData = &taskArgs[chartIndex]; @@ -7000,8 +7883,6 @@ struct Atlas void addUvMeshCharts(UvMeshInstance *mesh) { BitArray vertexUsed(mesh->texcoords.size()); - Array<Vector2> boundary; - boundary.reserve(16); BoundingBox2D boundingBox; for (uint32_t c = 0; c < mesh->mesh->charts.size(); c++) { UvMeshChart *uvChart = mesh->mesh->charts[c]; @@ -7013,14 +7894,15 @@ struct Atlas chart->vertices = mesh->texcoords.data(); chart->vertexCount = mesh->texcoords.size(); chart->allowRotate = mesh->rotateCharts; + chart->boundaryEdges = nullptr; chart->faces.resize(uvChart->faces.size()); memcpy(chart->faces.data(), uvChart->faces.data(), sizeof(uint32_t) * uvChart->faces.size()); // Find unique vertices. - vertexUsed.clearAll(); + vertexUsed.zeroOutMemory(); for (uint32_t i = 0; i < chart->indexCount; i++) { const uint32_t vertex = chart->indices[i]; - if (!vertexUsed.bitAt(vertex)) { - vertexUsed.setBitAt(vertex); + if (!vertexUsed.get(vertex)) { + vertexUsed.set(vertex); chart->uniqueVertices.push_back(vertex); } } @@ -7045,24 +7927,22 @@ struct Atlas const Vector2 bounds = (maxCorner - minCorner) * 0.5f; chart->parametricArea = bounds.x * bounds.y; } - // Compute list of boundary vertices. + // Compute bounding box of chart. // Using all unique vertices for simplicity, can compute real boundaries if this is too slow. - boundary.clear(); + boundingBox.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. - boundingBox.compute(boundary.data(), boundary.size(), boundary.data(), boundary.size()); - chart->majorAxis = boundingBox.majorAxis(); - chart->minorAxis = boundingBox.minorAxis(); - chart->minCorner = boundingBox.minCorner(); - chart->maxCorner = boundingBox.maxCorner(); + boundingBox.appendBoundaryVertex(chart->uniqueVertexAt(v)); + boundingBox.compute(); + chart->majorAxis = boundingBox.majorAxis; + chart->minorAxis = boundingBox.minorAxis; + chart->minCorner = boundingBox.minCorner; + chart->maxCorner = boundingBox.maxCorner; m_charts.push_back(chart); } } // Pack charts in the smallest possible rectangle. - bool packCharts(TaskScheduler *taskScheduler, const PackOptions &options, ProgressFunc progressFunc, void *progressUserData) + bool packCharts(const PackOptions &options, ProgressFunc progressFunc, void *progressUserData) { if (progressFunc) { if (!progressFunc(ProgressCategory::PackCharts, 0, progressUserData)) @@ -7107,10 +7987,11 @@ struct Atlas for (uint32_t c = 0; c < chartCount; c++) { Chart *chart = m_charts[c]; // Compute chart scale - float scale = (chart->surfaceArea / chart->parametricArea) * m_texelsPerUnit; - if (chart->parametricArea == 0.0f) - scale = 0; - XA_ASSERT(isFinite(scale)); + float scale = 1.0f; + if (chart->parametricArea != 0.0f) { + scale = (chart->surfaceArea / chart->parametricArea) * m_texelsPerUnit; + XA_ASSERT(isFinite(scale)); + } // Translate, rotate and scale vertices. Compute extents. Vector2 minCorner(FLT_MAX, FLT_MAX); if (!chart->allowRotate) { @@ -7181,6 +8062,8 @@ struct Atlas texcoord.y += 0.5f + options.padding; extents = max(extents, texcoord); } + if (extents.x > resolution || extents.y > resolution) + XA_PRINT(" Chart %u extents are large (%gx%g)\n", c, extents.x, extents.y); chartExtents[c] = extents; chartOrderArray[c] = extents.x + extents.y; // Use perimeter for chart sort key. minChartPerimeter = min(minChartPerimeter, chartOrderArray[c]); @@ -7207,6 +8090,7 @@ struct Atlas // Rotated versions swap x and y. BitImage chartImage, chartImageBilinear, chartImagePadding; BitImage chartImageRotated, chartImageBilinearRotated, chartImagePaddingRotated; + UniformGrid2 boundaryEdgeGrid; Array<Vector2i> atlasSizes; atlasSizes.push_back(Vector2i(0, 0)); int progress = 0; @@ -7249,7 +8133,7 @@ struct Atlas } // Expand chart by pixels sampled by bilinear interpolation. if (options.bilinear) - bilinearExpand(chart, &chartImage, &chartImageBilinear, chart->allowRotate ? &chartImageBilinearRotated : nullptr); + bilinearExpand(chart, &chartImage, &chartImageBilinear, chart->allowRotate ? &chartImageBilinearRotated : nullptr, boundaryEdgeGrid); // Expand chart by padding pixels (dilation). if (options.padding > 0) { // Copy into the same BitImage instances for every chart to avoid reallocating BitImage buffers (largest chart is packed first). @@ -7310,7 +8194,7 @@ struct Atlas chartStartPositions.push_back(Vector2i(0, 0)); } XA_PROFILE_START(packChartsFindLocation) - const bool foundLocation = findChartLocation(taskScheduler, chartStartPositions[currentAtlas], options.bruteForce, m_bitImages[currentAtlas], chartImageToPack, chartImageToPackRotated, atlasSizes[currentAtlas].x, atlasSizes[currentAtlas].y, &best_x, &best_y, &best_cw, &best_ch, &best_r, options.blockAlign, maxResolution, chart->allowRotate); + const bool foundLocation = findChartLocation(chartStartPositions[currentAtlas], options.bruteForce, m_bitImages[currentAtlas], chartImageToPack, chartImageToPackRotated, atlasSizes[currentAtlas].x, atlasSizes[currentAtlas].y, &best_x, &best_y, &best_cw, &best_ch, &best_r, options.blockAlign, maxResolution, chart->allowRotate); XA_PROFILE_END(packChartsFindLocation) XA_DEBUG_ASSERT(!(firstChartInBitImage && !foundLocation)); // Chart doesn't fit in an empty, newly allocated bitImage. Shouldn't happen, since charts are resized if they are too big to fit in the atlas. if (maxResolution == 0) { @@ -7359,6 +8243,13 @@ struct Atlas } else { m_atlasImages[currentAtlas]->addChart(c, &chartImageRotated, options.bilinear ? &chartImageBilinearRotated : nullptr, options.padding > 0 ? &chartImagePaddingRotated : nullptr, atlasSizes[currentAtlas].x, atlasSizes[currentAtlas].y, best_x, best_y); } +#if XA_DEBUG_EXPORT_ATLAS_IMAGES && XA_DEBUG_EXPORT_ATLAS_IMAGES_PER_CHART + for (uint32_t j = 0; j < m_atlasImages.size(); j++) { + char filename[256]; + XA_SPRINTF(filename, sizeof(filename), "debug_atlas_image%02u_chart%04u.tga", j, i); + m_atlasImages[j]->writeTga(filename, (uint32_t)atlasSizes[j].x, (uint32_t)atlasSizes[j].y); + } +#endif } chart->atlasIndex = (int32_t)currentAtlas; // Modify texture coordinates: @@ -7415,7 +8306,7 @@ struct Atlas 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); + count += m_bitImages[i]->get(x, y); } m_utilization[i] = float(count) / (m_width * m_height); } @@ -7445,70 +8336,56 @@ private: // is occupied at this point. At the end we have many small charts and a large atlas with sparse holes. Finding those holes randomly is slow. A better approach would be to // start stacking large charts as if they were tetris pieces. Once charts get small try to place them randomly. It may be interesting to try a intermediate strategy, first try // along one axis and then try exhaustively along that axis. - bool findChartLocation(TaskScheduler *taskScheduler, const Vector2i &startPosition, bool bruteForce, const BitImage *atlasBitImage, const BitImage *chartBitImage, const BitImage *chartBitImageRotated, int w, int h, int *best_x, int *best_y, int *best_w, int *best_h, int *best_r, bool blockAligned, uint32_t maxResolution, bool allowRotate) + bool findChartLocation(const Vector2i &startPosition, bool bruteForce, const BitImage *atlasBitImage, const BitImage *chartBitImage, const BitImage *chartBitImageRotated, int w, int h, int *best_x, int *best_y, int *best_w, int *best_h, int *best_r, bool blockAligned, uint32_t maxResolution, bool allowRotate) { const int attempts = 4096; if (bruteForce || attempts >= w * h) - return findChartLocation_bruteForce(taskScheduler, startPosition, atlasBitImage, chartBitImage, chartBitImageRotated, w, h, best_x, best_y, best_w, best_h, best_r, blockAligned, maxResolution, allowRotate); + return findChartLocation_bruteForce(startPosition, atlasBitImage, chartBitImage, chartBitImageRotated, w, h, best_x, best_y, best_w, best_h, best_r, blockAligned, maxResolution, allowRotate); return findChartLocation_random(atlasBitImage, chartBitImage, chartBitImageRotated, w, h, best_x, best_y, best_w, best_h, best_r, attempts, blockAligned, maxResolution, allowRotate); } - bool findChartLocation_bruteForce(TaskScheduler *taskScheduler, const Vector2i &startPosition, const BitImage *atlasBitImage, const BitImage *chartBitImage, const BitImage *chartBitImageRotated, int w, int h, int *best_x, int *best_y, int *best_w, int *best_h, int *best_r, bool blockAligned, uint32_t maxResolution, bool allowRotate) + bool findChartLocation_bruteForce(const Vector2i &startPosition, const BitImage *atlasBitImage, const BitImage *chartBitImage, const BitImage *chartBitImageRotated, int w, int h, int *best_x, int *best_y, int *best_w, int *best_h, int *best_r, bool blockAligned, uint32_t maxResolution, bool allowRotate) { const int stepSize = blockAligned ? 4 : 1; - const int chartMinHeight = min(chartBitImage->height(), chartBitImageRotated->height()); - uint32_t taskCount = 0; - for (int y = startPosition.y; y <= h + stepSize; y += stepSize) { - if (maxResolution > 0 && y > (int)maxResolution - chartMinHeight) - break; - taskCount++; - } - m_bruteForceTaskArgs.clear(); - m_bruteForceTaskArgs.resize(taskCount); - TaskGroupHandle taskGroup = taskScheduler->createTaskGroup(taskCount); - std::atomic<bool> finished(false); // One of the tasks found a location that doesn't expand the atlas. - uint32_t i = 0; - for (int y = startPosition.y; y <= h + stepSize; y += stepSize) { - if (maxResolution > 0 && y > (int)maxResolution - chartMinHeight) - break; - FindChartLocationBruteForceTaskArgs &args = m_bruteForceTaskArgs[i]; - args.finished = &finished; - args.startPosition = Vector2i(y == startPosition.y ? startPosition.x : 0, y); - args.atlasBitImage = atlasBitImage; - args.chartBitImage = chartBitImage; - args.chartBitImageRotated = chartBitImageRotated; - args.w = w; - args.h = h; - args.blockAligned = blockAligned; - args.allowRotate = allowRotate; - args.maxResolution = maxResolution; - Task task; - task.userData = &m_bruteForceTaskArgs[i]; - task.func = runFindChartLocationBruteForceTask; - taskScheduler->run(taskGroup, task); - i++; - } - taskScheduler->wait(&taskGroup); - // Find the task result with the best metric. int best_metric = INT_MAX; - bool best_insideAtlas = false; - for (i = 0; i < taskCount; i++) { - FindChartLocationBruteForceTaskArgs &args = m_bruteForceTaskArgs[i]; - if (args.best_metric > best_metric) - continue; - // A location that doesn't expand the atlas is always preferred. - if (!args.best_insideAtlas && best_insideAtlas) - continue; - // If metric is the same, pick the one closest to the origin. - if (args.best_insideAtlas == best_insideAtlas && args.best_metric == best_metric && max(args.best_x, args.best_y) >= max(*best_x, *best_y)) - continue; - best_metric = args.best_metric; - best_insideAtlas = args.best_insideAtlas; - *best_x = args.best_x; - *best_y = args.best_y; - *best_w = args.best_w; - *best_h = args.best_h; - *best_r = args.best_r; + // Try two different orientations. + for (int r = 0; r < 2; r++) { + 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 + stepSize; y += stepSize) { + if (maxResolution > 0 && y > (int)maxResolution - ch) + break; + for (int x = (y == startPosition.y ? startPosition.x : 0); x <= w + stepSize; x += stepSize) { + if (maxResolution > 0 && x > (int)maxResolution - cw) + break; + // Early out if metric is not better. + const int extentX = max(w, x + cw), extentY = max(h, y + ch); + const int area = extentX * extentY; + const int extents = max(extentX, extentY); + const int metric = extents * extents + area; + if (metric > best_metric) + continue; + // If metric is the same, pick the one closest to the origin. + if (metric == best_metric && max(x, y) >= max(*best_x, *best_y)) + continue; + if (!atlasBitImage->canBlit(r == 1 ? *chartBitImageRotated : *chartBitImage, x, y)) + continue; + best_metric = metric; + *best_x = x; + *best_y = y; + *best_w = cw; + *best_h = ch; + *best_r = r; + if (area == w * h) + return true; // Chart is completely inside, do not look at any other location. + } + } } return best_metric != INT_MAX; } @@ -7581,10 +8458,10 @@ private: for (int x = 0; x < w; x++) { int xx = x + offset_x; if (xx >= 0) { - if (image->bitAt(x, y)) { + if (image->get(x, y)) { if (xx < atlas_w && yy < atlas_h) { - XA_DEBUG_ASSERT(atlasBitImage->bitAt(xx, yy) == false); - atlasBitImage->setBitAt(xx, yy); + XA_DEBUG_ASSERT(atlasBitImage->get(xx, yy) == false); + atlasBitImage->set(xx, yy); } } } @@ -7593,14 +8470,23 @@ private: } } - void bilinearExpand(const Chart *chart, BitImage *source, BitImage *dest, BitImage *destRotated) const + void bilinearExpand(const Chart *chart, BitImage *source, BitImage *dest, BitImage *destRotated, UniformGrid2 &boundaryEdgeGrid) const { + boundaryEdgeGrid.reset(chart->vertices, chart->indices); + if (chart->boundaryEdges) { + const uint32_t edgeCount = chart->boundaryEdges->size(); + for (uint32_t i = 0; i < edgeCount; i++) + boundaryEdgeGrid.append((*chart->boundaryEdges)[i]); + } else { + for (uint32_t i = 0; i < chart->indexCount; i++) + boundaryEdgeGrid.append(i); + } const int xOffsets[] = { -1, 0, 1, -1, 1, -1, 0, 1 }; const int yOffsets[] = { -1, -1, -1, 0, 0, 1, 1, 1 }; for (uint32_t y = 0; y < source->height(); y++) { for (uint32_t x = 0; x < source->width(); x++) { // Copy pixels from source. - if (source->bitAt(x, y)) + if (source->get(x, y)) goto setPixel; // Empty pixel. If none of of the surrounding pixels are set, this pixel can't be sampled by bilinear interpolation. { @@ -7610,44 +8496,32 @@ private: const int sy = (int)y + yOffsets[s]; if (sx < 0 || sy < 0 || sx >= (int)source->width() || sy >= (int)source->height()) continue; - if (source->bitAt((uint32_t)sx, (uint32_t)sy)) + if (source->get((uint32_t)sx, (uint32_t)sy)) break; } if (s == 8) continue; } - // If a 2x2 square centered on the pixels centroid intersects the triangle, this pixel will be sampled by bilinear interpolation. - // See "Precomputed Global Illumination in Frostbite (GDC 2018)" page 95 - for (uint32_t f = 0; f < chart->indexCount / 3; f++) { + { + // If a 2x2 square centered on the pixels centroid intersects the triangle, this pixel will be sampled by bilinear interpolation. + // See "Precomputed Global Illumination in Frostbite (GDC 2018)" page 95 const Vector2 centroid((float)x + 0.5f, (float)y + 0.5f); - Vector2 vertices[3]; - for (uint32_t i = 0; i < 3; i++) - vertices[i] = chart->vertices[chart->indices[f * 3 + i]]; - // Test for triangle vertex in square bounds. - for (uint32_t i = 0; i < 3; i++) { - const Vector2 &v = vertices[i]; - if (v.x > centroid.x - 1.0f && v.x < centroid.x + 1.0f && v.y > centroid.y - 1.0f && v.y < centroid.y + 1.0f) - goto setPixel; - } - // Test for triangle edge intersection with square edge. const Vector2 squareVertices[4] = { Vector2(centroid.x - 1.0f, centroid.y - 1.0f), Vector2(centroid.x + 1.0f, centroid.y - 1.0f), Vector2(centroid.x + 1.0f, centroid.y + 1.0f), Vector2(centroid.x - 1.0f, centroid.y + 1.0f) }; - for (uint32_t i = 0; i < 3; i++) { - for (uint32_t j = 0; j < 4; j++) { - if (linesIntersect(vertices[i], vertices[(i + 1) % 3], squareVertices[j], squareVertices[(j + 1) % 4], 0.0f)) - goto setPixel; - } + for (uint32_t j = 0; j < 4; j++) { + if (boundaryEdgeGrid.intersect(squareVertices[j], squareVertices[(j + 1) % 4], 0.0f)) + goto setPixel; } } continue; setPixel: - dest->setBitAt(x, y); + dest->set(x, y); if (destRotated) - destRotated->setBitAt(y, x); + destRotated->set(y, x); } } } @@ -7660,9 +8534,9 @@ private: static bool drawTriangleCallback(void *param, int x, int y) { auto args = (DrawTriangleCallbackArgs *)param; - args->chartBitImage->setBitAt(x, y); + args->chartBitImage->set(x, y); if (args->chartBitImageRotated) - args->chartBitImageRotated->setBitAt(y, x); + args->chartBitImageRotated->set(y, x); return true; } @@ -7670,7 +8544,6 @@ private: Array<float> m_utilization; Array<BitImage *> m_bitImages; Array<Chart *> m_charts; - Array<FindChartLocationBruteForceTaskArgs> m_bruteForceTaskArgs; RadixSort m_radix; uint32_t m_width = 0; uint32_t m_height = 0; @@ -7789,13 +8662,6 @@ static void runAddMeshTask(void *userData) } 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()); @@ -7805,22 +8671,22 @@ static void runAddMeshTask(void *userData) mesh->writeObjVertices(file); // groups uint32_t numGroups = 0; - for (uint32_t i = 0; i < mesh->faceGroupCount(); i++) { - if (mesh->faceGroupAt(i) != UINT32_MAX) + for (uint32_t i = 0; i < mesh->faceCount(); i++) { + if (mesh->faceGroupAt(i) != Mesh::kInvalidFaceGroup) 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++) { + for (uint32_t f = 0; f < mesh->faceCount(); 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) + for (uint32_t f = 0; f < mesh->faceCount(); f++) { + if (mesh->faceGroupAt(f) == Mesh::kInvalidFaceGroup) mesh->writeObjFace(file, f); } mesh->writeObjBoundaryEges(file); @@ -8033,7 +8899,6 @@ void AddMeshJoin(Atlas *atlas) XA_PROFILE_PRINT_AND_RESET(" Total (thread): ", addMeshThread) XA_PROFILE_PRINT_AND_RESET(" Create colocals: ", addMeshCreateColocals) XA_PROFILE_PRINT_AND_RESET(" Create face groups: ", addMeshCreateFaceGroups) - XA_PROFILE_PRINT_AND_RESET(" Create boundaries: ", addMeshCreateBoundaries) XA_PROFILE_PRINT_AND_RESET(" Create chart groups (real): ", addMeshCreateChartGroupsReal) XA_PROFILE_PRINT_AND_RESET(" Create chart groups (thread): ", addMeshCreateChartGroupsThread) XA_PRINT_MEM_USAGE @@ -8044,16 +8909,7 @@ 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; - } + bool operator==(const EdgeKey &k) const { return v0 == k.v0 && v1 == k.v1; } uint32_t v0; uint32_t v1; @@ -8119,15 +8975,15 @@ AddMeshError::Enum AddUvMesh(Atlas *atlas, const UvMeshDecl &decl) for (uint32_t i = 0; i < indexCount; i++) vertexToFaceMap.add(meshInstance->texcoords[mesh->indices[i]]); internal::BitArray faceAssigned(faceCount); - faceAssigned.clearAll(); + faceAssigned.zeroOutMemory(); for (uint32_t f = 0; f < faceCount; f++) { - if (faceAssigned.bitAt(f)) + if (faceAssigned.get(f)) continue; // Found an unassigned face, create a new chart. internal::UvMeshChart *chart = XA_NEW(internal::MemTag::Default, internal::UvMeshChart); chart->material = decl.faceMaterialData ? decl.faceMaterialData[f] : 0; // Walk incident faces and assign them to the chart. - faceAssigned.setBitAt(f); + faceAssigned.set(f); chart->faces.push_back(f); for (;;) { bool newFaceAssigned = false; @@ -8140,8 +8996,8 @@ AddMeshError::Enum AddUvMesh(Atlas *atlas, const UvMeshDecl &decl) while (mapIndex != UINT32_MAX) { const uint32_t face2 = mapIndex / 3; // 3 vertices added per face. // Materials must match. - if (!faceAssigned.bitAt(face2) && (!decl.faceMaterialData || decl.faceMaterialData[face] == decl.faceMaterialData[face2])) { - faceAssigned.setBitAt(face2); + if (!faceAssigned.get(face2) && (!decl.faceMaterialData || decl.faceMaterialData[face] == decl.faceMaterialData[face2])) { + faceAssigned.set(face2); chart->faces.push_back(face2); newFaceAssigned = true; } @@ -8202,6 +9058,7 @@ void ComputeCharts(Atlas *atlas, ChartOptions chartOptions) continue; for (uint32_t k = 0; k < chartGroup->chartCount(); k++) { const internal::param::Chart *chart = chartGroup->chartAt(k); +#if XA_PRINT_CHART_WARNINGS if (chart->warningFlags() & internal::param::ChartWarningFlags::CloseHolesFailed) XA_PRINT_WARNING(" Chart %u (mesh %u, group %u, id %u): failed to close holes\n", chartCount, i, j, k); if (chart->warningFlags() & internal::param::ChartWarningFlags::FixTJunctionsDuplicatedEdge) @@ -8210,8 +9067,7 @@ void ComputeCharts(Atlas *atlas, ChartOptions chartOptions) 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); +#endif holesCount += chart->closedHolesCount(); if (chart->closedHolesCount() > 0) chartsWithHolesCount++; @@ -8279,7 +9135,7 @@ void ParameterizeCharts(Atlas *atlas, ParameterizeFunc func) return; } XA_PROFILE_END(parameterizeChartsReal) - uint32_t chartCount = 0, orthoChartsCount = 0, planarChartsCount = 0, chartsAddedCount = 0, chartsDeletedCount = 0; + uint32_t chartCount = 0, orthoChartsCount = 0, planarChartsCount = 0, lscmChartsCount = 0, piecewiseChartsCount = 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); @@ -8287,19 +9143,23 @@ void ParameterizeCharts(Atlas *atlas, ParameterizeFunc func) continue; for (uint32_t k = 0; k < chartGroup->chartCount(); k++) { const internal::param::Chart *chart = chartGroup->chartAt(k); - if (chart->isPlanar()) + if (chart->type() == ChartType::Planar) planarChartsCount++; - else if (chart->isOrtho()) + else if (chart->type() == ChartType::Ortho) orthoChartsCount++; + else if (chart->type() == ChartType::LSCM) + lscmChartsCount++; + else if (chart->type() == ChartType::Piecewise) + piecewiseChartsCount++; } chartCount += chartGroup->chartCount(); chartsAddedCount += chartGroup->paramAddedChartsCount(); chartsDeletedCount += chartGroup->paramDeletedChartsCount(); } } - XA_PRINT(" %u planar charts, %u ortho charts, %u other\n", planarChartsCount, orthoChartsCount, chartCount - (planarChartsCount + orthoChartsCount)); + XA_PRINT(" %u planar charts, %u ortho charts, %u LSCM charts, %u piecewise charts\n", planarChartsCount, orthoChartsCount, lscmChartsCount, piecewiseChartsCount); if (chartsDeletedCount > 0) { - XA_PRINT(" %u charts deleted due to invalid parameterizations, %u new charts added\n", chartsDeletedCount, chartsAddedCount); + XA_PRINT(" %u charts with invalid parameterizations replaced with %u new charts\n", chartsDeletedCount, chartsAddedCount); XA_PRINT(" %u charts\n", chartCount); } uint32_t chartIndex = 0, invalidParamCount = 0; @@ -8310,7 +9170,7 @@ void ParameterizeCharts(Atlas *atlas, ParameterizeFunc func) 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(); + const internal::param::Quality &quality = chart->quality(); #if XA_DEBUG_EXPORT_OBJ_CHARTS_AFTER_PARAMETERIZATION { char filename[256]; @@ -8319,13 +9179,20 @@ void ParameterizeCharts(Atlas *atlas, ParameterizeFunc func) } #endif bool invalid = false; + const char *type = "LSCM"; + if (chart->type() == ChartType::Planar) + type = "planar"; + else if (chart->type() == ChartType::Ortho) + type = "ortho"; + else if (chart->type() == ChartType::Piecewise) + type = "piecewise"; if (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"); + XA_PRINT_WARNING(" Chart %u (mesh %u, group %u, id %u) (%s): invalid parameterization, self-intersecting boundary.\n", chartIndex, i, j, k, type); } if (quality.flippedTriangleCount > 0) { 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); + XA_PRINT_WARNING(" Chart %u (mesh %u, group %u, id %u) (%s): invalid parameterization, %u / %u flipped triangles.\n", chartIndex, i, j, k, type, quality.flippedTriangleCount, quality.totalTriangleCount); } if (invalid) invalidParamCount++; @@ -8415,7 +9282,7 @@ void PackCharts(Atlas *atlas, PackOptions packOptions) packAtlas.addCharts(ctx->taskScheduler, &ctx->paramAtlas); XA_PROFILE_END(packChartsAddCharts) XA_PROFILE_START(packCharts) - if (!packAtlas.packCharts(ctx->taskScheduler, packOptions, ctx->progressFunc, ctx->progressUserData)) + if (!packAtlas.packCharts(packOptions, ctx->progressFunc, ctx->progressUserData)) return; XA_PROFILE_END(packCharts) // Populate atlas object with pack results. @@ -8440,8 +9307,7 @@ void PackCharts(Atlas *atlas, PackOptions packOptions) XA_PROFILE_PRINT_AND_RESET(" Restore texcoords: ", packChartsAddChartsRestoreTexcoords) XA_PROFILE_PRINT_AND_RESET(" Rasterize: ", packChartsRasterize) XA_PROFILE_PRINT_AND_RESET(" Dilate (padding): ", packChartsDilate) - XA_PROFILE_PRINT_AND_RESET(" Find location (real): ", packChartsFindLocation) - XA_PROFILE_PRINT_AND_RESET(" Find location (thread): ", packChartsFindLocationThread) + XA_PROFILE_PRINT_AND_RESET(" Find location: ", packChartsFindLocation) XA_PROFILE_PRINT_AND_RESET(" Blit: ", packChartsBlit) XA_PRINT_MEM_USAGE XA_PRINT("Building output meshes\n"); @@ -8527,9 +9393,7 @@ void PackCharts(Atlas *atlas, PackOptions packOptions) 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->type = chart->type(); outputChart->faceCount = mesh->faceCount(); outputChart->faceArray = XA_ALLOC_ARRAY(internal::MemTag::Default, uint32_t, outputChart->faceCount); for (uint32_t f = 0; f < outputChart->faceCount; f++) diff --git a/thirdparty/xatlas/xatlas.h b/thirdparty/xatlas/xatlas.h index 7be165e7e5..e59f493287 100644 --- a/thirdparty/xatlas/xatlas.h +++ b/thirdparty/xatlas/xatlas.h @@ -35,11 +35,14 @@ Copyright NVIDIA Corporation 2006 -- Ignacio Castano <icastano@nvidia.com> namespace xatlas { -struct ChartFlags +struct ChartType { - enum + enum Enum { - Invalid = 1 << 0 + Planar, + Ortho, + LSCM, + Piecewise }; }; @@ -47,10 +50,10 @@ struct ChartFlags struct Chart { uint32_t atlasIndex; // Sub-atlas index. - uint32_t flags; uint32_t *faceArray; uint32_t faceCount; uint32_t material; + ChartType::Enum type; }; // Output vertex. |