godot/thirdparty/xatlas/xatlas.cpp

7385 lines
217 KiB
C++
Raw Blame History

// This code is in the public domain -- castanyo@yahoo.es
#include "xatlas.h"
#include <assert.h>
#include <float.h>
#include <math.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <algorithm>
#include <cmath>
#include <memory>
#include <unordered_map>
#include <vector>
#undef min
#undef max
#ifndef xaAssert
#define xaAssert(exp) \
if (!(exp)) { \
xaPrint("%s %s %s\n", #exp, __FILE__, __LINE__); \
}
#endif
#ifndef xaDebugAssert
#define xaDebugAssert(exp) assert(exp)
#endif
#ifndef xaPrint
#define xaPrint(...) \
if (xatlas::internal::s_print) { \
xatlas::internal::s_print(__VA_ARGS__); \
}
#endif
#ifdef _MSC_VER
// Ignore gcc attributes.
#define __attribute__(X)
#endif
#ifdef _MSC_VER
#define restrict
#define NV_FORCEINLINE __forceinline
#else
#define restrict __restrict__
#define NV_FORCEINLINE __attribute__((always_inline)) inline
#endif
#define NV_UINT32_MAX 0xffffffff
#define NV_FLOAT_MAX 3.402823466e+38F
#ifndef PI
#define PI float(3.1415926535897932384626433833)
#endif
#define NV_EPSILON (0.0001f)
#define NV_NORMAL_EPSILON (0.001f)
namespace xatlas {
namespace internal {
static PrintFunc s_print = NULL;
static int align(int x, int a) {
return (x + a - 1) & ~(a - 1);
}
static bool isAligned(int x, int a) {
return (x & (a - 1)) == 0;
}
/// Return the maximum of the three arguments.
template <typename T>
static T max3(const T &a, const T &b, const T &c) {
return std::max(a, std::max(b, c));
}
/// Return the maximum of the three arguments.
template <typename T>
static T min3(const T &a, const T &b, const T &c) {
return std::min(a, std::min(b, c));
}
/// Clamp between two values.
template <typename T>
static T clamp(const T &x, const T &a, const T &b) {
return std::min(std::max(x, a), b);
}
static float saturate(float f) {
return clamp(f, 0.0f, 1.0f);
}
// Robust floating point comparisons:
// http://realtimecollisiondetection.net/blog/?p=89
static bool equal(const float f0, const float f1, const float epsilon = NV_EPSILON) {
//return fabs(f0-f1) <= epsilon;
return fabs(f0 - f1) <= epsilon * max3(1.0f, fabsf(f0), fabsf(f1));
}
NV_FORCEINLINE static int ftoi_floor(float val) {
return (int)val;
}
NV_FORCEINLINE static int ftoi_ceil(float val) {
return (int)ceilf(val);
}
NV_FORCEINLINE static int ftoi_round(float f) {
return int(floorf(f + 0.5f));
}
static bool isZero(const float f, const float epsilon = NV_EPSILON) {
return fabs(f) <= epsilon;
}
static float lerp(float f0, float f1, float t) {
const float s = 1.0f - t;
return f0 * s + f1 * t;
}
static float square(float f) {
return f * f;
}
static int square(int i) {
return i * i;
}
/** Return the next power of two.
* @see http://graphics.stanford.edu/~seander/bithacks.html
* @warning Behaviour for 0 is undefined.
* @note isPowerOfTwo(x) == true -> nextPowerOfTwo(x) == x
* @note nextPowerOfTwo(x) = 2 << log2(x-1)
*/
static uint32_t nextPowerOfTwo(uint32_t x) {
xaDebugAssert(x != 0);
// On modern CPUs this is supposed to be as fast as using the bsr instruction.
x--;
x |= x >> 1;
x |= x >> 2;
x |= x >> 4;
x |= x >> 8;
x |= x >> 16;
return x + 1;
}
static uint64_t nextPowerOfTwo(uint64_t x) {
xaDebugAssert(x != 0);
uint32_t p = 1;
while (x > p) {
p += p;
}
return p;
}
static uint32_t sdbmHash(const void *data_in, uint32_t size, uint32_t h = 5381) {
const uint8_t *data = (const uint8_t *)data_in;
uint32_t i = 0;
while (i < size) {
h = (h << 16) + (h << 6) - h + (uint32_t)data[i++];
}
return h;
}
// Note that this hash does not handle NaN properly.
static uint32_t sdbmFloatHash(const float *f, uint32_t count, uint32_t h = 5381) {
for (uint32_t i = 0; i < count; i++) {
union {
float f;
uint32_t i;
} x = { f[i] };
if (x.i == 0x80000000) x.i = 0;
h = sdbmHash(&x, 4, h);
}
return h;
}
template <typename T>
static uint32_t hash(const T &t, uint32_t h = 5381) {
return sdbmHash(&t, sizeof(T), h);
}
static uint32_t hash(const float &f, uint32_t h) {
return sdbmFloatHash(&f, 1, h);
}
// Functors for hash table:
template <typename Key>
struct Hash {
uint32_t operator()(const Key &k) const { return hash(k); }
};
template <typename Key>
struct Equal {
bool operator()(const Key &k0, const Key &k1) const { return k0 == k1; }
};
class Vector2 {
public:
typedef Vector2 const &Arg;
Vector2() {}
explicit Vector2(float f) :
x(f),
y(f) {}
Vector2(float x, float y) :
x(x),
y(y) {}
Vector2(Vector2::Arg v) :
x(v.x),
y(v.y) {}
const Vector2 &operator=(Vector2::Arg v) {
x = v.x;
y = v.y;
return *this;
}
const float *ptr() const { return &x; }
void set(float _x, float _y) {
x = _x;
y = _y;
}
Vector2 operator-() const {
return Vector2(-x, -y);
}
void operator+=(Vector2::Arg v) {
x += v.x;
y += v.y;
}
void operator-=(Vector2::Arg v) {
x -= v.x;
y -= v.y;
}
void operator*=(float s) {
x *= s;
y *= s;
}
void operator*=(Vector2::Arg v) {
x *= v.x;
y *= v.y;
}
friend bool operator==(Vector2::Arg a, Vector2::Arg b) {
return a.x == b.x && a.y == b.y;
}
friend bool operator!=(Vector2::Arg a, Vector2::Arg b) {
return a.x != b.x || a.y != b.y;
}
union {
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable : 4201)
#endif
struct
{
float x, y;
};
#ifdef _MSC_VER
#pragma warning(pop)
#endif
float component[2];
};
};
Vector2 operator+(Vector2::Arg a, Vector2::Arg b) {
return Vector2(a.x + b.x, a.y + b.y);
}
Vector2 operator-(Vector2::Arg a, Vector2::Arg b) {
return Vector2(a.x - b.x, a.y - b.y);
}
Vector2 operator*(Vector2::Arg v, float s) {
return Vector2(v.x * s, v.y * s);
}
Vector2 operator*(Vector2::Arg v1, Vector2::Arg v2) {
return Vector2(v1.x * v2.x, v1.y * v2.y);
}
Vector2 operator/(Vector2::Arg v, float s) {
return Vector2(v.x / s, v.y / s);
}
Vector2 lerp(Vector2::Arg v1, Vector2::Arg v2, float t) {
const float s = 1.0f - t;
return Vector2(v1.x * s + t * v2.x, v1.y * s + t * v2.y);
}
float dot(Vector2::Arg a, Vector2::Arg b) {
return a.x * b.x + a.y * b.y;
}
float lengthSquared(Vector2::Arg v) {
return v.x * v.x + v.y * v.y;
}
float length(Vector2::Arg v) {
return sqrtf(lengthSquared(v));
}
float distance(Vector2::Arg a, Vector2::Arg b) {
return length(a - b);
}
bool isNormalized(Vector2::Arg v, float epsilon = NV_NORMAL_EPSILON) {
return equal(length(v), 1, epsilon);
}
Vector2 normalize(Vector2::Arg v, float epsilon = NV_EPSILON) {
float l = length(v);
xaDebugAssert(!isZero(l, epsilon));
#ifdef NDEBUG
epsilon = 0; // silence unused parameter warning
#endif
Vector2 n = v * (1.0f / l);
xaDebugAssert(isNormalized(n));
return n;
}
Vector2 normalizeSafe(Vector2::Arg v, Vector2::Arg fallback, float epsilon = NV_EPSILON) {
float l = length(v);
if (isZero(l, epsilon)) {
return fallback;
}
return v * (1.0f / l);
}
bool equal(Vector2::Arg v1, Vector2::Arg v2, float epsilon = NV_EPSILON) {
return equal(v1.x, v2.x, epsilon) && equal(v1.y, v2.y, epsilon);
}
Vector2 max(Vector2::Arg a, Vector2::Arg b) {
return Vector2(std::max(a.x, b.x), std::max(a.y, b.y));
}
bool isFinite(Vector2::Arg v) {
return std::isfinite(v.x) && std::isfinite(v.y);
}
// Note, this is the area scaled by 2!
float triangleArea(Vector2::Arg v0, Vector2::Arg v1) {
return (v0.x * v1.y - v0.y * v1.x); // * 0.5f;
}
float triangleArea(Vector2::Arg a, Vector2::Arg b, Vector2::Arg c) {
// IC: While it may be appealing to use the following expression:
//return (c.x * a.y + a.x * b.y + b.x * c.y - b.x * a.y - c.x * b.y - a.x * c.y); // * 0.5f;
// That's actually a terrible idea. Small triangles far from the origin can end up producing fairly large floating point
// 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);
}
float triangleArea2(Vector2::Arg v1, Vector2::Arg v2, Vector2::Arg v3) {
return 0.5f * (v3.x * v1.y + v1.x * v2.y + v2.x * v3.y - v2.x * v1.y - v3.x * v2.y - v1.x * v3.y);
}
static uint32_t hash(const Vector2 &v, uint32_t h) {
return sdbmFloatHash(v.component, 2, h);
}
class Vector3 {
public:
typedef Vector3 const &Arg;
Vector3() {}
explicit Vector3(float f) :
x(f),
y(f),
z(f) {}
Vector3(float x, float y, float z) :
x(x),
y(y),
z(z) {}
Vector3(Vector2::Arg v, float z) :
x(v.x),
y(v.y),
z(z) {}
Vector3(Vector3::Arg v) :
x(v.x),
y(v.y),
z(v.z) {}
const Vector3 &operator=(Vector3::Arg v) {
x = v.x;
y = v.y;
z = v.z;
return *this;
}
Vector2 xy() const {
return Vector2(x, y);
}
const float *ptr() const { return &x; }
void set(float _x, float _y, float _z) {
x = _x;
y = _y;
z = _z;
}
Vector3 operator-() const {
return Vector3(-x, -y, -z);
}
void operator+=(Vector3::Arg v) {
x += v.x;
y += v.y;
z += v.z;
}
void operator-=(Vector3::Arg v) {
x -= v.x;
y -= v.y;
z -= v.z;
}
void operator*=(float s) {
x *= s;
y *= s;
z *= s;
}
void operator/=(float s) {
float is = 1.0f / s;
x *= is;
y *= is;
z *= is;
}
void operator*=(Vector3::Arg v) {
x *= v.x;
y *= v.y;
z *= v.z;
}
void operator/=(Vector3::Arg v) {
x /= v.x;
y /= v.y;
z /= v.z;
}
friend bool operator==(Vector3::Arg a, Vector3::Arg b) {
return a.x == b.x && a.y == b.y && a.z == b.z;
}
friend bool operator!=(Vector3::Arg a, Vector3::Arg b) {
return a.x != b.x || a.y != b.y || a.z != b.z;
}
union {
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable : 4201)
#endif
struct
{
float x, y, z;
};
#ifdef _MSC_VER
#pragma warning(pop)
#endif
float component[3];
};
};
Vector3 add(Vector3::Arg a, Vector3::Arg b) {
return Vector3(a.x + b.x, a.y + b.y, a.z + b.z);
}
Vector3 add(Vector3::Arg a, float b) {
return Vector3(a.x + b, a.y + b, a.z + b);
}
Vector3 operator+(Vector3::Arg a, Vector3::Arg b) {
return add(a, b);
}
Vector3 operator+(Vector3::Arg a, float b) {
return add(a, b);
}
Vector3 sub(Vector3::Arg a, Vector3::Arg b) {
return Vector3(a.x - b.x, a.y - b.y, a.z - b.z);
}
Vector3 sub(Vector3::Arg a, float b) {
return Vector3(a.x - b, a.y - b, a.z - b);
}
Vector3 operator-(Vector3::Arg a, Vector3::Arg b) {
return sub(a, b);
}
Vector3 operator-(Vector3::Arg a, float b) {
return sub(a, b);
}
Vector3 cross(Vector3::Arg a, Vector3::Arg b) {
return Vector3(a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x);
}
Vector3 operator*(Vector3::Arg v, float s) {
return Vector3(v.x * s, v.y * s, v.z * s);
}
Vector3 operator*(float s, Vector3::Arg v) {
return Vector3(v.x * s, v.y * s, v.z * s);
}
Vector3 operator*(Vector3::Arg v, Vector3::Arg s) {
return Vector3(v.x * s.x, v.y * s.y, v.z * s.z);
}
Vector3 operator/(Vector3::Arg v, float s) {
return v * (1.0f / s);
}
Vector3 lerp(Vector3::Arg v1, Vector3::Arg v2, float t) {
const float s = 1.0f - t;
return Vector3(v1.x * s + t * v2.x, v1.y * s + t * v2.y, v1.z * s + t * v2.z);
}
float dot(Vector3::Arg a, Vector3::Arg b) {
return a.x * b.x + a.y * b.y + a.z * b.z;
}
float lengthSquared(Vector3::Arg v) {
return v.x * v.x + v.y * v.y + v.z * v.z;
}
float length(Vector3::Arg v) {
return sqrtf(lengthSquared(v));
}
float distance(Vector3::Arg a, Vector3::Arg b) {
return length(a - b);
}
float distanceSquared(Vector3::Arg a, Vector3::Arg b) {
return lengthSquared(a - b);
}
bool isNormalized(Vector3::Arg v, float epsilon = NV_NORMAL_EPSILON) {
return equal(length(v), 1, epsilon);
}
Vector3 normalize(Vector3::Arg v, float epsilon = NV_EPSILON) {
float l = length(v);
xaDebugAssert(!isZero(l, epsilon));
#ifdef NDEBUG
epsilon = 0; // silence unused parameter warning
#endif
Vector3 n = v * (1.0f / l);
xaDebugAssert(isNormalized(n));
return n;
}
Vector3 normalizeSafe(Vector3::Arg v, Vector3::Arg fallback, float epsilon = NV_EPSILON) {
float l = length(v);
if (isZero(l, epsilon)) {
return fallback;
}
return v * (1.0f / l);
}
bool equal(Vector3::Arg v1, Vector3::Arg v2, float epsilon = NV_EPSILON) {
return equal(v1.x, v2.x, epsilon) && equal(v1.y, v2.y, epsilon) && equal(v1.z, v2.z, epsilon);
}
Vector3 min(Vector3::Arg a, Vector3::Arg b) {
return Vector3(std::min(a.x, b.x), std::min(a.y, b.y), std::min(a.z, b.z));
}
Vector3 max(Vector3::Arg a, Vector3::Arg b) {
return Vector3(std::max(a.x, b.x), std::max(a.y, b.y), std::max(a.z, b.z));
}
Vector3 clamp(Vector3::Arg v, float min, float max) {
return Vector3(clamp(v.x, min, max), clamp(v.y, min, max), clamp(v.z, min, max));
}
Vector3 saturate(Vector3::Arg v) {
return Vector3(saturate(v.x), saturate(v.y), saturate(v.z));
}
Vector3 floor(Vector3::Arg v) {
return Vector3(floorf(v.x), floorf(v.y), floorf(v.z));
}
bool isFinite(Vector3::Arg v) {
return std::isfinite(v.x) && std::isfinite(v.y) && std::isfinite(v.z);
}
static uint32_t hash(const Vector3 &v, uint32_t h) {
return sdbmFloatHash(v.component, 3, h);
}
/// Basis class to compute tangent space basis, ortogonalizations and to
/// transform vectors from one space to another.
class Basis {
public:
/// Create a null basis.
Basis() :
tangent(0, 0, 0),
bitangent(0, 0, 0),
normal(0, 0, 0) {}
void buildFrameForDirection(Vector3::Arg d, float angle = 0) {
xaAssert(isNormalized(d));
normal = d;
// Choose minimum axis.
if (fabsf(normal.x) < fabsf(normal.y) && fabsf(normal.x) < fabsf(normal.z)) {
tangent = Vector3(1, 0, 0);
} else if (fabsf(normal.y) < fabsf(normal.z)) {
tangent = Vector3(0, 1, 0);
} else {
tangent = Vector3(0, 0, 1);
}
// Ortogonalize
tangent -= normal * dot(normal, tangent);
tangent = normalize(tangent);
bitangent = cross(normal, tangent);
// Rotate frame around normal according to angle.
if (angle != 0.0f) {
float c = cosf(angle);
float s = sinf(angle);
Vector3 tmp = c * tangent - s * bitangent;
bitangent = s * tangent + c * bitangent;
tangent = tmp;
}
}
Vector3 tangent;
Vector3 bitangent;
Vector3 normal;
};
// Simple bit array.
class BitArray {
public:
BitArray() :
m_size(0) {}
BitArray(uint32_t sz) {
resize(sz);
}
uint32_t size() const {
return m_size;
}
void clear() {
resize(0);
}
void resize(uint32_t new_size) {
m_size = new_size;
m_wordArray.resize((m_size + 31) >> 5);
}
/// Get bit.
bool bitAt(uint32_t b) const {
xaDebugAssert(b < m_size);
return (m_wordArray[b >> 5] & (1 << (b & 31))) != 0;
}
// Set a bit.
void setBitAt(uint32_t idx) {
xaDebugAssert(idx < m_size);
m_wordArray[idx >> 5] |= (1 << (idx & 31));
}
// Toggle a bit.
void toggleBitAt(uint32_t idx) {
xaDebugAssert(idx < m_size);
m_wordArray[idx >> 5] ^= (1 << (idx & 31));
}
// Set a bit to the given value. @@ Rename modifyBitAt?
void setBitAt(uint32_t idx, bool b) {
xaDebugAssert(idx < m_size);
m_wordArray[idx >> 5] = setBits(m_wordArray[idx >> 5], 1 << (idx & 31), b);
xaDebugAssert(bitAt(idx) == b);
}
// Clear all the bits.
void clearAll() {
memset(m_wordArray.data(), 0, m_wordArray.size() * sizeof(uint32_t));
}
// Set all the bits.
void setAll() {
memset(m_wordArray.data(), 0xFF, m_wordArray.size() * sizeof(uint32_t));
}
private:
// See "Conditionally set or clear bits without branching" at http://graphics.stanford.edu/~seander/bithacks.html
uint32_t setBits(uint32_t w, uint32_t m, bool b) {
return (w & ~m) | (-int(b) & m);
}
// Number of bits stored.
uint32_t m_size;
// Array of bits.
std::vector<uint32_t> m_wordArray;
};
/// Bit map. This should probably be called BitImage.
class BitMap {
public:
BitMap() :
m_width(0),
m_height(0) {}
BitMap(uint32_t w, uint32_t h) :
m_width(w),
m_height(h),
m_bitArray(w * h) {}
uint32_t width() const {
return m_width;
}
uint32_t height() const {
return m_height;
}
void resize(uint32_t w, uint32_t h, bool initValue) {
BitArray tmp(w * h);
if (initValue)
tmp.setAll();
else
tmp.clearAll();
// @@ Copying one bit at a time. This could be much faster.
for (uint32_t y = 0; y < m_height; y++) {
for (uint32_t x = 0; x < m_width; x++) {
//tmp.setBitAt(y*w + x, bitAt(x, y));
if (bitAt(x, y) != initValue) tmp.toggleBitAt(y * w + x);
}
}
std::swap(m_bitArray, tmp);
m_width = w;
m_height = h;
}
bool bitAt(uint32_t x, uint32_t y) const {
xaDebugAssert(x < m_width && y < m_height);
return m_bitArray.bitAt(y * m_width + x);
}
void setBitAt(uint32_t x, uint32_t y) {
xaDebugAssert(x < m_width && y < m_height);
m_bitArray.setBitAt(y * m_width + x);
}
void clearAll() {
m_bitArray.clearAll();
}
private:
uint32_t m_width;
uint32_t m_height;
BitArray m_bitArray;
};
// Axis Aligned Bounding Box.
class Box {
public:
Box() {}
Box(const Box &b) :
minCorner(b.minCorner),
maxCorner(b.maxCorner) {}
Box(const Vector3 &mins, const Vector3 &maxs) :
minCorner(mins),
maxCorner(maxs) {}
operator const float *() const {
return reinterpret_cast<const float *>(this);
}
// Clear the bounds.
void clearBounds() {
minCorner.set(FLT_MAX, FLT_MAX, FLT_MAX);
maxCorner.set(-FLT_MAX, -FLT_MAX, -FLT_MAX);
}
// Return extents of the box.
Vector3 extents() const {
return (maxCorner - minCorner) * 0.5f;
}
// Add a point to this box.
void addPointToBounds(const Vector3 &p) {
minCorner = min(minCorner, p);
maxCorner = max(maxCorner, p);
}
// Get the volume of the box.
float volume() const {
Vector3 d = extents();
return 8.0f * (d.x * d.y * d.z);
}
Vector3 minCorner;
Vector3 maxCorner;
};
class Fit {
public:
static Vector3 computeCentroid(int n, const Vector3 *__restrict points) {
Vector3 centroid(0.0f);
for (int i = 0; i < n; i++) {
centroid += points[i];
}
centroid /= float(n);
return centroid;
}
static Vector3 computeCovariance(int n, const Vector3 *__restrict points, float *__restrict covariance) {
// compute the centroid
Vector3 centroid = computeCentroid(n, points);
// compute covariance matrix
for (int i = 0; i < 6; i++) {
covariance[i] = 0.0f;
}
for (int i = 0; i < n; i++) {
Vector3 v = points[i] - centroid;
covariance[0] += v.x * v.x;
covariance[1] += v.x * v.y;
covariance[2] += v.x * v.z;
covariance[3] += v.y * v.y;
covariance[4] += v.y * v.z;
covariance[5] += v.z * v.z;
}
return centroid;
}
static bool isPlanar(int n, const Vector3 *points, float epsilon = NV_EPSILON) {
// compute the centroid and covariance
float matrix[6];
computeCovariance(n, points, matrix);
float eigenValues[3];
Vector3 eigenVectors[3];
if (!eigenSolveSymmetric3(matrix, eigenValues, eigenVectors)) {
return false;
}
return eigenValues[2] < epsilon;
}
// Tridiagonal solver from Charles Bloom.
// Householder transforms followed by QL decomposition.
// Seems to be based on the code from Numerical Recipes in C.
static bool eigenSolveSymmetric3(const float matrix[6], float eigenValues[3], Vector3 eigenVectors[3]) {
xaDebugAssert(matrix != NULL && eigenValues != NULL && eigenVectors != NULL);
float subd[3];
float diag[3];
float work[3][3];
work[0][0] = matrix[0];
work[0][1] = work[1][0] = matrix[1];
work[0][2] = work[2][0] = matrix[2];
work[1][1] = matrix[3];
work[1][2] = work[2][1] = matrix[4];
work[2][2] = matrix[5];
EigenSolver3_Tridiagonal(work, diag, subd);
if (!EigenSolver3_QLAlgorithm(work, diag, subd)) {
for (int i = 0; i < 3; i++) {
eigenValues[i] = 0;
eigenVectors[i] = Vector3(0);
}
return false;
}
for (int i = 0; i < 3; i++) {
eigenValues[i] = (float)diag[i];
}
// eigenvectors are the columns; make them the rows :
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
eigenVectors[j].component[i] = (float)work[i][j];
}
}
// shuffle to sort by singular value :
if (eigenValues[2] > eigenValues[0] && eigenValues[2] > eigenValues[1]) {
std::swap(eigenValues[0], eigenValues[2]);
std::swap(eigenVectors[0], eigenVectors[2]);
}
if (eigenValues[1] > eigenValues[0]) {
std::swap(eigenValues[0], eigenValues[1]);
std::swap(eigenVectors[0], eigenVectors[1]);
}
if (eigenValues[2] > eigenValues[1]) {
std::swap(eigenValues[1], eigenValues[2]);
std::swap(eigenVectors[1], eigenVectors[2]);
}
xaDebugAssert(eigenValues[0] >= eigenValues[1] && eigenValues[0] >= eigenValues[2]);
xaDebugAssert(eigenValues[1] >= eigenValues[2]);
return true;
}
private:
static void EigenSolver3_Tridiagonal(float mat[3][3], float *diag, float *subd) {
// Householder reduction T = Q^t M Q
// Input:
// mat, symmetric 3x3 matrix M
// Output:
// mat, orthogonal matrix Q
// diag, diagonal entries of T
// subd, subdiagonal entries of T (T is symmetric)
const float epsilon = 1e-08f;
float a = mat[0][0];
float b = mat[0][1];
float c = mat[0][2];
float d = mat[1][1];
float e = mat[1][2];
float f = mat[2][2];
diag[0] = a;
subd[2] = 0.f;
if (fabsf(c) >= epsilon) {
const float ell = sqrtf(b * b + c * c);
b /= ell;
c /= ell;
const float q = 2 * b * e + c * (f - d);
diag[1] = d + c * q;
diag[2] = f - c * q;
subd[0] = ell;
subd[1] = e - b * q;
mat[0][0] = 1;
mat[0][1] = 0;
mat[0][2] = 0;
mat[1][0] = 0;
mat[1][1] = b;
mat[1][2] = c;
mat[2][0] = 0;
mat[2][1] = c;
mat[2][2] = -b;
} else {
diag[1] = d;
diag[2] = f;
subd[0] = b;
subd[1] = e;
mat[0][0] = 1;
mat[0][1] = 0;
mat[0][2] = 0;
mat[1][0] = 0;
mat[1][1] = 1;
mat[1][2] = 0;
mat[2][0] = 0;
mat[2][1] = 0;
mat[2][2] = 1;
}
}
static bool EigenSolver3_QLAlgorithm(float mat[3][3], float *diag, float *subd) {
// QL iteration with implicit shifting to reduce matrix from tridiagonal
// to diagonal
const int maxiter = 32;
for (int ell = 0; ell < 3; ell++) {
int iter;
for (iter = 0; iter < maxiter; iter++) {
int m;
for (m = ell; m <= 1; m++) {
float dd = fabsf(diag[m]) + fabsf(diag[m + 1]);
if (fabsf(subd[m]) + dd == dd)
break;
}
if (m == ell)
break;
float g = (diag[ell + 1] - diag[ell]) / (2 * subd[ell]);
float r = sqrtf(g * g + 1);
if (g < 0)
g = diag[m] - diag[ell] + subd[ell] / (g - r);
else
g = diag[m] - diag[ell] + subd[ell] / (g + r);
float s = 1, c = 1, p = 0;
for (int i = m - 1; i >= ell; i--) {
float f = s * subd[i], b = c * subd[i];
if (fabsf(f) >= fabsf(g)) {
c = g / f;
r = sqrtf(c * c + 1);
subd[i + 1] = f * r;
c *= (s = 1 / r);
} else {
s = f / g;
r = sqrtf(s * s + 1);
subd[i + 1] = g * r;
s *= (c = 1 / r);
}
g = diag[i + 1] - p;
r = (diag[i] - g) * s + 2 * b * c;
p = s * r;
diag[i + 1] = g + p;
g = c * r - b;
for (int k = 0; k < 3; k++) {
f = mat[k][i + 1];
mat[k][i + 1] = s * mat[k][i] + c * f;
mat[k][i] = c * mat[k][i] - s * f;
}
}
diag[ell] -= p;
subd[ell] = g;
subd[m] = 0;
}
if (iter == maxiter)
// should not get here under normal circumstances
return false;
}
return true;
}
};
/// Fixed size vector class.
class FullVector {
public:
FullVector(uint32_t dim) { m_array.resize(dim); }
FullVector(const FullVector &v) :
m_array(v.m_array) {}
const FullVector &operator=(const FullVector &v) {
xaAssert(dimension() == v.dimension());
m_array = v.m_array;
return *this;
}
uint32_t dimension() const { return m_array.size(); }
const float &operator[](uint32_t index) const { return m_array[index]; }
float &operator[](uint32_t index) { return m_array[index]; }
void fill(float f) {
const uint32_t dim = dimension();
for (uint32_t i = 0; i < dim; i++) {
m_array[i] = f;
}
}
void operator+=(const FullVector &v) {
xaDebugAssert(dimension() == v.dimension());
const uint32_t dim = dimension();
for (uint32_t i = 0; i < dim; i++) {
m_array[i] += v.m_array[i];
}
}
void operator-=(const FullVector &v) {
xaDebugAssert(dimension() == v.dimension());
const uint32_t dim = dimension();
for (uint32_t i = 0; i < dim; i++) {
m_array[i] -= v.m_array[i];
}
}
void operator*=(const FullVector &v) {
xaDebugAssert(dimension() == v.dimension());
const uint32_t dim = dimension();
for (uint32_t i = 0; i < dim; i++) {
m_array[i] *= v.m_array[i];
}
}
void operator+=(float f) {
const uint32_t dim = dimension();
for (uint32_t i = 0; i < dim; i++) {
m_array[i] += f;
}
}
void operator-=(float f) {
const uint32_t dim = dimension();
for (uint32_t i = 0; i < dim; i++) {
m_array[i] -= f;
}
}
void operator*=(float f) {
const uint32_t dim = dimension();
for (uint32_t i = 0; i < dim; i++) {
m_array[i] *= f;
}
}
private:
std::vector<float> m_array;
};
namespace halfedge {
class Face;
class Vertex;
class Edge {
public:
uint32_t id;
Edge *next;
Edge *prev; // This is not strictly half-edge, but makes algorithms easier and faster.
Edge *pair;
Vertex *vertex;
Face *face;
// Default constructor.
Edge(uint32_t id) :
id(id),
next(NULL),
prev(NULL),
pair(NULL),
vertex(NULL),
face(NULL) {}
// Vertex queries.
const Vertex *from() const {
return vertex;
}
Vertex *from() {
return vertex;
}
const Vertex *to() const {
return pair->vertex; // This used to be 'next->vertex', but that changed often when the connectivity of the mesh changes.
}
Vertex *to() {
return pair->vertex;
}
// Edge queries.
void setNext(Edge *e) {
next = e;
if (e != NULL) e->prev = this;
}
void setPrev(Edge *e) {
prev = e;
if (e != NULL) e->next = this;
}
// @@ It would be more simple to only check m_pair == NULL
// Face queries.
bool isBoundary() const {
return !(face && pair->face);
}
// @@ This is not exactly accurate, we should compare the texture coordinates...
bool isSeam() const {
return vertex != pair->next->vertex || next->vertex != pair->vertex;
}
bool isNormalSeam() const;
bool isTextureSeam() const;
bool isValid() const {
// null face is OK.
if (next == NULL || prev == NULL || pair == NULL || vertex == NULL) return false;
if (next->prev != this) return false;
if (prev->next != this) return false;
if (pair->pair != this) return false;
return true;
}
float length() const;
// Return angle between this edge and the previous one.
float angle() const;
};
class Vertex {
public:
uint32_t id;
uint32_t original_id;
Edge *edge;
Vertex *next;
Vertex *prev;
Vector3 pos;
Vector3 nor;
Vector2 tex;
Vertex(uint32_t id) :
id(id),
original_id(id),
edge(NULL),
pos(0.0f),
nor(0.0f),
tex(0.0f) {
next = this;
prev = this;
}
// Set first edge of all colocals.
void setEdge(Edge *e) {
for (VertexIterator it(colocals()); !it.isDone(); it.advance()) {
it.current()->edge = e;
}
}
// Update position of all colocals.
void setPos(const Vector3 &p) {
for (VertexIterator it(colocals()); !it.isDone(); it.advance()) {
it.current()->pos = p;
}
}
bool isFirstColocal() const {
return firstColocal() == this;
}
const Vertex *firstColocal() const {
uint32_t firstId = id;
const Vertex *vertex = this;
for (ConstVertexIterator it(colocals()); !it.isDone(); it.advance()) {
if (it.current()->id < firstId) {
firstId = vertex->id;
vertex = it.current();
}
}
return vertex;
}
Vertex *firstColocal() {
Vertex *vertex = this;
uint32_t firstId = id;
for (VertexIterator it(colocals()); !it.isDone(); it.advance()) {
if (it.current()->id < firstId) {
firstId = vertex->id;
vertex = it.current();
}
}
return vertex;
}
bool isColocal(const Vertex *v) const {
if (this == v) return true;
if (pos != v->pos) return false;
for (ConstVertexIterator it(colocals()); !it.isDone(); it.advance()) {
if (v == it.current()) {
return true;
}
}
return false;
}
void linkColocal(Vertex *v) {
next->prev = v;
v->next = next;
next = v;
v->prev = this;
}
void unlinkColocal() {
next->prev = prev;
prev->next = next;
next = this;
prev = this;
}
// @@ Note: This only works if linkBoundary has been called.
bool isBoundary() const {
return (edge && !edge->face);
}
// Iterator that visits the edges around this vertex in counterclockwise order.
class EdgeIterator //: public Iterator<Edge *>
{
public:
EdgeIterator(Edge *e) :
m_end(NULL),
m_current(e) {}
virtual void advance() {
if (m_end == NULL) m_end = m_current;
m_current = m_current->pair->next;
//m_current = m_current->prev->pair;
}
virtual bool isDone() const {
return m_end == m_current;
}
virtual Edge *current() const {
return m_current;
}
Vertex *vertex() const {
return m_current->vertex;
}
private:
Edge *m_end;
Edge *m_current;
};
EdgeIterator edges() {
return EdgeIterator(edge);
}
EdgeIterator edges(Edge *e) {
return EdgeIterator(e);
}
// Iterator that visits the edges around this vertex in counterclockwise order.
class ConstEdgeIterator //: public Iterator<Edge *>
{
public:
ConstEdgeIterator(const Edge *e) :
m_end(NULL),
m_current(e) {}
ConstEdgeIterator(EdgeIterator it) :
m_end(NULL),
m_current(it.current()) {}
virtual void advance() {
if (m_end == NULL) m_end = m_current;
m_current = m_current->pair->next;
//m_current = m_current->prev->pair;
}
virtual bool isDone() const {
return m_end == m_current;
}
virtual const Edge *current() const {
return m_current;
}
const Vertex *vertex() const {
return m_current->to();
}
private:
const Edge *m_end;
const Edge *m_current;
};
ConstEdgeIterator edges() const {
return ConstEdgeIterator(edge);
}
ConstEdgeIterator edges(const Edge *e) const {
return ConstEdgeIterator(e);
}
// Iterator that visits all the colocal vertices.
class VertexIterator //: public Iterator<Edge *>
{
public:
VertexIterator(Vertex *v) :
m_end(NULL),
m_current(v) {}
virtual void advance() {
if (m_end == NULL) m_end = m_current;
m_current = m_current->next;
}
virtual bool isDone() const {
return m_end == m_current;
}
virtual Vertex *current() const {
return m_current;
}
private:
Vertex *m_end;
Vertex *m_current;
};
VertexIterator colocals() {
return VertexIterator(this);
}
// Iterator that visits all the colocal vertices.
class ConstVertexIterator //: public Iterator<Edge *>
{
public:
ConstVertexIterator(const Vertex *v) :
m_end(NULL),
m_current(v) {}
virtual void advance() {
if (m_end == NULL) m_end = m_current;
m_current = m_current->next;
}
virtual bool isDone() const {
return m_end == m_current;
}
virtual const Vertex *current() const {
return m_current;
}
private:
const Vertex *m_end;
const Vertex *m_current;
};
ConstVertexIterator colocals() const {
return ConstVertexIterator(this);
}
};
bool Edge::isNormalSeam() const {
return (vertex->nor != pair->next->vertex->nor || next->vertex->nor != pair->vertex->nor);
}
bool Edge::isTextureSeam() const {
return (vertex->tex != pair->next->vertex->tex || next->vertex->tex != pair->vertex->tex);
}
float Edge::length() const {
return internal::length(to()->pos - from()->pos);
}
float Edge::angle() const {
Vector3 p = vertex->pos;
Vector3 a = prev->vertex->pos;
Vector3 b = next->vertex->pos;
Vector3 v0 = a - p;
Vector3 v1 = b - p;
return acosf(dot(v0, v1) / (internal::length(v0) * internal::length(v1)));
}
class Face {
public:
uint32_t id;
uint16_t group;
uint16_t material;
Edge *edge;
Face(uint32_t id) :
id(id),
group(uint16_t(~0)),
material(uint16_t(~0)),
edge(NULL) {}
float area() const {
float area = 0;
const Vector3 &v0 = edge->from()->pos;
for (ConstEdgeIterator it(edges(edge->next)); it.current() != edge->prev; it.advance()) {
const Edge *e = it.current();
const Vector3 &v1 = e->vertex->pos;
const Vector3 &v2 = e->next->vertex->pos;
area += length(cross(v1 - v0, v2 - v0));
}
return area * 0.5f;
}
float parametricArea() const {
float area = 0;
const Vector2 &v0 = edge->from()->tex;
for (ConstEdgeIterator it(edges(edge->next)); it.current() != edge->prev; it.advance()) {
const Edge *e = it.current();
const Vector2 &v1 = e->vertex->tex;
const Vector2 &v2 = e->next->vertex->tex;
area += triangleArea(v0, v1, v2);
}
return area * 0.5f;
}
Vector3 normal() const {
Vector3 n(0);
const Vertex *vertex0 = NULL;
for (ConstEdgeIterator it(edges()); !it.isDone(); it.advance()) {
const Edge *e = it.current();
xaAssert(e != NULL);
if (vertex0 == NULL) {
vertex0 = e->vertex;
} else if (e->next->vertex != vertex0) {
const halfedge::Vertex *vertex1 = e->from();
const halfedge::Vertex *vertex2 = e->to();
const Vector3 &p0 = vertex0->pos;
const Vector3 &p1 = vertex1->pos;
const Vector3 &p2 = vertex2->pos;
Vector3 v10 = p1 - p0;
Vector3 v20 = p2 - p0;
n += cross(v10, v20);
}
}
return normalizeSafe(n, Vector3(0, 0, 1), 0.0f);
}
Vector3 centroid() const {
Vector3 sum(0.0f);
uint32_t count = 0;
for (ConstEdgeIterator it(edges()); !it.isDone(); it.advance()) {
const Edge *e = it.current();
sum += e->from()->pos;
count++;
}
return sum / float(count);
}
// Unnormalized face normal assuming it's a triangle.
Vector3 triangleNormal() const {
Vector3 p0 = edge->vertex->pos;
Vector3 p1 = edge->next->vertex->pos;
Vector3 p2 = edge->next->next->vertex->pos;
Vector3 e0 = p2 - p0;
Vector3 e1 = p1 - p0;
return normalizeSafe(cross(e0, e1), Vector3(0), 0.0f);
}
Vector3 triangleNormalAreaScaled() const {
Vector3 p0 = edge->vertex->pos;
Vector3 p1 = edge->next->vertex->pos;
Vector3 p2 = edge->next->next->vertex->pos;
Vector3 e0 = p2 - p0;
Vector3 e1 = p1 - p0;
return cross(e0, e1);
}
// Average of the edge midpoints weighted by the edge length.
// I want a point inside the triangle, but closer to the cirumcenter.
Vector3 triangleCenter() const {
Vector3 p0 = edge->vertex->pos;
Vector3 p1 = edge->next->vertex->pos;
Vector3 p2 = edge->next->next->vertex->pos;
float l0 = length(p1 - p0);
float l1 = length(p2 - p1);
float l2 = length(p0 - p2);
Vector3 m0 = (p0 + p1) * l0 / (l0 + l1 + l2);
Vector3 m1 = (p1 + p2) * l1 / (l0 + l1 + l2);
Vector3 m2 = (p2 + p0) * l2 / (l0 + l1 + l2);
return m0 + m1 + m2;
}
bool isValid() const {
uint32_t count = 0;
for (ConstEdgeIterator it(edges()); !it.isDone(); it.advance()) {
const Edge *e = it.current();
if (e->face != this) return false;
if (!e->isValid()) return false;
if (!e->pair->isValid()) return false;
count++;
}
if (count < 3) return false;
return true;
}
bool contains(const Edge *e) const {
for (ConstEdgeIterator it(edges()); !it.isDone(); it.advance()) {
if (it.current() == e) return true;
}
return false;
}
uint32_t edgeCount() const {
uint32_t count = 0;
for (ConstEdgeIterator it(edges()); !it.isDone(); it.advance()) {
++count;
}
return count;
}
// The iterator that visits the edges of this face in clockwise order.
class EdgeIterator //: public Iterator<Edge *>
{
public:
EdgeIterator(Edge *e) :
m_end(NULL),
m_current(e) {}
virtual void advance() {
if (m_end == NULL) m_end = m_current;
m_current = m_current->next;
}
virtual bool isDone() const {
return m_end == m_current;
}
virtual Edge *current() const {
return m_current;
}
Vertex *vertex() const {
return m_current->vertex;
}
private:
Edge *m_end;
Edge *m_current;
};
EdgeIterator edges() {
return EdgeIterator(edge);
}
EdgeIterator edges(Edge *e) {
xaDebugAssert(contains(e));
return EdgeIterator(e);
}
// The iterator that visits the edges of this face in clockwise order.
class ConstEdgeIterator //: public Iterator<const Edge *>
{
public:
ConstEdgeIterator(const Edge *e) :
m_end(NULL),
m_current(e) {}
ConstEdgeIterator(const EdgeIterator &it) :
m_end(NULL),
m_current(it.current()) {}
virtual void advance() {
if (m_end == NULL) m_end = m_current;
m_current = m_current->next;
}
virtual bool isDone() const {
return m_end == m_current;
}
virtual const Edge *current() const {
return m_current;
}
const Vertex *vertex() const {
return m_current->vertex;
}
private:
const Edge *m_end;
const Edge *m_current;
};
ConstEdgeIterator edges() const {
return ConstEdgeIterator(edge);
}
ConstEdgeIterator edges(const Edge *e) const {
xaDebugAssert(contains(e));
return ConstEdgeIterator(e);
}
};
/// Simple half edge mesh designed for dynamic mesh manipulation.
class Mesh {
public:
Mesh() :
m_colocalVertexCount(0) {}
Mesh(const Mesh *mesh) {
// Copy mesh vertices.
const uint32_t vertexCount = mesh->vertexCount();
m_vertexArray.resize(vertexCount);
for (uint32_t v = 0; v < vertexCount; v++) {
const Vertex *vertex = mesh->vertexAt(v);
xaDebugAssert(vertex->id == v);
m_vertexArray[v] = new Vertex(v);
m_vertexArray[v]->pos = vertex->pos;
m_vertexArray[v]->nor = vertex->nor;
m_vertexArray[v]->tex = vertex->tex;
}
m_colocalVertexCount = vertexCount;
// Copy mesh faces.
const uint32_t faceCount = mesh->faceCount();
std::vector<uint32_t> indexArray;
indexArray.reserve(3);
for (uint32_t f = 0; f < faceCount; f++) {
const Face *face = mesh->faceAt(f);
for (Face::ConstEdgeIterator it(face->edges()); !it.isDone(); it.advance()) {
const Vertex *vertex = it.current()->from();
indexArray.push_back(vertex->id);
}
addFace(indexArray);
indexArray.clear();
}
}
~Mesh() {
clear();
}
void clear() {
for (size_t i = 0; i < m_vertexArray.size(); i++)
delete m_vertexArray[i];
m_vertexArray.clear();
for (auto it = m_edgeMap.begin(); it != m_edgeMap.end(); it++)
delete it->second;
m_edgeArray.clear();
m_edgeMap.clear();
for (size_t i = 0; i < m_faceArray.size(); i++)
delete m_faceArray[i];
m_faceArray.clear();
}
Vertex *addVertex(const Vector3 &pos) {
xaDebugAssert(isFinite(pos));
Vertex *v = new Vertex(m_vertexArray.size());
v->pos = pos;
m_vertexArray.push_back(v);
return v;
}
/// Link colocal vertices based on geometric location only.
void linkColocals() {
xaPrint("--- Linking colocals:\n");
const uint32_t vertexCount = this->vertexCount();
std::unordered_map<Vector3, Vertex *, Hash<Vector3>, Equal<Vector3> > vertexMap;
vertexMap.reserve(vertexCount);
for (uint32_t v = 0; v < vertexCount; v++) {
Vertex *vertex = vertexAt(v);
Vertex *colocal = vertexMap[vertex->pos];
if (colocal) {
colocal->linkColocal(vertex);
} else {
vertexMap[vertex->pos] = vertex;
}
}
m_colocalVertexCount = vertexMap.size();
xaPrint("--- %d vertex positions.\n", m_colocalVertexCount);
// @@ Remove duplicated vertices? or just leave them as colocals?
}
void linkColocalsWithCanonicalMap(const std::vector<uint32_t> &canonicalMap) {
xaPrint("--- Linking colocals:\n");
uint32_t vertexMapSize = 0;
for (uint32_t i = 0; i < canonicalMap.size(); i++) {
vertexMapSize = std::max(vertexMapSize, canonicalMap[i] + 1);
}
std::vector<Vertex *> vertexMap;
vertexMap.resize(vertexMapSize, NULL);
m_colocalVertexCount = 0;
const uint32_t vertexCount = this->vertexCount();
for (uint32_t v = 0; v < vertexCount; v++) {
Vertex *vertex = vertexAt(v);
Vertex *colocal = vertexMap[canonicalMap[v]];
if (colocal != NULL) {
xaDebugAssert(vertex->pos == colocal->pos);
colocal->linkColocal(vertex);
} else {
vertexMap[canonicalMap[v]] = vertex;
m_colocalVertexCount++;
}
}
xaPrint("--- %d vertex positions.\n", m_colocalVertexCount);
}
Face *addFace() {
Face *f = new Face(m_faceArray.size());
m_faceArray.push_back(f);
return f;
}
Face *addFace(uint32_t v0, uint32_t v1, uint32_t v2) {
uint32_t indexArray[3];
indexArray[0] = v0;
indexArray[1] = v1;
indexArray[2] = v2;
return addFace(indexArray, 3, 0, 3);
}
Face *addUniqueFace(uint32_t v0, uint32_t v1, uint32_t v2) {
int base_vertex = m_vertexArray.size();
uint32_t ids[3] = { v0, v1, v2 };
Vector3 base[3] = {
m_vertexArray[v0]->pos,
m_vertexArray[v1]->pos,
m_vertexArray[v2]->pos,
};
//make sure its not a degenerate
bool degenerate = distanceSquared(base[0], base[1]) < NV_EPSILON || distanceSquared(base[0], base[2]) < NV_EPSILON || distanceSquared(base[1], base[2]) < NV_EPSILON;
xaDebugAssert(!degenerate);
float min_x = 0;
for (int i = 0; i < 3; i++) {
if (i == 0 || m_vertexArray[v0]->pos.x < min_x) {
min_x = m_vertexArray[v0]->pos.x;
}
}
float max_x = 0;
for (int j = 0; j < m_vertexArray.size(); j++) {
if (j == 0 || m_vertexArray[j]->pos.x > max_x) { //vertex already exists
max_x = m_vertexArray[j]->pos.x;
}
}
//separate from everything else, in x axis
for (int i = 0; i < 3; i++) {
base[i].x -= min_x;
base[i].x += max_x + 10.0;
}
for (int i = 0; i < 3; i++) {
Vertex *v = new Vertex(m_vertexArray.size());
v->pos = base[i];
v->nor = m_vertexArray[ids[i]]->nor,
v->tex = m_vertexArray[ids[i]]->tex,
v->original_id = ids[i];
m_vertexArray.push_back(v);
}
uint32_t indexArray[3];
indexArray[0] = base_vertex + 0;
indexArray[1] = base_vertex + 1;
indexArray[2] = base_vertex + 2;
return addFace(indexArray, 3, 0, 3);
}
Face *addFace(uint32_t v0, uint32_t v1, uint32_t v2, uint32_t v3) {
uint32_t indexArray[4];
indexArray[0] = v0;
indexArray[1] = v1;
indexArray[2] = v2;
indexArray[3] = v3;
return addFace(indexArray, 4, 0, 4);
}
Face *addFace(const std::vector<uint32_t> &indexArray) {
return addFace(indexArray, 0, indexArray.size());
}
Face *addFace(const std::vector<uint32_t> &indexArray, uint32_t first, uint32_t num) {
return addFace(indexArray.data(), (uint32_t)indexArray.size(), first, num);
}
Face *addFace(const uint32_t *indexArray, uint32_t indexCount, uint32_t first, uint32_t num) {
xaDebugAssert(first < indexCount);
xaDebugAssert(num <= indexCount - first);
xaDebugAssert(num > 2);
if (!canAddFace(indexArray, first, num)) {
return NULL;
}
Face *f = new Face(m_faceArray.size());
Edge *firstEdge = NULL;
Edge *last = NULL;
Edge *current = NULL;
for (uint32_t i = 0; i < num - 1; i++) {
current = addEdge(indexArray[first + i], indexArray[first + i + 1]);
xaAssert(current != NULL && current->face == NULL);
current->face = f;
if (last != NULL)
last->setNext(current);
else
firstEdge = current;
last = current;
}
current = addEdge(indexArray[first + num - 1], indexArray[first]);
xaAssert(current != NULL && current->face == NULL);
current->face = f;
last->setNext(current);
current->setNext(firstEdge);
f->edge = firstEdge;
m_faceArray.push_back(f);
return f;
}
// These functions disconnect the given element from the mesh and delete it.
// @@ We must always disconnect edge pairs simultaneously.
void disconnect(Edge *edge) {
xaDebugAssert(edge != NULL);
// Remove from edge list.
if ((edge->id & 1) == 0) {
xaDebugAssert(m_edgeArray[edge->id / 2] == edge);
m_edgeArray[edge->id / 2] = NULL;
}
// Remove edge from map. @@ Store map key inside edge?
xaDebugAssert(edge->from() != NULL && edge->to() != NULL);
size_t removed = m_edgeMap.erase(Key(edge->from()->id, edge->to()->id));
xaDebugAssert(removed == 1);
#ifdef NDEBUG
removed = 0; // silence unused parameter warning
#endif
// Disconnect from vertex.
if (edge->vertex != NULL) {
if (edge->vertex->edge == edge) {
if (edge->prev && edge->prev->pair) {
edge->vertex->edge = edge->prev->pair;
} else if (edge->pair && edge->pair->next) {
edge->vertex->edge = edge->pair->next;
} else {
edge->vertex->edge = NULL;
// @@ Remove disconnected vertex?
}
}
}
// Disconnect from face.
if (edge->face != NULL) {
if (edge->face->edge == edge) {
if (edge->next != NULL && edge->next != edge) {
edge->face->edge = edge->next;
} else if (edge->prev != NULL && edge->prev != edge) {
edge->face->edge = edge->prev;
} else {
edge->face->edge = NULL;
// @@ Remove disconnected face?
}
}
}
// Disconnect from previous.
if (edge->prev) {
if (edge->prev->next == edge) {
edge->prev->setNext(NULL);
}
//edge->setPrev(NULL);
}
// Disconnect from next.
if (edge->next) {
if (edge->next->prev == edge) {
edge->next->setPrev(NULL);
}
//edge->setNext(NULL);
}
}
void remove(Edge *edge) {
xaDebugAssert(edge != NULL);
disconnect(edge);
delete edge;
}
void remove(Vertex *vertex) {
xaDebugAssert(vertex != NULL);
// Remove from vertex list.
m_vertexArray[vertex->id] = NULL;
// Disconnect from colocals.
vertex->unlinkColocal();
// Disconnect from edges.
if (vertex->edge != NULL) {
// @@ Removing a connected vertex is asking for trouble...
if (vertex->edge->vertex == vertex) {
// @@ Connect edge to a colocal?
vertex->edge->vertex = NULL;
}
vertex->setEdge(NULL);
}
delete vertex;
}
void remove(Face *face) {
xaDebugAssert(face != NULL);
// Remove from face list.
m_faceArray[face->id] = NULL;
// Disconnect from edges.
if (face->edge != NULL) {
xaDebugAssert(face->edge->face == face);
face->edge->face = NULL;
face->edge = NULL;
}
delete face;
}
// Triangulate in place.
void triangulate() {
bool all_triangles = true;
const uint32_t faceCount = m_faceArray.size();
for (uint32_t f = 0; f < faceCount; f++) {
Face *face = m_faceArray[f];
if (face->edgeCount() != 3) {
all_triangles = false;
break;
}
}
if (all_triangles) {
return;
}
// Do not touch vertices, but rebuild edges and faces.
std::vector<Edge *> edgeArray;
std::vector<Face *> faceArray;
std::swap(edgeArray, m_edgeArray);
std::swap(faceArray, m_faceArray);
m_edgeMap.clear();
for (uint32_t f = 0; f < faceCount; f++) {
Face *face = faceArray[f];
// Trivial fan-like triangulation.
const uint32_t v0 = face->edge->vertex->id;
uint32_t v2, v1 = (uint32_t)-1;
for (Face::EdgeIterator it(face->edges()); !it.isDone(); it.advance()) {
Edge *edge = it.current();
v2 = edge->to()->id;
if (v2 == v0) break;
if (v1 != -1) addFace(v0, v1, v2);
v1 = v2;
}
}
xaDebugAssert(m_faceArray.size() > faceCount); // triangle count > face count
linkBoundary();
for (size_t i = 0; i < edgeArray.size(); i++)
delete edgeArray[i];
for (size_t i = 0; i < faceArray.size(); i++)
delete faceArray[i];
}
/// Link boundary edges once the mesh has been created.
void linkBoundary() {
xaPrint("--- Linking boundaries:\n");
int num = 0;
// Create boundary edges.
uint32_t edgeCount = this->edgeCount();
for (uint32_t e = 0; e < edgeCount; e++) {
Edge *edge = edgeAt(e);
if (edge != NULL && edge->pair == NULL) {
Edge *pair = new Edge(edge->id + 1);
uint32_t i = edge->from()->id;
uint32_t j = edge->next->from()->id;
Key key(j, i);
xaAssert(m_edgeMap.find(key) == m_edgeMap.end());
pair->vertex = m_vertexArray[j];
m_edgeMap[key] = pair;
edge->pair = pair;
pair->pair = edge;
num++;
}
}
// Link boundary edges.
for (uint32_t e = 0; e < edgeCount; e++) {
Edge *edge = edgeAt(e);
if (edge != NULL && edge->pair->face == NULL) {
linkBoundaryEdge(edge->pair);
}
}
xaPrint("--- %d boundary edges.\n", num);
}
/*
Fixing T-junctions.
- Find T-junctions. Find vertices that are on an edge.
- This test is approximate.
- Insert edges on a spatial index to speedup queries.
- Consider only open edges, that is edges that have no pairs.
- Consider only vertices on boundaries.
- Close T-junction.
- Split edge.
*/
bool splitBoundaryEdges() // Returns true if any split was made.
{
std::vector<Vertex *> boundaryVertices;
for (uint32_t i = 0; i < m_vertexArray.size(); i++) {
Vertex *v = m_vertexArray[i];
if (v->isBoundary()) {
boundaryVertices.push_back(v);
}
}
xaPrint("Fixing T-junctions:\n");
int splitCount = 0;
for (uint32_t v = 0; v < boundaryVertices.size(); v++) {
Vertex *vertex = boundaryVertices[v];
Vector3 x0 = vertex->pos;
// Find edges that this vertex overlaps with.
for (uint32_t e = 0; e < m_edgeArray.size(); e++) {
Edge *edge = m_edgeArray[e];
if (edge != NULL && edge->isBoundary()) {
if (edge->from() == vertex || edge->to() == vertex) {
continue;
}
Vector3 x1 = edge->from()->pos;
Vector3 x2 = edge->to()->pos;
Vector3 v01 = x0 - x1;
Vector3 v21 = x2 - x1;
float l = length(v21);
float d = length(cross(v01, v21)) / l;
if (isZero(d)) {
float t = dot(v01, v21) / (l * l);
if (t > 0.0f + NV_EPSILON && t < 1.0f - NV_EPSILON) {
xaDebugAssert(equal(lerp(x1, x2, t), x0));
Vertex *splitVertex = splitBoundaryEdge(edge, t, x0);
vertex->linkColocal(splitVertex); // @@ Should we do this here?
splitCount++;
}
}
}
}
}
xaPrint(" - %d edges split.\n", splitCount);
xaDebugAssert(isValid());
return splitCount != 0;
}
// Vertices
uint32_t vertexCount() const {
return m_vertexArray.size();
}
const Vertex *vertexAt(int i) const {
return m_vertexArray[i];
}
Vertex *vertexAt(int i) {
return m_vertexArray[i];
}
uint32_t colocalVertexCount() const {
return m_colocalVertexCount;
}
// Faces
uint32_t faceCount() const {
return m_faceArray.size();
}
const Face *faceAt(int i) const {
return m_faceArray[i];
}
Face *faceAt(int i) {
return m_faceArray[i];
}
// Edges
uint32_t edgeCount() const {
return m_edgeArray.size();
}
const Edge *edgeAt(int i) const {
return m_edgeArray[i];
}
Edge *edgeAt(int i) {
return m_edgeArray[i];
}
class ConstVertexIterator;
class VertexIterator {
friend class ConstVertexIterator;
public:
VertexIterator(Mesh *mesh) :
m_mesh(mesh),
m_current(0) {}
virtual void advance() {
m_current++;
}
virtual bool isDone() const {
return m_current == m_mesh->vertexCount();
}
virtual Vertex *current() const {
return m_mesh->vertexAt(m_current);
}
private:
halfedge::Mesh *m_mesh;
uint32_t m_current;
};
VertexIterator vertices() {
return VertexIterator(this);
}
class ConstVertexIterator {
public:
ConstVertexIterator(const Mesh *mesh) :
m_mesh(mesh),
m_current(0) {}
ConstVertexIterator(class VertexIterator &it) :
m_mesh(it.m_mesh),
m_current(it.m_current) {}
virtual void advance() {
m_current++;
}
virtual bool isDone() const {
return m_current == m_mesh->vertexCount();
}
virtual const Vertex *current() const {
return m_mesh->vertexAt(m_current);
}
private:
const halfedge::Mesh *m_mesh;
uint32_t m_current;
};
ConstVertexIterator vertices() const {
return ConstVertexIterator(this);
}
class ConstFaceIterator;
class FaceIterator {
friend class ConstFaceIterator;
public:
FaceIterator(Mesh *mesh) :
m_mesh(mesh),
m_current(0) {}
virtual void advance() {
m_current++;
}
virtual bool isDone() const {
return m_current == m_mesh->faceCount();
}
virtual Face *current() const {
return m_mesh->faceAt(m_current);
}
private:
halfedge::Mesh *m_mesh;
uint32_t m_current;
};
FaceIterator faces() {
return FaceIterator(this);
}
class ConstFaceIterator {
public:
ConstFaceIterator(const Mesh *mesh) :
m_mesh(mesh),
m_current(0) {}
ConstFaceIterator(const FaceIterator &it) :
m_mesh(it.m_mesh),
m_current(it.m_current) {}
virtual void advance() {
m_current++;
}
virtual bool isDone() const {
return m_current == m_mesh->faceCount();
}
virtual const Face *current() const {
return m_mesh->faceAt(m_current);
}
private:
const halfedge::Mesh *m_mesh;
uint32_t m_current;
};
ConstFaceIterator faces() const {
return ConstFaceIterator(this);
}
class ConstEdgeIterator;
class EdgeIterator {
friend class ConstEdgeIterator;
public:
EdgeIterator(Mesh *mesh) :
m_mesh(mesh),
m_current(0) {}
virtual void advance() {
m_current++;
}
virtual bool isDone() const {
return m_current == m_mesh->edgeCount();
}
virtual Edge *current() const {
return m_mesh->edgeAt(m_current);
}
private:
halfedge::Mesh *m_mesh;
uint32_t m_current;
};
EdgeIterator edges() {
return EdgeIterator(this);
}
class ConstEdgeIterator {
public:
ConstEdgeIterator(const Mesh *mesh) :
m_mesh(mesh),
m_current(0) {}
ConstEdgeIterator(const EdgeIterator &it) :
m_mesh(it.m_mesh),
m_current(it.m_current) {}
virtual void advance() {
m_current++;
}
virtual bool isDone() const {
return m_current == m_mesh->edgeCount();
}
virtual const Edge *current() const {
return m_mesh->edgeAt(m_current);
}
private:
const halfedge::Mesh *m_mesh;
uint32_t m_current;
};
ConstEdgeIterator edges() const {
return ConstEdgeIterator(this);
}
// @@ Add half-edge iterator.
bool isValid() const {
// Make sure all edges are valid.
const uint32_t edgeCount = m_edgeArray.size();
for (uint32_t e = 0; e < edgeCount; e++) {
Edge *edge = m_edgeArray[e];
if (edge != NULL) {
if (edge->id != 2 * e) {
return false;
}
if (!edge->isValid()) {
return false;
}
if (edge->pair->id != 2 * e + 1) {
return false;
}
if (!edge->pair->isValid()) {
return false;
}
}
}
// @@ Make sure all faces are valid.
// @@ Make sure all vertices are valid.
return true;
}
// Error status:
struct ErrorCode {
enum Enum {
AlreadyAddedEdge,
DegenerateColocalEdge,
DegenerateEdge,
DuplicateEdge
};
};
mutable ErrorCode::Enum errorCode;
mutable uint32_t errorIndex0;
mutable uint32_t errorIndex1;
private:
// Return true if the face can be added to the manifold mesh.
bool canAddFace(const std::vector<uint32_t> &indexArray, uint32_t first, uint32_t num) const {
return canAddFace(indexArray.data(), first, num);
}
bool canAddFace(const uint32_t *indexArray, uint32_t first, uint32_t num) const {
for (uint32_t j = num - 1, i = 0; i < num; j = i++) {
if (!canAddEdge(indexArray[first + j], indexArray[first + i])) {
errorIndex0 = indexArray[first + j];
errorIndex1 = indexArray[first + i];
return false;
}
}
// We also have to make sure the face does not have any duplicate edge!
for (uint32_t i = 0; i < num; i++) {
int i0 = indexArray[first + i + 0];
int i1 = indexArray[first + (i + 1) % num];
for (uint32_t j = i + 1; j < num; j++) {
int j0 = indexArray[first + j + 0];
int j1 = indexArray[first + (j + 1) % num];
if (i0 == j0 && i1 == j1) {
errorCode = ErrorCode::DuplicateEdge;
errorIndex0 = i0;
errorIndex1 = i1;
return false;
}
}
}
return true;
}
// Return true if the edge doesn't exist or doesn't have any adjacent face.
bool canAddEdge(uint32_t i, uint32_t j) const {
if (i == j) {
// Skip degenerate edges.
errorCode = ErrorCode::DegenerateEdge;
return false;
}
// Same check, but taking into account colocal vertices.
const Vertex *v0 = vertexAt(i);
const Vertex *v1 = vertexAt(j);
for (Vertex::ConstVertexIterator it(v0->colocals()); !it.isDone(); it.advance()) {
if (it.current() == v1) {
// Skip degenerate edges.
errorCode = ErrorCode::DegenerateColocalEdge;
return false;
}
}
// Make sure edge has not been added yet.
Edge *edge = findEdge(i, j);
// We ignore edges that don't have an adjacent face yet, since this face could become the edge's face.
if (!(edge == NULL || edge->face == NULL)) {
errorCode = ErrorCode::AlreadyAddedEdge;
return false;
}
return true;
}
Edge *addEdge(uint32_t i, uint32_t j) {
xaAssert(i != j);
Edge *edge = findEdge(i, j);
if (edge != NULL) {
// Edge may already exist, but its face must not be set.
xaDebugAssert(edge->face == NULL);
// Nothing else to do!
} else {
// Add new edge.
// Lookup pair.
Edge *pair = findEdge(j, i);
if (pair != NULL) {
// Create edge with same id.
edge = new Edge(pair->id + 1);
// Link edge pairs.
edge->pair = pair;
pair->pair = edge;
// @@ I'm not sure this is necessary!
pair->vertex->setEdge(pair);
} else {
// Create edge.
edge = new Edge(2 * m_edgeArray.size());
// Add only unpaired edges.
m_edgeArray.push_back(edge);
}
edge->vertex = m_vertexArray[i];
m_edgeMap[Key(i, j)] = edge;
}
// Face and Next are set by addFace.
return edge;
}
/// Find edge, test all colocals.
Edge *findEdge(uint32_t i, uint32_t j) const {
Edge *edge = NULL;
const Vertex *v0 = vertexAt(i);
const Vertex *v1 = vertexAt(j);
// Test all colocal pairs.
for (Vertex::ConstVertexIterator it0(v0->colocals()); !it0.isDone(); it0.advance()) {
for (Vertex::ConstVertexIterator it1(v1->colocals()); !it1.isDone(); it1.advance()) {
Key key(it0.current()->id, it1.current()->id);
if (edge == NULL) {
auto edgeIt = m_edgeMap.find(key);
if (edgeIt != m_edgeMap.end())
edge = (*edgeIt).second;
#if !defined(_DEBUG)
if (edge != NULL) return edge;
#endif
} else {
// Make sure that only one edge is found.
xaDebugAssert(m_edgeMap.find(key) == m_edgeMap.end());
}
}
}
return edge;
}
/// Link this boundary edge.
void linkBoundaryEdge(Edge *edge) {
xaAssert(edge->face == NULL);
// Make sure next pointer has not been set. @@ We want to be able to relink boundary edges after mesh changes.
Edge *next = edge;
while (next->pair->face != NULL) {
// Get pair prev
Edge *e = next->pair->next;
while (e->next != next->pair) {
e = e->next;
}
next = e;
}
edge->setNext(next->pair);
// Adjust vertex edge, so that it's the boundary edge. (required for isBoundary())
if (edge->vertex->edge != edge) {
// Multiple boundaries in the same edge.
edge->vertex->edge = edge;
}
}
Vertex *splitBoundaryEdge(Edge *edge, float t, const Vector3 &pos) {
/*
We want to go from this configuration:
+ +
| ^
edge |<->| pair
v |
+ +
To this one:
+ +
| ^
e0 |<->| p0
v |
vertex + +
| ^
e1 |<->| p1
v |
+ +
*/
Edge *pair = edge->pair;
// Make sure boundaries are linked.
xaDebugAssert(pair != NULL);
// Make sure edge is a boundary edge.
xaDebugAssert(pair->face == NULL);
// Add new vertex.
Vertex *vertex = addVertex(pos);
vertex->nor = lerp(edge->from()->nor, edge->to()->nor, t);
vertex->tex = lerp(edge->from()->tex, edge->to()->tex, t);
disconnect(edge);
disconnect(pair);
// Add edges.
Edge *e0 = addEdge(edge->from()->id, vertex->id);
Edge *p0 = addEdge(vertex->id, pair->to()->id);
Edge *e1 = addEdge(vertex->id, edge->to()->id);
Edge *p1 = addEdge(pair->from()->id, vertex->id);
// Link edges.
e0->setNext(e1);
p1->setNext(p0);
e0->setPrev(edge->prev);
e1->setNext(edge->next);
p1->setPrev(pair->prev);
p0->setNext(pair->next);
xaDebugAssert(e0->next == e1);
xaDebugAssert(e1->prev == e0);
xaDebugAssert(p1->next == p0);
xaDebugAssert(p0->prev == p1);
xaDebugAssert(p0->pair == e0);
xaDebugAssert(e0->pair == p0);
xaDebugAssert(p1->pair == e1);
xaDebugAssert(e1->pair == p1);
// Link faces.
e0->face = edge->face;
e1->face = edge->face;
// Link vertices.
edge->from()->setEdge(e0);
vertex->setEdge(e1);
delete edge;
delete pair;
return vertex;
}
private:
std::vector<Vertex *> m_vertexArray;
std::vector<Edge *> m_edgeArray;
std::vector<Face *> m_faceArray;
struct Key {
Key() {}
Key(const Key &k) :
p0(k.p0),
p1(k.p1) {}
Key(uint32_t v0, uint32_t v1) :
p0(v0),
p1(v1) {}
void operator=(const Key &k) {
p0 = k.p0;
p1 = k.p1;
}
bool operator==(const Key &k) const {
return p0 == k.p0 && p1 == k.p1;
}
uint32_t p0;
uint32_t p1;
};
friend struct Hash<Mesh::Key>;
std::unordered_map<Key, Edge *, Hash<Key>, Equal<Key> > m_edgeMap;
uint32_t m_colocalVertexCount;
};
class MeshTopology {
public:
MeshTopology(const Mesh *mesh) {
buildTopologyInfo(mesh);
}
/// Determine if the mesh is connected.
bool isConnected() const {
return m_connectedCount == 1;
}
/// Determine if the mesh is closed. (Each edge is shared by two faces)
bool isClosed() const {
return m_boundaryCount == 0;
}
/// Return true if the mesh has the topology of a disk.
bool isDisk() const {
return isConnected() && m_boundaryCount == 1 /* && m_eulerNumber == 1*/;
}
private:
void buildTopologyInfo(const Mesh *mesh) {
const uint32_t vertexCount = mesh->colocalVertexCount();
const uint32_t faceCount = mesh->faceCount();
const uint32_t edgeCount = mesh->edgeCount();
xaPrint("--- Building mesh topology:\n");
std::vector<uint32_t> stack(faceCount);
BitArray bitFlags(faceCount);
bitFlags.clearAll();
// Compute connectivity.
xaPrint("--- Computing connectivity.\n");
m_connectedCount = 0;
for (uint32_t f = 0; f < faceCount; f++) {
if (bitFlags.bitAt(f) == false) {
m_connectedCount++;
stack.push_back(f);
while (!stack.empty()) {
const uint32_t top = stack.back();
xaAssert(top != uint32_t(~0));
stack.pop_back();
if (bitFlags.bitAt(top) == false) {
bitFlags.setBitAt(top);
const Face *face = mesh->faceAt(top);
const Edge *firstEdge = face->edge;
const Edge *edge = firstEdge;
do {
const Face *neighborFace = edge->pair->face;
if (neighborFace != NULL) {
stack.push_back(neighborFace->id);
}
edge = edge->next;
} while (edge != firstEdge);
}
}
}
}
xaAssert(stack.empty());
xaPrint("--- %d connected components.\n", m_connectedCount);
// Count boundary loops.
xaPrint("--- Counting boundary loops.\n");
m_boundaryCount = 0;
bitFlags.resize(edgeCount);
bitFlags.clearAll();
// Don't forget to link the boundary otherwise this won't work.
for (uint32_t e = 0; e < edgeCount; e++) {
const Edge *startEdge = mesh->edgeAt(e);
if (startEdge != NULL && startEdge->isBoundary() && bitFlags.bitAt(e) == false) {
xaDebugAssert(startEdge->face != NULL);
xaDebugAssert(startEdge->pair->face == NULL);
startEdge = startEdge->pair;
m_boundaryCount++;
const Edge *edge = startEdge;
do {
bitFlags.setBitAt(edge->id / 2);
edge = edge->next;
} while (startEdge != edge);
}
}
xaPrint("--- %d boundary loops found.\n", m_boundaryCount);
// Compute euler number.
m_eulerNumber = vertexCount - edgeCount + faceCount;
xaPrint("--- Euler number: %d.\n", m_eulerNumber);
// Compute genus. (only valid on closed connected surfaces)
m_genus = -1;
if (isClosed() && isConnected()) {
m_genus = (2 - m_eulerNumber) / 2;
xaPrint("--- Genus: %d.\n", m_genus);
}
}
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;
};
float computeSurfaceArea(const halfedge::Mesh *mesh) {
float area = 0;
for (halfedge::Mesh::ConstFaceIterator it(mesh->faces()); !it.isDone(); it.advance()) {
const halfedge::Face *face = it.current();
area += face->area();
}
xaDebugAssert(area >= 0);
return area;
}
float computeParametricArea(const halfedge::Mesh *mesh) {
float area = 0;
for (halfedge::Mesh::ConstFaceIterator it(mesh->faces()); !it.isDone(); it.advance()) {
const halfedge::Face *face = it.current();
area += face->parametricArea();
}
return area;
}
uint32_t countMeshTriangles(const Mesh *mesh) {
const uint32_t faceCount = mesh->faceCount();
uint32_t triangleCount = 0;
for (uint32_t f = 0; f < faceCount; f++) {
const Face *face = mesh->faceAt(f);
uint32_t edgeCount = face->edgeCount();
xaDebugAssert(edgeCount > 2);
triangleCount += edgeCount - 2;
}
return triangleCount;
}
Mesh *unifyVertices(const Mesh *inputMesh) {
Mesh *mesh = new Mesh;
// Only add the first colocal.
const uint32_t vertexCount = inputMesh->vertexCount();
for (uint32_t v = 0; v < vertexCount; v++) {
const Vertex *vertex = inputMesh->vertexAt(v);
if (vertex->isFirstColocal()) {
mesh->addVertex(vertex->pos);
}
}
std::vector<uint32_t> indexArray;
// Add new faces pointing to first colocals.
uint32_t faceCount = inputMesh->faceCount();
for (uint32_t f = 0; f < faceCount; f++) {
const Face *face = inputMesh->faceAt(f);
indexArray.clear();
for (Face::ConstEdgeIterator it(face->edges()); !it.isDone(); it.advance()) {
const Edge *edge = it.current();
const Vertex *vertex = edge->vertex->firstColocal();
indexArray.push_back(vertex->id);
}
mesh->addFace(indexArray);
}
mesh->linkBoundary();
return mesh;
}
static bool pointInTriangle(const Vector2 &p, const Vector2 &a, const Vector2 &b, const Vector2 &c) {
return triangleArea(a, b, p) >= 0.00001f &&
triangleArea(b, c, p) >= 0.00001f &&
triangleArea(c, a, p) >= 0.00001f;
}
// This is doing a simple ear-clipping algorithm that skips invalid triangles. Ideally, we should
// also sort the ears by angle, start with the ones that have the smallest angle and proceed in order.
Mesh *triangulate(const Mesh *inputMesh) {
Mesh *mesh = new Mesh;
// Add all vertices.
const uint32_t vertexCount = inputMesh->vertexCount();
for (uint32_t v = 0; v < vertexCount; v++) {
const Vertex *vertex = inputMesh->vertexAt(v);
mesh->addVertex(vertex->pos);
}
std::vector<int> polygonVertices;
std::vector<float> polygonAngles;
std::vector<Vector2> polygonPoints;
const uint32_t faceCount = inputMesh->faceCount();
for (uint32_t f = 0; f < faceCount; f++) {
const Face *face = inputMesh->faceAt(f);
xaDebugAssert(face != NULL);
const uint32_t edgeCount = face->edgeCount();
xaDebugAssert(edgeCount >= 3);
polygonVertices.clear();
polygonVertices.reserve(edgeCount);
if (edgeCount == 3) {
// Simple case for triangles.
for (Face::ConstEdgeIterator it(face->edges()); !it.isDone(); it.advance()) {
const Edge *edge = it.current();
const Vertex *vertex = edge->vertex;
polygonVertices.push_back(vertex->id);
}
int v0 = polygonVertices[0];
int v1 = polygonVertices[1];
int v2 = polygonVertices[2];
mesh->addFace(v0, v1, v2);
} else {
// Build 2D polygon projecting vertices onto normal plane.
// Faces are not necesarily planar, this is for example the case, when the face comes from filling a hole. In such cases
// it's much better to use the best fit plane.
const Vector3 fn = face->normal();
Basis basis;
basis.buildFrameForDirection(fn);
polygonPoints.clear();
polygonPoints.reserve(edgeCount);
polygonAngles.clear();
polygonAngles.reserve(edgeCount);
for (Face::ConstEdgeIterator it(face->edges()); !it.isDone(); it.advance()) {
const Edge *edge = it.current();
const Vertex *vertex = edge->vertex;
polygonVertices.push_back(vertex->id);
Vector2 p;
p.x = dot(basis.tangent, vertex->pos);
p.y = dot(basis.bitangent, vertex->pos);
polygonPoints.push_back(p);
}
polygonAngles.resize(edgeCount);
while (polygonVertices.size() > 2) {
uint32_t size = polygonVertices.size();
// Update polygon angles. @@ Update only those that have changed.
float minAngle = 2 * PI;
uint32_t bestEar = 0; // Use first one if none of them is valid.
bool bestIsValid = false;
for (uint32_t i = 0; i < size; i++) {
uint32_t i0 = i;
uint32_t i1 = (i + 1) % size; // Use Sean's polygon interation trick.
uint32_t i2 = (i + 2) % size;
Vector2 p0 = polygonPoints[i0];
Vector2 p1 = polygonPoints[i1];
Vector2 p2 = polygonPoints[i2];
bool degenerate = distance(p0, p1) < NV_EPSILON || distance(p0, p2) < NV_EPSILON || distance(p1, p2) < NV_EPSILON;
if (degenerate) {
continue;
}
float d = clamp(dot(p0 - p1, p2 - p1) / (length(p0 - p1) * length(p2 - p1)), -1.0f, 1.0f);
float angle = acosf(d);
float area = triangleArea(p0, p1, p2);
if (area < 0.0f) angle = 2.0f * PI - angle;
polygonAngles[i1] = angle;
if (angle < minAngle || !bestIsValid) {
// Make sure this is a valid ear, if not, skip this point.
bool valid = true;
for (uint32_t j = 0; j < size; j++) {
if (j == i0 || j == i1 || j == i2) continue;
Vector2 p = polygonPoints[j];
if (pointInTriangle(p, p0, p1, p2)) {
valid = false;
break;
}
}
if (valid || !bestIsValid) {
minAngle = angle;
bestEar = i1;
bestIsValid = valid;
}
}
}
if (!bestIsValid)
break;
xaDebugAssert(minAngle <= 2 * PI);
// Clip best ear:
uint32_t i0 = (bestEar + size - 1) % size;
uint32_t i1 = (bestEar + 0) % size;
uint32_t i2 = (bestEar + 1) % size;
int v0 = polygonVertices[i0];
int v1 = polygonVertices[i1];
int v2 = polygonVertices[i2];
mesh->addFace(v0, v1, v2);
polygonVertices.erase(polygonVertices.begin() + i1);
polygonPoints.erase(polygonPoints.begin() + i1);
polygonAngles.erase(polygonAngles.begin() + i1);
}
}
}
mesh->linkBoundary();
return mesh;
}
} // namespace halfedge
/// Mersenne twister random number generator.
class MTRand {
public:
enum time_e { Time };
enum { N = 624 }; // length of state vector
enum { M = 397 };
/// Constructor that uses the current time as the seed.
MTRand(time_e) {
seed((uint32_t)time(NULL));
}
/// Constructor that uses the given seed.
MTRand(uint32_t s = 0) {
seed(s);
}
/// Provide a new seed.
void seed(uint32_t s) {
initialize(s);
reload();
}
/// Get a random number between 0 - 65536.
uint32_t get() {
// Pull a 32-bit integer from the generator state
// Every other access function simply transforms the numbers extracted here
if (left == 0) {
reload();
}
left--;
uint32_t s1;
s1 = *next++;
s1 ^= (s1 >> 11);
s1 ^= (s1 << 7) & 0x9d2c5680U;
s1 ^= (s1 << 15) & 0xefc60000U;
return (s1 ^ (s1 >> 18));
};
/// Get a random number on [0, max] interval.
uint32_t getRange(uint32_t max) {
if (max == 0) return 0;
if (max == NV_UINT32_MAX) return get();
const uint32_t np2 = nextPowerOfTwo(max + 1); // @@ This fails if max == NV_UINT32_MAX
const uint32_t mask = np2 - 1;
uint32_t n;
do {
n = get() & mask;
} while (n > max);
return n;
}
private:
void initialize(uint32_t seed) {
// Initialize generator state with seed
// See Knuth TAOCP Vol 2, 3rd Ed, p.106 for multiplier.
// In previous versions, most significant bits (MSBs) of the seed affect
// only MSBs of the state array. Modified 9 Jan 2002 by Makoto Matsumoto.
uint32_t *s = state;
uint32_t *r = state;
int i = 1;
*s++ = seed & 0xffffffffUL;
for (; i < N; ++i) {
*s++ = (1812433253UL * (*r ^ (*r >> 30)) + i) & 0xffffffffUL;
r++;
}
}
void reload() {
// Generate N new values in state
// Made clearer and faster by Matthew Bellew (matthew.bellew@home.com)
uint32_t *p = state;
int i;
for (i = N - M; i--; ++p)
*p = twist(p[M], p[0], p[1]);
for (i = M; --i; ++p)
*p = twist(p[M - N], p[0], p[1]);
*p = twist(p[M - N], p[0], state[0]);
left = N, next = state;
}
uint32_t hiBit(uint32_t u) const {
return u & 0x80000000U;
}
uint32_t loBit(uint32_t u) const {
return u & 0x00000001U;
}
uint32_t loBits(uint32_t u) const {
return u & 0x7fffffffU;
}
uint32_t mixBits(uint32_t u, uint32_t v) const {
return hiBit(u) | loBits(v);
}
uint32_t twist(uint32_t m, uint32_t s0, uint32_t s1) const {
return m ^ (mixBits(s0, s1) >> 1) ^ ((~loBit(s1) + 1) & 0x9908b0dfU);
}
uint32_t state[N]; // internal state
uint32_t *next; // next value to get from state
int left; // number of values left before reload needed
};
namespace morton {
// Code from ryg:
// http://fgiesen.wordpress.com/2009/12/13/decoding-morton-codes/
// Inverse of part1By1 - "delete" all odd-indexed bits
uint32_t compact1By1(uint32_t x) {
x &= 0x55555555; // x = -f-e -d-c -b-a -9-8 -7-6 -5-4 -3-2 -1-0
x = (x ^ (x >> 1)) & 0x33333333; // x = --fe --dc --ba --98 --76 --54 --32 --10
x = (x ^ (x >> 2)) & 0x0f0f0f0f; // x = ---- fedc ---- ba98 ---- 7654 ---- 3210
x = (x ^ (x >> 4)) & 0x00ff00ff; // x = ---- ---- fedc ba98 ---- ---- 7654 3210
x = (x ^ (x >> 8)) & 0x0000ffff; // x = ---- ---- ---- ---- fedc ba98 7654 3210
return x;
}
// Inverse of part1By2 - "delete" all bits not at positions divisible by 3
uint32_t compact1By2(uint32_t x) {
x &= 0x09249249; // x = ---- 9--8 --7- -6-- 5--4 --3- -2-- 1--0
x = (x ^ (x >> 2)) & 0x030c30c3; // x = ---- --98 ---- 76-- --54 ---- 32-- --10
x = (x ^ (x >> 4)) & 0x0300f00f; // x = ---- --98 ---- ---- 7654 ---- ---- 3210
x = (x ^ (x >> 8)) & 0xff0000ff; // x = ---- --98 ---- ---- ---- ---- 7654 3210
x = (x ^ (x >> 16)) & 0x000003ff; // x = ---- ---- ---- ---- ---- --98 7654 3210
return x;
}
uint32_t decodeMorton2X(uint32_t code) {
return compact1By1(code >> 0);
}
uint32_t decodeMorton2Y(uint32_t code) {
return compact1By1(code >> 1);
}
uint32_t decodeMorton3X(uint32_t code) {
return compact1By2(code >> 0);
}
uint32_t decodeMorton3Y(uint32_t code) {
return compact1By2(code >> 1);
}
uint32_t decodeMorton3Z(uint32_t code) {
return compact1By2(code >> 2);
}
} // namespace morton
// A simple, dynamic proximity grid based on Jon's code.
// Instead of storing pointers here I store indices.
struct ProximityGrid {
void init(const Box &box, uint32_t count) {
cellArray.clear();
// Determine grid size.
float cellWidth;
Vector3 diagonal = box.extents() * 2.f;
float volume = box.volume();
if (equal(volume, 0)) {
// Degenerate box, treat like a quad.
Vector2 quad;
if (diagonal.x < diagonal.y && diagonal.x < diagonal.z) {
quad.x = diagonal.y;
quad.y = diagonal.z;
} else if (diagonal.y < diagonal.x && diagonal.y < diagonal.z) {
quad.x = diagonal.x;
quad.y = diagonal.z;
} else {
quad.x = diagonal.x;
quad.y = diagonal.y;
}
float cellArea = quad.x * quad.y / count;
cellWidth = sqrtf(cellArea); // pow(cellArea, 1.0f / 2.0f);
} else {
// Ideally we want one cell per point.
float cellVolume = volume / count;
cellWidth = powf(cellVolume, 1.0f / 3.0f);
}
xaDebugAssert(cellWidth != 0);
sx = std::max(1, ftoi_ceil(diagonal.x / cellWidth));
sy = std::max(1, ftoi_ceil(diagonal.y / cellWidth));
sz = std::max(1, ftoi_ceil(diagonal.z / cellWidth));
invCellSize.x = float(sx) / diagonal.x;
invCellSize.y = float(sy) / diagonal.y;
invCellSize.z = float(sz) / diagonal.z;
cellArray.resize(sx * sy * sz);
corner = box.minCorner; // @@ Align grid better?
}
int index_x(float x) const {
return clamp(ftoi_floor((x - corner.x) * invCellSize.x), 0, sx - 1);
}
int index_y(float y) const {
return clamp(ftoi_floor((y - corner.y) * invCellSize.y), 0, sy - 1);
}
int index_z(float z) const {
return clamp(ftoi_floor((z - corner.z) * invCellSize.z), 0, sz - 1);
}
int index(int x, int y, int z) const {
xaDebugAssert(x >= 0 && x < sx);
xaDebugAssert(y >= 0 && y < sy);
xaDebugAssert(z >= 0 && z < sz);
int idx = (z * sy + y) * sx + x;
xaDebugAssert(idx >= 0 && uint32_t(idx) < cellArray.size());
return idx;
}
uint32_t mortonCount() const {
uint64_t s = uint64_t(max3(sx, sy, sz));
s = nextPowerOfTwo(s);
if (s > 1024) {
return uint32_t(s * s * min3(sx, sy, sz));
}
return uint32_t(s * s * s);
}
int mortonIndex(uint32_t code) const {
uint32_t x, y, z;
uint32_t s = uint32_t(max3(sx, sy, sz));
if (s > 1024) {
// Use layered two-dimensional morton order.
s = nextPowerOfTwo(s);
uint32_t layer = code / (s * s);
code = code % (s * s);
uint32_t layer_count = uint32_t(min3(sx, sy, sz));
if (sx == (int)layer_count) {
x = layer;
y = morton::decodeMorton2X(code);
z = morton::decodeMorton2Y(code);
} else if (sy == (int)layer_count) {
x = morton::decodeMorton2Y(code);
y = layer;
z = morton::decodeMorton2X(code);
} else { /*if (sz == layer_count)*/
x = morton::decodeMorton2X(code);
y = morton::decodeMorton2Y(code);
z = layer;
}
} else {
x = morton::decodeMorton3X(code);
y = morton::decodeMorton3Y(code);
z = morton::decodeMorton3Z(code);
}
if (x >= uint32_t(sx) || y >= uint32_t(sy) || z >= uint32_t(sz)) {
return -1;
}
return index(x, y, z);
}
void add(const Vector3 &pos, uint32_t key) {
int x = index_x(pos.x);
int y = index_y(pos.y);
int z = index_z(pos.z);
uint32_t idx = index(x, y, z);
cellArray[idx].indexArray.push_back(key);
}
// Gather all points inside the given sphere.
// Radius is assumed to be small, so we don't bother culling the cells.
void gather(const Vector3 &position, float radius, std::vector<uint32_t> &indexArray) {
int x0 = index_x(position.x - radius);
int x1 = index_x(position.x + radius);
int y0 = index_y(position.y - radius);
int y1 = index_y(position.y + radius);
int z0 = index_z(position.z - radius);
int z1 = index_z(position.z + radius);
for (int z = z0; z <= z1; z++) {
for (int y = y0; y <= y1; y++) {
for (int x = x0; x <= x1; x++) {
int idx = index(x, y, z);
indexArray.insert(indexArray.begin(), cellArray[idx].indexArray.begin(), cellArray[idx].indexArray.end());
}
}
}
}
struct Cell {
std::vector<uint32_t> indexArray;
};
std::vector<Cell> cellArray;
Vector3 corner;
Vector3 invCellSize;
int sx, sy, sz;
};
// Based on Pierre Terdiman's and Michael Herf's source code.
// http://www.codercorner.com/RadixSortRevisited.htm
// http://www.stereopsis.com/radix.html
class RadixSort {
public:
RadixSort() :
m_size(0),
m_ranks(NULL),
m_ranks2(NULL),
m_validRanks(false) {}
~RadixSort() {
// Release everything
free(m_ranks2);
free(m_ranks);
}
RadixSort &sort(const float *input, uint32_t count) {
if (input == NULL || count == 0) return *this;
// Resize lists if needed
if (count != m_size) {
if (count > m_size) {
m_ranks2 = (uint32_t *)realloc(m_ranks2, sizeof(uint32_t) * count);
m_ranks = (uint32_t *)realloc(m_ranks, sizeof(uint32_t) * count);
}
m_size = count;
m_validRanks = false;
}
if (count < 32) {
insertionSort(input, count);
} else {
// @@ Avoid touching the input multiple times.
for (uint32_t i = 0; i < count; i++) {
FloatFlip((uint32_t &)input[i]);
}
radixSort<uint32_t>((const uint32_t *)input, count);
for (uint32_t i = 0; i < count; i++) {
IFloatFlip((uint32_t &)input[i]);
}
}
return *this;
}
RadixSort &sort(const std::vector<float> &input) {
return sort(input.data(), input.size());
}
// Access to results. m_ranks is a list of indices in sorted order, i.e. in the order you may further process your data
const uint32_t *ranks() const {
xaDebugAssert(m_validRanks);
return m_ranks;
}
uint32_t *ranks() {
xaDebugAssert(m_validRanks);
return m_ranks;
}
private:
uint32_t m_size;
uint32_t *m_ranks;
uint32_t *m_ranks2;
bool m_validRanks;
void FloatFlip(uint32_t &f) {
int32_t mask = (int32_t(f) >> 31) | 0x80000000; // Warren Hunt, Manchor Ko.
f ^= mask;
}
void IFloatFlip(uint32_t &f) {
uint32_t mask = ((f >> 31) - 1) | 0x80000000; // Michael Herf.
f ^= mask;
}
template <typename T>
void createHistograms(const T *buffer, uint32_t count, uint32_t *histogram) {
const uint32_t bucketCount = sizeof(T); // (8 * sizeof(T)) / log2(radix)
// Init bucket pointers.
uint32_t *h[bucketCount];
for (uint32_t i = 0; i < bucketCount; i++) {
h[i] = histogram + 256 * i;
}
// Clear histograms.
memset(histogram, 0, 256 * bucketCount * sizeof(uint32_t));
// @@ Add support for signed integers.
// Build histograms.
const uint8_t *p = (const uint8_t *)buffer; // @@ Does this break aliasing rules?
const uint8_t *pe = p + count * sizeof(T);
while (p != pe) {
h[0][*p++]++, h[1][*p++]++, h[2][*p++]++, h[3][*p++]++;
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable : 4127)
#endif
if (bucketCount == 8) h[4][*p++]++, h[5][*p++]++, h[6][*p++]++, h[7][*p++]++;
#ifdef _MSC_VER
#pragma warning(pop)
#endif
}
}
template <typename T>
void insertionSort(const T *input, uint32_t count) {
if (!m_validRanks) {
m_ranks[0] = 0;
for (uint32_t i = 1; i != count; ++i) {
int rank = m_ranks[i] = i;
uint32_t j = i;
while (j != 0 && input[rank] < input[m_ranks[j - 1]]) {
m_ranks[j] = m_ranks[j - 1];
--j;
}
if (i != j) {
m_ranks[j] = rank;
}
}
m_validRanks = true;
} else {
for (uint32_t i = 1; i != count; ++i) {
int rank = m_ranks[i];
uint32_t j = i;
while (j != 0 && input[rank] < input[m_ranks[j - 1]]) {
m_ranks[j] = m_ranks[j - 1];
--j;
}
if (i != j) {
m_ranks[j] = rank;
}
}
}
}
template <typename T>
void radixSort(const T *input, uint32_t count) {
const uint32_t P = sizeof(T); // pass count
// Allocate histograms & offsets on the stack
uint32_t histogram[256 * P];
uint32_t *link[256];
createHistograms(input, count, histogram);
// Radix sort, j is the pass number (0=LSB, P=MSB)
for (uint32_t j = 0; j < P; j++) {
// Pointer to this bucket.
const uint32_t *h = &histogram[j * 256];
const uint8_t *inputBytes = (const uint8_t *)input; // @@ Is this aliasing legal?
inputBytes += j;
if (h[inputBytes[0]] == count) {
// Skip this pass, all values are the same.
continue;
}
// Create offsets
link[0] = m_ranks2;
for (uint32_t i = 1; i < 256; i++)
link[i] = link[i - 1] + h[i - 1];
// Perform Radix Sort
if (!m_validRanks) {
for (uint32_t i = 0; i < count; i++) {
*link[inputBytes[i * P]]++ = i;
}
m_validRanks = true;
} else {
for (uint32_t i = 0; i < count; i++) {
const uint32_t idx = m_ranks[i];
*link[inputBytes[idx * P]]++ = idx;
}
}
// Swap pointers for next pass. Valid indices - the most recent ones - are in m_ranks after the swap.
std::swap(m_ranks, m_ranks2);
}
// All values were equal, generate linear ranks.
if (!m_validRanks) {
for (uint32_t i = 0; i < count; i++) {
m_ranks[i] = i;
}
m_validRanks = true;
}
}
};
namespace raster {
class ClippedTriangle {
public:
ClippedTriangle(Vector2::Arg a, Vector2::Arg b, Vector2::Arg c) {
m_numVertices = 3;
m_activeVertexBuffer = 0;
m_verticesA[0] = a;
m_verticesA[1] = b;
m_verticesA[2] = c;
m_vertexBuffers[0] = m_verticesA;
m_vertexBuffers[1] = m_verticesB;
}
uint32_t vertexCount() {
return m_numVertices;
}
const Vector2 *vertices() {
return m_vertexBuffers[m_activeVertexBuffer];
}
void clipHorizontalPlane(float offset, float clipdirection) {
Vector2 *v = m_vertexBuffers[m_activeVertexBuffer];
m_activeVertexBuffer ^= 1;
Vector2 *v2 = m_vertexBuffers[m_activeVertexBuffer];
v[m_numVertices] = v[0];
float dy2, dy1 = offset - v[0].y;
int dy2in, dy1in = clipdirection * dy1 >= 0;
uint32_t p = 0;
for (uint32_t k = 0; k < m_numVertices; k++) {
dy2 = offset - v[k + 1].y;
dy2in = clipdirection * dy2 >= 0;
if (dy1in) v2[p++] = v[k];
if (dy1in + dy2in == 1) { // not both in/out
float dx = v[k + 1].x - v[k].x;
float dy = v[k + 1].y - v[k].y;
v2[p++] = Vector2(v[k].x + dy1 * (dx / dy), offset);
}
dy1 = dy2;
dy1in = dy2in;
}
m_numVertices = p;
//for (uint32_t k=0; k<m_numVertices; k++) printf("(%f, %f)\n", v2[k].x, v2[k].y); printf("\n");
}
void clipVerticalPlane(float offset, float clipdirection) {
Vector2 *v = m_vertexBuffers[m_activeVertexBuffer];
m_activeVertexBuffer ^= 1;
Vector2 *v2 = m_vertexBuffers[m_activeVertexBuffer];
v[m_numVertices] = v[0];
float dx2, dx1 = offset - v[0].x;
int dx2in, dx1in = clipdirection * dx1 >= 0;
uint32_t p = 0;
for (uint32_t k = 0; k < m_numVertices; k++) {
dx2 = offset - v[k + 1].x;
dx2in = clipdirection * dx2 >= 0;
if (dx1in) v2[p++] = v[k];
if (dx1in + dx2in == 1) { // not both in/out
float dx = v[k + 1].x - v[k].x;
float dy = v[k + 1].y - v[k].y;
v2[p++] = Vector2(offset, v[k].y + dx1 * (dy / dx));
}
dx1 = dx2;
dx1in = dx2in;
}
m_numVertices = p;
}
void computeAreaCentroid() {
Vector2 *v = m_vertexBuffers[m_activeVertexBuffer];
v[m_numVertices] = v[0];
m_area = 0;
float centroidx = 0, centroidy = 0;
for (uint32_t k = 0; k < m_numVertices; k++) {
// http://local.wasp.uwa.edu.au/~pbourke/geometry/polyarea/
float f = v[k].x * v[k + 1].y - v[k + 1].x * v[k].y;
m_area += f;
centroidx += f * (v[k].x + v[k + 1].x);
centroidy += f * (v[k].y + v[k + 1].y);
}
m_area = 0.5f * fabsf(m_area);
if (m_area == 0) {
m_centroid = Vector2(0.0f);
} else {
m_centroid = Vector2(centroidx / (6 * m_area), centroidy / (6 * m_area));
}
}
void clipAABox(float x0, float y0, float x1, float y1) {
clipVerticalPlane(x0, -1);
clipHorizontalPlane(y0, -1);
clipVerticalPlane(x1, 1);
clipHorizontalPlane(y1, 1);
computeAreaCentroid();
}
Vector2 centroid() {
return m_centroid;
}
float area() {
return m_area;
}
private:
Vector2 m_verticesA[7 + 1];
Vector2 m_verticesB[7 + 1];
Vector2 *m_vertexBuffers[2];
uint32_t m_numVertices;
uint32_t m_activeVertexBuffer;
float m_area;
Vector2 m_centroid;
};
/// A callback to sample the environment. Return false to terminate rasterization.
typedef bool (*SamplingCallback)(void *param, int x, int y, Vector3::Arg bar, Vector3::Arg dx, Vector3::Arg dy, float coverage);
/// A triangle for rasterization.
struct Triangle {
Triangle(Vector2::Arg v0, Vector2::Arg v1, Vector2::Arg v2, Vector3::Arg t0, Vector3::Arg t1, Vector3::Arg t2) {
// Init vertices.
this->v1 = v0;
this->v2 = v2;
this->v3 = v1;
// Set barycentric coordinates.
this->t1 = t0;
this->t2 = t2;
this->t3 = t1;
// make sure every triangle is front facing.
flipBackface();
// Compute deltas.
valid = computeDeltas();
computeUnitInwardNormals();
}
/// Compute texture space deltas.
/// This method takes two edge vectors that form a basis, determines the
/// coordinates of the canonic vectors in that basis, and computes the
/// texture gradient that corresponds to those vectors.
bool computeDeltas() {
Vector2 e0 = v3 - v1;
Vector2 e1 = v2 - v1;
Vector3 de0 = t3 - t1;
Vector3 de1 = t2 - t1;
float denom = 1.0f / (e0.y * e1.x - e1.y * e0.x);
if (!std::isfinite(denom)) {
return false;
}
float lambda1 = -e1.y * denom;
float lambda2 = e0.y * denom;
float lambda3 = e1.x * denom;
float lambda4 = -e0.x * denom;
dx = de0 * lambda1 + de1 * lambda2;
dy = de0 * lambda3 + de1 * lambda4;
return true;
}
bool draw(const Vector2 &extents, bool enableScissors, SamplingCallback cb, void *param) {
// 28.4 fixed-point coordinates
const int Y1 = ftoi_round(16.0f * v1.y);
const int Y2 = ftoi_round(16.0f * v2.y);
const int Y3 = ftoi_round(16.0f * v3.y);
const int X1 = ftoi_round(16.0f * v1.x);
const int X2 = ftoi_round(16.0f * v2.x);
const int X3 = ftoi_round(16.0f * v3.x);
// Deltas
const int DX12 = X1 - X2;
const int DX23 = X2 - X3;
const int DX31 = X3 - X1;
const int DY12 = Y1 - Y2;
const int DY23 = Y2 - Y3;
const int DY31 = Y3 - Y1;
// Fixed-point deltas
const int FDX12 = DX12 << 4;
const int FDX23 = DX23 << 4;
const int FDX31 = DX31 << 4;
const int FDY12 = DY12 << 4;
const int FDY23 = DY23 << 4;
const int FDY31 = DY31 << 4;
int minx, miny, maxx, maxy;
if (enableScissors) {
int frustumX0 = 0 << 4;
int frustumY0 = 0 << 4;
int frustumX1 = (int)extents.x << 4;
int frustumY1 = (int)extents.y << 4;
// Bounding rectangle
minx = (std::max(min3(X1, X2, X3), frustumX0) + 0xF) >> 4;
miny = (std::max(min3(Y1, Y2, Y3), frustumY0) + 0xF) >> 4;
maxx = (std::min(max3(X1, X2, X3), frustumX1) + 0xF) >> 4;
maxy = (std::min(max3(Y1, Y2, Y3), frustumY1) + 0xF) >> 4;
} else {
// Bounding rectangle
minx = (min3(X1, X2, X3) + 0xF) >> 4;
miny = (min3(Y1, Y2, Y3) + 0xF) >> 4;
maxx = (max3(X1, X2, X3) + 0xF) >> 4;
maxy = (max3(Y1, Y2, Y3) + 0xF) >> 4;
}
// Block size, standard 8x8 (must be power of two)
const int q = 8;
// @@ This won't work when minx,miny are negative. This code path is not used. Leaving as is for now.
xaAssert(minx >= 0);
xaAssert(miny >= 0);
// Start in corner of 8x8 block
minx &= ~(q - 1);
miny &= ~(q - 1);
// Half-edge constants
int C1 = DY12 * X1 - DX12 * Y1;
int C2 = DY23 * X2 - DX23 * Y2;
int C3 = DY31 * X3 - DX31 * Y3;
// Correct for fill convention
if (DY12 < 0 || (DY12 == 0 && DX12 > 0)) C1++;
if (DY23 < 0 || (DY23 == 0 && DX23 > 0)) C2++;
if (DY31 < 0 || (DY31 == 0 && DX31 > 0)) C3++;
// Loop through blocks
for (int y = miny; y < maxy; y += q) {
for (int x = minx; x < maxx; x += q) {
// Corners of block
int x0 = x << 4;
int x1 = (x + q - 1) << 4;
int y0 = y << 4;
int y1 = (y + q - 1) << 4;
// Evaluate half-space functions
bool a00 = C1 + DX12 * y0 - DY12 * x0 > 0;
bool a10 = C1 + DX12 * y0 - DY12 * x1 > 0;
bool a01 = C1 + DX12 * y1 - DY12 * x0 > 0;
bool a11 = C1 + DX12 * y1 - DY12 * x1 > 0;
int a = (a00 << 0) | (a10 << 1) | (a01 << 2) | (a11 << 3);
bool b00 = C2 + DX23 * y0 - DY23 * x0 > 0;
bool b10 = C2 + DX23 * y0 - DY23 * x1 > 0;
bool b01 = C2 + DX23 * y1 - DY23 * x0 > 0;
bool b11 = C2 + DX23 * y1 - DY23 * x1 > 0;
int b = (b00 << 0) | (b10 << 1) | (b01 << 2) | (b11 << 3);
bool c00 = C3 + DX31 * y0 - DY31 * x0 > 0;
bool c10 = C3 + DX31 * y0 - DY31 * x1 > 0;
bool c01 = C3 + DX31 * y1 - DY31 * x0 > 0;
bool c11 = C3 + DX31 * y1 - DY31 * x1 > 0;
int c = (c00 << 0) | (c10 << 1) | (c01 << 2) | (c11 << 3);
// Skip block when outside an edge
if (a == 0x0 || b == 0x0 || c == 0x0) continue;
// Accept whole block when totally covered
if (a == 0xF && b == 0xF && c == 0xF) {
Vector3 texRow = t1 + dy * (y0 - v1.y) + dx * (x0 - v1.x);
for (int iy = y; iy < y + q; iy++) {
Vector3 tex = texRow;
for (int ix = x; ix < x + q; ix++) {
//Vector3 tex = t1 + dx * (ix - v1.x) + dy * (iy - v1.y);
if (!cb(param, ix, iy, tex, dx, dy, 1.0)) {
// early out.
return false;
}
tex += dx;
}
texRow += dy;
}
} else { // Partially covered block
int CY1 = C1 + DX12 * y0 - DY12 * x0;
int CY2 = C2 + DX23 * y0 - DY23 * x0;
int CY3 = C3 + DX31 * y0 - DY31 * x0;
Vector3 texRow = t1 + dy * (y0 - v1.y) + dx * (x0 - v1.x);
for (int iy = y; iy < y + q; iy++) {
int CX1 = CY1;
int CX2 = CY2;
int CX3 = CY3;
Vector3 tex = texRow;
for (int ix = x; ix < x + q; ix++) {
if (CX1 > 0 && CX2 > 0 && CX3 > 0) {
if (!cb(param, ix, iy, tex, dx, dy, 1.0)) {
// early out.
return false;
}
}
CX1 -= FDY12;
CX2 -= FDY23;
CX3 -= FDY31;
tex += dx;
}
CY1 += FDX12;
CY2 += FDX23;
CY3 += FDX31;
texRow += dy;
}
}
}
}
return true;
}
// extents has to be multiple of BK_SIZE!!
bool drawAA(const Vector2 &extents, bool enableScissors, SamplingCallback cb, void *param) {
const float PX_INSIDE = 1.0f / sqrt(2.0f);
const float PX_OUTSIDE = -1.0f / sqrt(2.0f);
const float BK_SIZE = 8;
const float BK_INSIDE = sqrt(BK_SIZE * BK_SIZE / 2.0f);
const float BK_OUTSIDE = -sqrt(BK_SIZE * BK_SIZE / 2.0f);
float minx, miny, maxx, maxy;
if (enableScissors) {
// Bounding rectangle
minx = floorf(std::max(min3(v1.x, v2.x, v3.x), 0.0f));
miny = floorf(std::max(min3(v1.y, v2.y, v3.y), 0.0f));
maxx = ceilf(std::min(max3(v1.x, v2.x, v3.x), extents.x - 1.0f));
maxy = ceilf(std::min(max3(v1.y, v2.y, v3.y), extents.y - 1.0f));
} else {
// Bounding rectangle
minx = floorf(min3(v1.x, v2.x, v3.x));
miny = floorf(min3(v1.y, v2.y, v3.y));
maxx = ceilf(max3(v1.x, v2.x, v3.x));
maxy = ceilf(max3(v1.y, v2.y, v3.y));
}
// There's no reason to align the blocks to the viewport, instead we align them to the origin of the triangle bounds.
minx = floorf(minx);
miny = floorf(miny);
//minx = (float)(((int)minx) & (~((int)BK_SIZE - 1))); // align to blocksize (we don't need to worry about blocks partially out of viewport)
//miny = (float)(((int)miny) & (~((int)BK_SIZE - 1)));
minx += 0.5;
miny += 0.5; // sampling at texel centers!
maxx += 0.5;
maxy += 0.5;
// Half-edge constants
float C1 = n1.x * (-v1.x) + n1.y * (-v1.y);
float C2 = n2.x * (-v2.x) + n2.y * (-v2.y);
float C3 = n3.x * (-v3.x) + n3.y * (-v3.y);
// Loop through blocks
for (float y0 = miny; y0 <= maxy; y0 += BK_SIZE) {
for (float x0 = minx; x0 <= maxx; x0 += BK_SIZE) {
// Corners of block
float xc = (x0 + (BK_SIZE - 1) / 2.0f);
float yc = (y0 + (BK_SIZE - 1) / 2.0f);
// Evaluate half-space functions
float aC = C1 + n1.x * xc + n1.y * yc;
float bC = C2 + n2.x * xc + n2.y * yc;
float cC = C3 + n3.x * xc + n3.y * yc;
// Skip block when outside an edge
if ((aC <= BK_OUTSIDE) || (bC <= BK_OUTSIDE) || (cC <= BK_OUTSIDE)) continue;
// Accept whole block when totally covered
if ((aC >= BK_INSIDE) && (bC >= BK_INSIDE) && (cC >= BK_INSIDE)) {
Vector3 texRow = t1 + dy * (y0 - v1.y) + dx * (x0 - v1.x);
for (float y = y0; y < y0 + BK_SIZE; y++) {
Vector3 tex = texRow;
for (float x = x0; x < x0 + BK_SIZE; x++) {
if (!cb(param, (int)x, (int)y, tex, dx, dy, 1.0f)) {
return false;
}
tex += dx;
}
texRow += dy;
}
} else { // Partially covered block
float CY1 = C1 + n1.x * x0 + n1.y * y0;
float CY2 = C2 + n2.x * x0 + n2.y * y0;
float CY3 = C3 + n3.x * x0 + n3.y * y0;
Vector3 texRow = t1 + dy * (y0 - v1.y) + dx * (x0 - v1.x);
for (float y = y0; y < y0 + BK_SIZE; y++) { // @@ This is not clipping to scissor rectangle correctly.
float CX1 = CY1;
float CX2 = CY2;
float CX3 = CY3;
Vector3 tex = texRow;
for (float x = x0; x < x0 + BK_SIZE; x++) { // @@ This is not clipping to scissor rectangle correctly.
if (CX1 >= PX_INSIDE && CX2 >= PX_INSIDE && CX3 >= PX_INSIDE) {
// pixel completely covered
Vector3 tex2 = t1 + dx * (x - v1.x) + dy * (y - v1.y);
if (!cb(param, (int)x, (int)y, tex2, dx, dy, 1.0f)) {
return false;
}
} else if ((CX1 >= PX_OUTSIDE) && (CX2 >= PX_OUTSIDE) && (CX3 >= PX_OUTSIDE)) {
// triangle partially covers pixel. do clipping.
ClippedTriangle ct(v1 - Vector2(x, y), v2 - Vector2(x, y), v3 - Vector2(x, y));
ct.clipAABox(-0.5, -0.5, 0.5, 0.5);
Vector2 centroid = ct.centroid();
float area = ct.area();
if (area > 0.0f) {
Vector3 texCent = tex - dx * centroid.x - dy * centroid.y;
//xaAssert(texCent.x >= -0.1f && texCent.x <= 1.1f); // @@ Centroid is not very exact...
//xaAssert(texCent.y >= -0.1f && texCent.y <= 1.1f);
//xaAssert(texCent.z >= -0.1f && texCent.z <= 1.1f);
//Vector3 texCent2 = t1 + dx * (x - v1.x) + dy * (y - v1.y);
if (!cb(param, (int)x, (int)y, texCent, dx, dy, area)) {
return false;
}
}
}
CX1 += n1.x;
CX2 += n2.x;
CX3 += n3.x;
tex += dx;
}
CY1 += n1.y;
CY2 += n2.y;
CY3 += n3.y;
texRow += dy;
}
}
}
}
return true;
}
void flipBackface() {
// check if triangle is backfacing, if so, swap two vertices
if (((v3.x - v1.x) * (v2.y - v1.y) - (v3.y - v1.y) * (v2.x - v1.x)) < 0) {
Vector2 hv = v1;
v1 = v2;
v2 = hv; // swap pos
Vector3 ht = t1;
t1 = t2;
t2 = ht; // swap tex
}
}
// compute unit inward normals for each edge.
void computeUnitInwardNormals() {
n1 = v1 - v2;
n1 = Vector2(-n1.y, n1.x);
n1 = n1 * (1.0f / sqrtf(n1.x * n1.x + n1.y * n1.y));
n2 = v2 - v3;
n2 = Vector2(-n2.y, n2.x);
n2 = n2 * (1.0f / sqrtf(n2.x * n2.x + n2.y * n2.y));
n3 = v3 - v1;
n3 = Vector2(-n3.y, n3.x);
n3 = n3 * (1.0f / sqrtf(n3.x * n3.x + n3.y * n3.y));
}
// Vertices.
Vector2 v1, v2, v3;
Vector2 n1, n2, n3; // unit inward normals
Vector3 t1, t2, t3;
// Deltas.
Vector3 dx, dy;
float sign;
bool valid;
};
enum Mode {
Mode_Nearest,
Mode_Antialiased
};
// Process the given triangle. Returns false if rasterization was interrupted by the callback.
static bool drawTriangle(Mode mode, Vector2::Arg extents, bool enableScissors, const Vector2 v[3], SamplingCallback cb, void *param) {
Triangle tri(v[0], v[1], v[2], Vector3(1, 0, 0), Vector3(0, 1, 0), Vector3(0, 0, 1));
// @@ It would be nice to have a conservative drawing mode that enlarges the triangle extents by one texel and is able to handle degenerate triangles.
// @@ Maybe the simplest thing to do would be raster triangle edges.
if (tri.valid) {
if (mode == Mode_Antialiased) {
return tri.drawAA(extents, enableScissors, cb, param);
}
if (mode == Mode_Nearest) {
return tri.draw(extents, enableScissors, cb, param);
}
}
return true;
}
// Process the given quad. Returns false if rasterization was interrupted by the callback.
static bool drawQuad(Mode mode, Vector2::Arg extents, bool enableScissors, const Vector2 v[4], SamplingCallback cb, void *param) {
bool sign0 = triangleArea2(v[0], v[1], v[2]) > 0.0f;
bool sign1 = triangleArea2(v[0], v[2], v[3]) > 0.0f;
// Divide the quad into two non overlapping triangles.
if (sign0 == sign1) {
Triangle tri0(v[0], v[1], v[2], Vector3(0, 0, 0), Vector3(1, 0, 0), Vector3(1, 1, 0));
Triangle tri1(v[0], v[2], v[3], Vector3(0, 0, 0), Vector3(1, 1, 0), Vector3(0, 1, 0));
if (tri0.valid && tri1.valid) {
if (mode == Mode_Antialiased) {
return tri0.drawAA(extents, enableScissors, cb, param) && tri1.drawAA(extents, enableScissors, cb, param);
} else {
return tri0.draw(extents, enableScissors, cb, param) && tri1.draw(extents, enableScissors, cb, param);
}
}
} else {
Triangle tri0(v[0], v[1], v[3], Vector3(0, 0, 0), Vector3(1, 0, 0), Vector3(0, 1, 0));
Triangle tri1(v[1], v[2], v[3], Vector3(1, 0, 0), Vector3(1, 1, 0), Vector3(0, 1, 0));
if (tri0.valid && tri1.valid) {
if (mode == Mode_Antialiased) {
return tri0.drawAA(extents, enableScissors, cb, param) && tri1.drawAA(extents, enableScissors, cb, param);
} else {
return tri0.draw(extents, enableScissors, cb, param) && tri1.draw(extents, enableScissors, cb, param);
}
}
}
return true;
}
} // namespace raster
// Full and sparse vector and matrix classes. BLAS subset.
// Pseudo-BLAS interface.
namespace sparse {
enum Transpose {
NoTransposed = 0,
Transposed = 1
};
/**
* Sparse matrix class. The matrix is assumed to be sparse and to have
* very few non-zero elements, for this reason it's stored in indexed
* format. To multiply column vectors efficiently, the matrix stores
* the elements in indexed-column order, there is a list of indexed
* elements for each row of the matrix. As with the FullVector the
* dimension of the matrix is constant.
**/
class Matrix {
public:
// An element of the sparse array.
struct Coefficient {
uint32_t x; // column
float v; // value
};
Matrix(uint32_t d) :
m_width(d) { m_array.resize(d); }
Matrix(uint32_t w, uint32_t h) :
m_width(w) { m_array.resize(h); }
Matrix(const Matrix &m) :
m_width(m.m_width) { m_array = m.m_array; }
const Matrix &operator=(const Matrix &m) {
xaAssert(width() == m.width());
xaAssert(height() == m.height());
m_array = m.m_array;
return *this;
}
uint32_t width() const { return m_width; }
uint32_t height() const { return m_array.size(); }
bool isSquare() const { return width() == height(); }
// x is column, y is row
float getCoefficient(uint32_t x, uint32_t y) const {
xaDebugAssert(x < width());
xaDebugAssert(y < height());
const uint32_t count = m_array[y].size();
for (uint32_t i = 0; i < count; i++) {
if (m_array[y][i].x == x) return m_array[y][i].v;
}
return 0.0f;
}
void setCoefficient(uint32_t x, uint32_t y, float f) {
xaDebugAssert(x < width());
xaDebugAssert(y < height());
const uint32_t count = m_array[y].size();
for (uint32_t i = 0; i < count; i++) {
if (m_array[y][i].x == x) {
m_array[y][i].v = f;
return;
}
}
if (f != 0.0f) {
Coefficient c = { x, f };
m_array[y].push_back(c);
}
}
float dotRow(uint32_t y, const FullVector &v) const {
xaDebugAssert(y < height());
const uint32_t count = m_array[y].size();
float sum = 0;
for (uint32_t i = 0; i < count; i++) {
sum += m_array[y][i].v * v[m_array[y][i].x];
}
return sum;
}
void madRow(uint32_t y, float alpha, FullVector &v) const {
xaDebugAssert(y < height());
const uint32_t count = m_array[y].size();
for (uint32_t i = 0; i < count; i++) {
v[m_array[y][i].x] += alpha * m_array[y][i].v;
}
}
void clearRow(uint32_t y) {
xaDebugAssert(y < height());
m_array[y].clear();
}
void scaleRow(uint32_t y, float f) {
xaDebugAssert(y < height());
const uint32_t count = m_array[y].size();
for (uint32_t i = 0; i < count; i++) {
m_array[y][i].v *= f;
}
}
const std::vector<Coefficient> &getRow(uint32_t y) const { return m_array[y]; }
private:
/// Number of columns.
const uint32_t m_width;
/// Array of matrix elements.
std::vector<std::vector<Coefficient> > m_array;
};
// y = a * x + y
static void saxpy(float a, const FullVector &x, FullVector &y) {
xaDebugAssert(x.dimension() == y.dimension());
const uint32_t dim = x.dimension();
for (uint32_t i = 0; i < dim; i++) {
y[i] += a * x[i];
}
}
static void copy(const FullVector &x, FullVector &y) {
xaDebugAssert(x.dimension() == y.dimension());
const uint32_t dim = x.dimension();
for (uint32_t i = 0; i < dim; i++) {
y[i] = x[i];
}
}
static void scal(float a, FullVector &x) {
const uint32_t dim = x.dimension();
for (uint32_t i = 0; i < dim; i++) {
x[i] *= a;
}
}
static float dot(const FullVector &x, const FullVector &y) {
xaDebugAssert(x.dimension() == y.dimension());
const uint32_t dim = x.dimension();
float sum = 0;
for (uint32_t i = 0; i < dim; i++) {
sum += x[i] * y[i];
}
return sum;
}
static void mult(Transpose TM, const Matrix &M, const FullVector &x, FullVector &y) {
const uint32_t w = M.width();
const uint32_t h = M.height();
if (TM == Transposed) {
xaDebugAssert(h == x.dimension());
xaDebugAssert(w == y.dimension());
y.fill(0.0f);
for (uint32_t i = 0; i < h; i++) {
M.madRow(i, x[i], y);
}
} else {
xaDebugAssert(w == x.dimension());
xaDebugAssert(h == y.dimension());
for (uint32_t i = 0; i < h; i++) {
y[i] = M.dotRow(i, x);
}
}
}
// y = M * x
static void mult(const Matrix &M, const FullVector &x, FullVector &y) {
mult(NoTransposed, M, x, y);
}
static void sgemv(float alpha, Transpose TA, const Matrix &A, const FullVector &x, float beta, FullVector &y) {
const uint32_t w = A.width();
const uint32_t h = A.height();
if (TA == Transposed) {
xaDebugAssert(h == x.dimension());
xaDebugAssert(w == y.dimension());
for (uint32_t i = 0; i < h; i++) {
A.madRow(i, alpha * x[i], y);
}
} else {
xaDebugAssert(w == x.dimension());
xaDebugAssert(h == y.dimension());
for (uint32_t i = 0; i < h; i++) {
y[i] = alpha * A.dotRow(i, x) + beta * y[i];
}
}
}
// y = alpha*A*x + beta*y
static void sgemv(float alpha, const Matrix &A, const FullVector &x, float beta, FullVector &y) {
sgemv(alpha, NoTransposed, A, x, beta, y);
}
// dot y-row of A by x-column of B
static float dotRowColumn(int y, const Matrix &A, int x, const Matrix &B) {
const std::vector<Matrix::Coefficient> &row = A.getRow(y);
const uint32_t count = row.size();
float sum = 0.0f;
for (uint32_t i = 0; i < count; i++) {
const Matrix::Coefficient &c = row[i];
sum += c.v * B.getCoefficient(x, c.x);
}
return sum;
}
// dot y-row of A by x-row of B
static float dotRowRow(int y, const Matrix &A, int x, const Matrix &B) {
const std::vector<Matrix::Coefficient> &row = A.getRow(y);
const uint32_t count = row.size();
float sum = 0.0f;
for (uint32_t i = 0; i < count; i++) {
const Matrix::Coefficient &c = row[i];
sum += c.v * B.getCoefficient(c.x, x);
}
return sum;
}
// dot y-column of A by x-column of B
static float dotColumnColumn(int y, const Matrix &A, int x, const Matrix &B) {
xaDebugAssert(A.height() == B.height());
const uint32_t h = A.height();
float sum = 0.0f;
for (uint32_t i = 0; i < h; i++) {
sum += A.getCoefficient(y, i) * B.getCoefficient(x, i);
}
return sum;
}
static void transpose(const Matrix &A, Matrix &B) {
xaDebugAssert(A.width() == B.height());
xaDebugAssert(B.width() == A.height());
const uint32_t w = A.width();
for (uint32_t x = 0; x < w; x++) {
B.clearRow(x);
}
const uint32_t h = A.height();
for (uint32_t y = 0; y < h; y++) {
const std::vector<Matrix::Coefficient> &row = A.getRow(y);
const uint32_t count = row.size();
for (uint32_t i = 0; i < count; i++) {
const Matrix::Coefficient &c = row[i];
xaDebugAssert(c.x < w);
B.setCoefficient(y, c.x, c.v);
}
}
}
static void sgemm(float alpha, Transpose TA, const Matrix &A, Transpose TB, const Matrix &B, float beta, Matrix &C) {
const uint32_t w = C.width();
const uint32_t h = C.height();
uint32_t aw = (TA == NoTransposed) ? A.width() : A.height();
uint32_t ah = (TA == NoTransposed) ? A.height() : A.width();
uint32_t bw = (TB == NoTransposed) ? B.width() : B.height();
uint32_t bh = (TB == NoTransposed) ? B.height() : B.width();
xaDebugAssert(aw == bh);
xaDebugAssert(bw == ah);
xaDebugAssert(w == bw);
xaDebugAssert(h == ah);
#ifdef NDEBUG
aw = ah = bw = bh = 0; // silence unused parameter warning
#endif
for (uint32_t y = 0; y < h; y++) {
for (uint32_t x = 0; x < w; x++) {
float c = beta * C.getCoefficient(x, y);
if (TA == NoTransposed && TB == NoTransposed) {
// dot y-row of A by x-column of B.
c += alpha * dotRowColumn(y, A, x, B);
} else if (TA == Transposed && TB == Transposed) {
// dot y-column of A by x-row of B.
c += alpha * dotRowColumn(x, B, y, A);
} else if (TA == Transposed && TB == NoTransposed) {
// dot y-column of A by x-column of B.
c += alpha * dotColumnColumn(y, A, x, B);
} else if (TA == NoTransposed && TB == Transposed) {
// dot y-row of A by x-row of B.
c += alpha * dotRowRow(y, A, x, B);
}
C.setCoefficient(x, y, c);
}
}
}
static void mult(Transpose TA, const Matrix &A, Transpose TB, const Matrix &B, Matrix &C) {
sgemm(1.0f, TA, A, TB, B, 0.0f, C);
}
// C = A * B
static void mult(const Matrix &A, const Matrix &B, Matrix &C) {
mult(NoTransposed, A, NoTransposed, B, C);
}
} // namespace sparse
class JacobiPreconditioner {
public:
JacobiPreconditioner(const sparse::Matrix &M, bool symmetric) :
m_inverseDiagonal(M.width()) {
xaAssert(M.isSquare());
for (uint32_t x = 0; x < M.width(); x++) {
float elem = M.getCoefficient(x, x);
//xaDebugAssert( elem != 0.0f ); // This can be zero in the presence of zero area triangles.
if (symmetric) {
m_inverseDiagonal[x] = (elem != 0) ? 1.0f / sqrtf(fabsf(elem)) : 1.0f;
} else {
m_inverseDiagonal[x] = (elem != 0) ? 1.0f / elem : 1.0f;
}
}
}
void apply(const FullVector &x, FullVector &y) const {
xaDebugAssert(x.dimension() == m_inverseDiagonal.dimension());
xaDebugAssert(y.dimension() == m_inverseDiagonal.dimension());
// @@ Wrap vector component-wise product into a separate function.
const uint32_t D = x.dimension();
for (uint32_t i = 0; i < D; i++) {
y[i] = m_inverseDiagonal[i] * x[i];
}
}
private:
FullVector m_inverseDiagonal;
};
// Linear solvers.
class Solver {
public:
// Solve the symmetric system: At<41>A<EFBFBD>x = At<41>b
static bool LeastSquaresSolver(const sparse::Matrix &A, const FullVector &b, FullVector &x, float epsilon = 1e-5f) {
xaDebugAssert(A.width() == x.dimension());
xaDebugAssert(A.height() == b.dimension());
xaDebugAssert(A.height() >= A.width()); // @@ If height == width we could solve it directly...
const uint32_t D = A.width();
sparse::Matrix At(A.height(), A.width());
sparse::transpose(A, At);
FullVector Atb(D);
sparse::mult(At, b, Atb);
sparse::Matrix AtA(D);
sparse::mult(At, A, AtA);
return SymmetricSolver(AtA, Atb, x, epsilon);
}
// See section 10.4.3 in: Mesh Parameterization: Theory and Practice, Siggraph Course Notes, August 2007
static bool LeastSquaresSolver(const sparse::Matrix &A, const FullVector &b, FullVector &x, const uint32_t *lockedParameters, uint32_t lockedCount, float epsilon = 1e-5f) {
xaDebugAssert(A.width() == x.dimension());
xaDebugAssert(A.height() == b.dimension());
xaDebugAssert(A.height() >= A.width() - lockedCount);
// @@ This is not the most efficient way of building a system with reduced degrees of freedom. It would be faster to do it on the fly.
const uint32_t D = A.width() - lockedCount;
xaDebugAssert(D > 0);
// Compute: b - Al * xl
FullVector b_Alxl(b);
for (uint32_t y = 0; y < A.height(); y++) {
const uint32_t count = A.getRow(y).size();
for (uint32_t e = 0; e < count; e++) {
uint32_t column = A.getRow(y)[e].x;
bool isFree = true;
for (uint32_t i = 0; i < lockedCount; i++) {
isFree &= (lockedParameters[i] != column);
}
if (!isFree) {
b_Alxl[y] -= x[column] * A.getRow(y)[e].v;
}
}
}
// Remove locked columns from A.
sparse::Matrix Af(D, A.height());
for (uint32_t y = 0; y < A.height(); y++) {
const uint32_t count = A.getRow(y).size();
for (uint32_t e = 0; e < count; e++) {
uint32_t column = A.getRow(y)[e].x;
uint32_t ix = column;
bool isFree = true;
for (uint32_t i = 0; i < lockedCount; i++) {
isFree &= (lockedParameters[i] != column);
if (column > lockedParameters[i]) ix--; // shift columns
}
if (isFree) {
Af.setCoefficient(ix, y, A.getRow(y)[e].v);
}
}
}
// Remove elements from x
FullVector xf(D);
for (uint32_t i = 0, j = 0; i < A.width(); i++) {
bool isFree = true;
for (uint32_t l = 0; l < lockedCount; l++) {
isFree &= (lockedParameters[l] != i);
}
if (isFree) {
xf[j++] = x[i];
}
}
// Solve reduced system.
bool result = LeastSquaresSolver(Af, b_Alxl, xf, epsilon);
// Copy results back to x.
for (uint32_t i = 0, j = 0; i < A.width(); i++) {
bool isFree = true;
for (uint32_t l = 0; l < lockedCount; l++) {
isFree &= (lockedParameters[l] != i);
}
if (isFree) {
x[i] = xf[j++];
}
}
return result;
}
private:
/**
* Compute the solution of the sparse linear system Ab=x using the Conjugate
* Gradient method.
*
* Solving sparse linear systems:
* (1) A<>x = b
*
* The conjugate gradient algorithm solves (1) only in the case that A is
* symmetric and positive definite. It is based on the idea of minimizing the
* function
*
* (2) f(x) = 1/2<>x<EFBFBD>A<EFBFBD>x - b<>x
*
* This function is minimized when its gradient
*
* (3) df = A<>x - b
*
* is zero, which is equivalent to (1). The minimization is carried out by
* generating a succession of search directions p.k and improved minimizers x.k.
* At each stage a quantity alfa.k is found that minimizes f(x.k + alfa.k<>p.k),
* and x.k+1 is set equal to the new point x.k + alfa.k<>p.k. The p.k and x.k are
* built up in such a way that x.k+1 is also the minimizer of f over the whole
* vector space of directions already taken, {p.1, p.2, . . . , p.k}. After N
* iterations you arrive at the minimizer over the entire vector space, i.e., the
* solution to (1).
*
* For a really good explanation of the method see:
*
* "An Introduction to the Conjugate Gradient Method Without the Agonizing Pain",
* Jonhathan Richard Shewchuk.
*
**/
static bool ConjugateGradientSolver(const sparse::Matrix &A, const FullVector &b, FullVector &x, float epsilon) {
xaDebugAssert(A.isSquare());
xaDebugAssert(A.width() == b.dimension());
xaDebugAssert(A.width() == x.dimension());
int i = 0;
const int D = A.width();
const int i_max = 4 * D; // Convergence should be linear, but in some cases, it's not.
FullVector r(D); // residual
FullVector p(D); // search direction
FullVector q(D); //
float delta_0;
float delta_old;
float delta_new;
float alpha;
float beta;
// r = b - A<>x;
sparse::copy(b, r);
sparse::sgemv(-1, A, x, 1, r);
// p = r;
sparse::copy(r, p);
delta_new = sparse::dot(r, r);
delta_0 = delta_new;
while (i < i_max && delta_new > epsilon * epsilon * delta_0) {
i++;
// q = A<>p
mult(A, p, q);
// alpha = delta_new / p<>q
alpha = delta_new / sparse::dot(p, q);
// x = alfa<66>p + x
sparse::saxpy(alpha, p, x);
if ((i & 31) == 0) { // recompute r after 32 steps
// r = b - A<>x
sparse::copy(b, r);
sparse::sgemv(-1, A, x, 1, r);
} else {
// r = r - alpha<68>q
sparse::saxpy(-alpha, q, r);
}
delta_old = delta_new;
delta_new = sparse::dot(r, r);
beta = delta_new / delta_old;
// p = beta<74>p + r
sparse::scal(beta, p);
sparse::saxpy(1, r, p);
}
return delta_new <= epsilon * epsilon * delta_0;
}
// Conjugate gradient with preconditioner.
static bool ConjugateGradientSolver(const JacobiPreconditioner &preconditioner, const sparse::Matrix &A, const FullVector &b, FullVector &x, float epsilon) {
xaDebugAssert(A.isSquare());
xaDebugAssert(A.width() == b.dimension());
xaDebugAssert(A.width() == x.dimension());
int i = 0;
const int D = A.width();
const int i_max = 4 * D; // Convergence should be linear, but in some cases, it's not.
FullVector r(D); // residual
FullVector p(D); // search direction
FullVector q(D); //
FullVector s(D); // preconditioned
float delta_0;
float delta_old;
float delta_new;
float alpha;
float beta;
// r = b - A<>x
sparse::copy(b, r);
sparse::sgemv(-1, A, x, 1, r);
// p = M^-1 <20> r
preconditioner.apply(r, p);
delta_new = sparse::dot(r, p);
delta_0 = delta_new;
while (i < i_max && delta_new > epsilon * epsilon * delta_0) {
i++;
// q = A<>p
mult(A, p, q);
// alpha = delta_new / p<>q
alpha = delta_new / sparse::dot(p, q);
// x = alfa<66>p + x
sparse::saxpy(alpha, p, x);
if ((i & 31) == 0) { // recompute r after 32 steps
// r = b - A<>x
sparse::copy(b, r);
sparse::sgemv(-1, A, x, 1, r);
} else {
// r = r - alfa<66>q
sparse::saxpy(-alpha, q, r);
}
// s = M^-1 <20> r
preconditioner.apply(r, s);
delta_old = delta_new;
delta_new = sparse::dot(r, s);
beta = delta_new / delta_old;
// p = s + beta<74>p
sparse::scal(beta, p);
sparse::saxpy(1, s, p);
}
return delta_new <= epsilon * epsilon * delta_0;
}
static bool SymmetricSolver(const sparse::Matrix &A, const FullVector &b, FullVector &x, float epsilon = 1e-5f) {
xaDebugAssert(A.height() == A.width());
xaDebugAssert(A.height() == b.dimension());
xaDebugAssert(b.dimension() == x.dimension());
JacobiPreconditioner jacobi(A, true);
return ConjugateGradientSolver(jacobi, A, b, x, epsilon);
}
};
namespace param {
class Atlas;
class Chart;
// Fast sweep in 3 directions
static bool findApproximateDiameterVertices(halfedge::Mesh *mesh, halfedge::Vertex **a, halfedge::Vertex **b) {
xaDebugAssert(mesh != NULL);
xaDebugAssert(a != NULL);
xaDebugAssert(b != NULL);
const uint32_t vertexCount = mesh->vertexCount();
halfedge::Vertex *minVertex[3];
halfedge::Vertex *maxVertex[3];
minVertex[0] = minVertex[1] = minVertex[2] = NULL;
maxVertex[0] = maxVertex[1] = maxVertex[2] = NULL;
for (uint32_t v = 1; v < vertexCount; v++) {
halfedge::Vertex *vertex = mesh->vertexAt(v);
xaDebugAssert(vertex != NULL);
if (vertex->isBoundary()) {
minVertex[0] = minVertex[1] = minVertex[2] = vertex;
maxVertex[0] = maxVertex[1] = maxVertex[2] = vertex;
break;
}
}
if (minVertex[0] == NULL) {
// Input mesh has not boundaries.
return false;
}
for (uint32_t v = 1; v < vertexCount; v++) {
halfedge::Vertex *vertex = mesh->vertexAt(v);
xaDebugAssert(vertex != NULL);
if (!vertex->isBoundary()) {
// Skip interior vertices.
continue;
}
if (vertex->pos.x < minVertex[0]->pos.x)
minVertex[0] = vertex;
else if (vertex->pos.x > maxVertex[0]->pos.x)
maxVertex[0] = vertex;
if (vertex->pos.y < minVertex[1]->pos.y)
minVertex[1] = vertex;
else if (vertex->pos.y > maxVertex[1]->pos.y)
maxVertex[1] = vertex;
if (vertex->pos.z < minVertex[2]->pos.z)
minVertex[2] = vertex;
else if (vertex->pos.z > maxVertex[2]->pos.z)
maxVertex[2] = vertex;
}
float lengths[3];
for (int i = 0; i < 3; i++) {
lengths[i] = length(minVertex[i]->pos - maxVertex[i]->pos);
}
if (lengths[0] > lengths[1] && lengths[0] > lengths[2]) {
*a = minVertex[0];
*b = maxVertex[0];
} else if (lengths[1] > lengths[2]) {
*a = minVertex[1];
*b = maxVertex[1];
} else {
*a = minVertex[2];
*b = maxVertex[2];
}
return true;
}
// Conformal relations from Brecht Van Lommel (based on ABF):
static float vec_angle_cos(Vector3::Arg v1, Vector3::Arg v2, Vector3::Arg v3) {
Vector3 d1 = v1 - v2;
Vector3 d2 = v3 - v2;
return clamp(dot(d1, d2) / (length(d1) * length(d2)), -1.0f, 1.0f);
}
static float vec_angle(Vector3::Arg v1, Vector3::Arg v2, Vector3::Arg v3) {
float dot = vec_angle_cos(v1, v2, v3);
return acosf(dot);
}
static void triangle_angles(Vector3::Arg v1, Vector3::Arg v2, Vector3::Arg v3, float *a1, float *a2, float *a3) {
*a1 = vec_angle(v3, v1, v2);
*a2 = vec_angle(v1, v2, v3);
*a3 = PI - *a2 - *a1;
}
static void setup_abf_relations(sparse::Matrix &A, int row, const halfedge::Vertex *v0, const halfedge::Vertex *v1, const halfedge::Vertex *v2) {
int id0 = v0->id;
int id1 = v1->id;
int id2 = v2->id;
Vector3 p0 = v0->pos;
Vector3 p1 = v1->pos;
Vector3 p2 = v2->pos;
// @@ IC: Wouldn't it be more accurate to return cos and compute 1-cos^2?
// It does indeed seem to be a little bit more robust.
// @@ Need to revisit this more carefully!
float a0, a1, a2;
triangle_angles(p0, p1, p2, &a0, &a1, &a2);
float s0 = sinf(a0);
float s1 = sinf(a1);
float s2 = sinf(a2);
if (s1 > s0 && s1 > s2) {
std::swap(s1, s2);
std::swap(s0, s1);
std::swap(a1, a2);
std::swap(a0, a1);
std::swap(id1, id2);
std::swap(id0, id1);
} else if (s0 > s1 && s0 > s2) {
std::swap(s0, s2);
std::swap(s0, s1);
std::swap(a0, a2);
std::swap(a0, a1);
std::swap(id0, id2);
std::swap(id0, id1);
}
float c0 = cosf(a0);
float ratio = (s2 == 0.0f) ? 1.0f : s1 / s2;
float cosine = c0 * ratio;
float sine = s0 * ratio;
// Note : 2*id + 0 --> u
// 2*id + 1 --> v
int u0_id = 2 * id0 + 0;
int v0_id = 2 * id0 + 1;
int u1_id = 2 * id1 + 0;
int v1_id = 2 * id1 + 1;
int u2_id = 2 * id2 + 0;
int v2_id = 2 * id2 + 1;
// Real part
A.setCoefficient(u0_id, 2 * row + 0, cosine - 1.0f);
A.setCoefficient(v0_id, 2 * row + 0, -sine);
A.setCoefficient(u1_id, 2 * row + 0, -cosine);
A.setCoefficient(v1_id, 2 * row + 0, sine);
A.setCoefficient(u2_id, 2 * row + 0, 1);
// Imaginary part
A.setCoefficient(u0_id, 2 * row + 1, sine);
A.setCoefficient(v0_id, 2 * row + 1, cosine - 1.0f);
A.setCoefficient(u1_id, 2 * row + 1, -sine);
A.setCoefficient(v1_id, 2 * row + 1, -cosine);
A.setCoefficient(v2_id, 2 * row + 1, 1);
}
bool computeLeastSquaresConformalMap(halfedge::Mesh *mesh) {
xaDebugAssert(mesh != NULL);
// For this to work properly, mesh should not have colocals that have the same
// attributes, unless you want the vertices to actually have different texcoords.
const uint32_t vertexCount = mesh->vertexCount();
const uint32_t D = 2 * vertexCount;
const uint32_t N = 2 * halfedge::countMeshTriangles(mesh);
// N is the number of equations (one per triangle)
// D is the number of variables (one per vertex; there are 2 pinned vertices).
if (N < D - 4) {
return false;
}
sparse::Matrix A(D, N);
FullVector b(N);
FullVector x(D);
// Fill b:
b.fill(0.0f);
// Fill x:
halfedge::Vertex *v0;
halfedge::Vertex *v1;
if (!findApproximateDiameterVertices(mesh, &v0, &v1)) {
// Mesh has no boundaries.
return false;
}
if (v0->tex == v1->tex) {
// LSCM expects an existing parameterization.
return false;
}
for (uint32_t v = 0; v < vertexCount; v++) {
halfedge::Vertex *vertex = mesh->vertexAt(v);
xaDebugAssert(vertex != NULL);
// Initial solution.
x[2 * v + 0] = vertex->tex.x;
x[2 * v + 1] = vertex->tex.y;
}
// Fill A:
const uint32_t faceCount = mesh->faceCount();
for (uint32_t f = 0, t = 0; f < faceCount; f++) {
const halfedge::Face *face = mesh->faceAt(f);
xaDebugAssert(face != NULL);
xaDebugAssert(face->edgeCount() == 3);
const halfedge::Vertex *vertex0 = NULL;
for (halfedge::Face::ConstEdgeIterator it(face->edges()); !it.isDone(); it.advance()) {
const halfedge::Edge *edge = it.current();
xaAssert(edge != NULL);
if (vertex0 == NULL) {
vertex0 = edge->vertex;
} else if (edge->next->vertex != vertex0) {
const halfedge::Vertex *vertex1 = edge->from();
const halfedge::Vertex *vertex2 = edge->to();
setup_abf_relations(A, t, vertex0, vertex1, vertex2);
//setup_conformal_map_relations(A, t, vertex0, vertex1, vertex2);
t++;
}
}
}
const uint32_t lockedParameters[] = {
2 * v0->id + 0,
2 * v0->id + 1,
2 * v1->id + 0,
2 * v1->id + 1
};
// Solve
Solver::LeastSquaresSolver(A, b, x, lockedParameters, 4, 0.000001f);
// Map x back to texcoords:
for (uint32_t v = 0; v < vertexCount; v++) {
halfedge::Vertex *vertex = mesh->vertexAt(v);
xaDebugAssert(vertex != NULL);
vertex->tex = Vector2(x[2 * v + 0], x[2 * v + 1]);
}
return true;
}
bool computeOrthogonalProjectionMap(halfedge::Mesh *mesh) {
Vector3 axis[2];
uint32_t vertexCount = mesh->vertexCount();
std::vector<Vector3> points(vertexCount);
points.resize(vertexCount);
for (uint32_t i = 0; i < vertexCount; i++) {
points[i] = mesh->vertexAt(i)->pos;
}
// Avoid redundant computations.
float matrix[6];
Fit::computeCovariance(vertexCount, points.data(), matrix);
if (matrix[0] == 0 && matrix[3] == 0 && matrix[5] == 0) {
return false;
}
float eigenValues[3];
Vector3 eigenVectors[3];
if (!Fit::eigenSolveSymmetric3(matrix, eigenValues, eigenVectors)) {
return false;
}
axis[0] = normalize(eigenVectors[0]);
axis[1] = normalize(eigenVectors[1]);
// Project vertices to plane.
for (halfedge::Mesh::VertexIterator it(mesh->vertices()); !it.isDone(); it.advance()) {
halfedge::Vertex *vertex = it.current();
vertex->tex.x = dot(axis[0], vertex->pos);
vertex->tex.y = dot(axis[1], vertex->pos);
}
return true;
}
void computeSingleFaceMap(halfedge::Mesh *mesh) {
xaDebugAssert(mesh != NULL);
xaDebugAssert(mesh->faceCount() == 1);
halfedge::Face *face = mesh->faceAt(0);
xaAssert(face != NULL);
Vector3 p0 = face->edge->from()->pos;
Vector3 p1 = face->edge->to()->pos;
Vector3 X = normalizeSafe(p1 - p0, Vector3(0.0f), 0.0f);
Vector3 Z = face->normal();
Vector3 Y = normalizeSafe(cross(Z, X), Vector3(0.0f), 0.0f);
uint32_t i = 0;
for (halfedge::Face::EdgeIterator it(face->edges()); !it.isDone(); it.advance(), i++) {
halfedge::Vertex *vertex = it.vertex();
xaAssert(vertex != NULL);
if (i == 0) {
vertex->tex = Vector2(0);
} else {
Vector3 pn = vertex->pos;
float xn = dot((pn - p0), X);
float yn = dot((pn - p0), Y);
vertex->tex = Vector2(xn, yn);
}
}
}
// Dummy implementation of a priority queue using sort at insertion.
// - Insertion is o(n)
// - Smallest element goes at the end, so that popping it is o(1).
// - 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 {
PriorityQueue(uint32_t size = UINT_MAX) :
maxSize(size) {}
void push(float priority, uint32_t face) {
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.insert(pairs.begin() + i, p);
if (pairs.size() > maxSize) {
pairs.erase(pairs.begin());
}
}
// push face out of order, to be sorted later.
void push(uint32_t face) {
Pair p = { 0.0f, face };
pairs.push_back(p);
}
uint32_t pop() {
uint32_t f = pairs.back().face;
pairs.pop_back();
return f;
}
void sort() {
//sort(pairs); // @@ My intro sort appears to be much slower than it should!
std::sort(pairs.begin(), pairs.end());
}
void clear() {
pairs.clear();
}
uint32_t count() const {
return pairs.size();
}
float firstPriority() const {
return pairs.back().priority;
}
const uint32_t maxSize;
struct Pair {
bool operator<(const Pair &p) const {
return priority > p.priority; // !! Sort in inverse priority order!
}
float priority;
uint32_t face;
};
std::vector<Pair> pairs;
};
struct ChartBuildData {
ChartBuildData(int p_id) :
id(p_id) {
planeNormal = Vector3(0);
centroid = Vector3(0);
coneAxis = Vector3(0);
coneAngle = 0;
area = 0;
boundaryLength = 0;
normalSum = Vector3(0);
centroidSum = Vector3(0);
}
int id;
// Proxy info:
Vector3 planeNormal;
Vector3 centroid;
Vector3 coneAxis;
float coneAngle;
float area;
float boundaryLength;
Vector3 normalSum;
Vector3 centroidSum;
std::vector<uint32_t> seeds; // @@ These could be a pointers to the halfedge faces directly.
std::vector<uint32_t> faces;
PriorityQueue candidates;
};
struct AtlasBuilder {
AtlasBuilder(const halfedge::Mesh *m) :
mesh(m),
facesLeft(m->faceCount()) {
const uint32_t faceCount = m->faceCount();
faceChartArray.resize(faceCount, -1);
faceCandidateArray.resize(faceCount, (uint32_t)-1);
// @@ Floyd for the whole mesh is too slow. We could compute floyd progressively per patch as the patch grows. We need a better solution to compute most central faces.
//computeShortestPaths();
// Precompute edge lengths and face areas.
uint32_t edgeCount = m->edgeCount();
edgeLengths.resize(edgeCount);
for (uint32_t i = 0; i < edgeCount; i++) {
uint32_t id = m->edgeAt(i)->id;
xaDebugAssert(id / 2 == i);
#ifdef NDEBUG
id = 0; // silence unused parameter warning
#endif
edgeLengths[i] = m->edgeAt(i)->length();
}
faceAreas.resize(faceCount);
for (uint32_t i = 0; i < faceCount; i++) {
faceAreas[i] = m->faceAt(i)->area();
}
}
~AtlasBuilder() {
const uint32_t chartCount = chartArray.size();
for (uint32_t i = 0; i < chartCount; i++) {
delete chartArray[i];
}
}
void markUnchartedFaces(const std::vector<uint32_t> &unchartedFaces) {
const uint32_t unchartedFaceCount = unchartedFaces.size();
for (uint32_t i = 0; i < unchartedFaceCount; i++) {
uint32_t f = unchartedFaces[i];
faceChartArray[f] = -2;
//faceCandidateArray[f] = -2; // @@ ?
removeCandidate(f);
}
xaDebugAssert(facesLeft >= unchartedFaceCount);
facesLeft -= unchartedFaceCount;
}
void computeShortestPaths() {
const uint32_t faceCount = mesh->faceCount();
shortestPaths.resize(faceCount * faceCount, FLT_MAX);
// Fill edges:
for (uint32_t i = 0; i < faceCount; i++) {
shortestPaths[i * faceCount + i] = 0.0f;
const halfedge::Face *face_i = mesh->faceAt(i);
Vector3 centroid_i = face_i->centroid();
for (halfedge::Face::ConstEdgeIterator it(face_i->edges()); !it.isDone(); it.advance()) {
const halfedge::Edge *edge = it.current();
if (!edge->isBoundary()) {
const halfedge::Face *face_j = edge->pair->face;
uint32_t j = face_j->id;
Vector3 centroid_j = face_j->centroid();
shortestPaths[i * faceCount + j] = shortestPaths[j * faceCount + i] = length(centroid_i - centroid_j);
}
}
}
// Use Floyd-Warshall algorithm to compute all paths:
for (uint32_t k = 0; k < faceCount; k++) {
for (uint32_t i = 0; i < faceCount; i++) {
for (uint32_t j = 0; j < faceCount; j++) {
shortestPaths[i * faceCount + j] = std::min(shortestPaths[i * faceCount + j], shortestPaths[i * faceCount + k] + shortestPaths[k * faceCount + j]);
}
}
}
}
void placeSeeds(float threshold, uint32_t maxSeedCount) {
// Instead of using a predefiened number of seeds:
// - Add seeds one by one, growing chart until a certain treshold.
// - Undo charts and restart growing process.
// @@ How can we give preference to faces far from sharp features as in the LSCM paper?
// - those points can be found using a simple flood filling algorithm.
// - how do we weight the probabilities?
for (uint32_t i = 0; i < maxSeedCount; i++) {
if (facesLeft == 0) {
// No faces left, stop creating seeds.
break;
}
createRandomChart(threshold);
}
}
void createRandomChart(float threshold) {
ChartBuildData *chart = new ChartBuildData(chartArray.size());
chartArray.push_back(chart);
// Pick random face that is not used by any chart yet.
uint32_t randomFaceIdx = rand.getRange(facesLeft - 1);
uint32_t i = 0;
for (uint32_t f = 0; f != randomFaceIdx; f++, i++) {
while (faceChartArray[i] != -1)
i++;
}
while (faceChartArray[i] != -1)
i++;
chart->seeds.push_back(i);
addFaceToChart(chart, i, true);
// Grow the chart as much as possible within the given threshold.
growChart(chart, threshold * 0.5f, facesLeft);
//growCharts(threshold - threshold * 0.75f / chartCount(), facesLeft);
}
void addFaceToChart(ChartBuildData *chart, uint32_t f, bool recomputeProxy = false) {
// Add face to chart.
chart->faces.push_back(f);
xaDebugAssert(faceChartArray[f] == -1);
faceChartArray[f] = chart->id;
facesLeft--;
// Update area and boundary length.
chart->area = evaluateChartArea(chart, f);
chart->boundaryLength = evaluateBoundaryLength(chart, f);
chart->normalSum = evaluateChartNormalSum(chart, f);
chart->centroidSum = evaluateChartCentroidSum(chart, f);
if (recomputeProxy) {
// Update proxy and candidate's priorities.
updateProxy(chart);
}
// Update candidates.
removeCandidate(f);
updateCandidates(chart, f);
updatePriorities(chart);
}
// Returns true if any of the charts can grow more.
bool growCharts(float threshold, uint32_t faceCount) {
// Using one global list.
faceCount = std::min(faceCount, facesLeft);
for (uint32_t i = 0; i < faceCount; i++) {
const Candidate &candidate = getBestCandidate();
if (candidate.metric > threshold) {
return false; // Can't grow more.
}
addFaceToChart(candidate.chart, candidate.face);
}
return facesLeft != 0; // Can continue growing.
}
bool growChart(ChartBuildData *chart, float threshold, uint32_t faceCount) {
// Try to add faceCount faces within threshold to chart.
for (uint32_t i = 0; i < faceCount;) {
if (chart->candidates.count() == 0 || chart->candidates.firstPriority() > threshold) {
return false;
}
uint32_t f = chart->candidates.pop();
if (faceChartArray[f] == -1) {
addFaceToChart(chart, f);
i++;
}
}
if (chart->candidates.count() == 0 || chart->candidates.firstPriority() > threshold) {
return false;
}
return true;
}
void resetCharts() {
const uint32_t faceCount = mesh->faceCount();
for (uint32_t i = 0; i < faceCount; i++) {
faceChartArray[i] = -1;
faceCandidateArray[i] = (uint32_t)-1;
}
facesLeft = faceCount;
candidateArray.clear();
const uint32_t chartCount = chartArray.size();
for (uint32_t i = 0; i < chartCount; i++) {
ChartBuildData *chart = chartArray[i];
const uint32_t seed = chart->seeds.back();
chart->area = 0.0f;
chart->boundaryLength = 0.0f;
chart->normalSum = Vector3(0);
chart->centroidSum = Vector3(0);
chart->faces.clear();
chart->candidates.clear();
addFaceToChart(chart, seed);
}
}
void updateCandidates(ChartBuildData *chart, uint32_t f) {
const halfedge::Face *face = mesh->faceAt(f);
// Traverse neighboring faces, add the ones that do not belong to any chart yet.
for (halfedge::Face::ConstEdgeIterator it(face->edges()); !it.isDone(); it.advance()) {
const halfedge::Edge *edge = it.current()->pair;
if (!edge->isBoundary()) {
uint32_t faceId = edge->face->id;
if (faceChartArray[faceId] == -1) {
chart->candidates.push(faceId);
}
}
}
}
void updateProxies() {
const uint32_t chartCount = chartArray.size();
for (uint32_t i = 0; i < chartCount; i++) {
updateProxy(chartArray[i]);
}
}
void updateProxy(ChartBuildData *chart) {
//#pragma message(NV_FILE_LINE "TODO: Use best fit plane instead of average normal.")
chart->planeNormal = normalizeSafe(chart->normalSum, Vector3(0), 0.0f);
chart->centroid = chart->centroidSum / float(chart->faces.size());
}
bool relocateSeeds() {
bool anySeedChanged = false;
const uint32_t chartCount = chartArray.size();
for (uint32_t i = 0; i < chartCount; i++) {
if (relocateSeed(chartArray[i])) {
anySeedChanged = true;
}
}
return anySeedChanged;
}
bool relocateSeed(ChartBuildData *chart) {
Vector3 centroid = computeChartCentroid(chart);
const uint32_t N = 10; // @@ Hardcoded to 10?
PriorityQueue bestTriangles(N);
// Find the first N triangles that fit the proxy best.
const uint32_t faceCount = chart->faces.size();
for (uint32_t i = 0; i < faceCount; i++) {
float priority = evaluateProxyFitMetric(chart, chart->faces[i]);
bestTriangles.push(priority, chart->faces[i]);
}
// Of those, choose the most central triangle.
uint32_t mostCentral;
float maxDistance = -1;
const uint32_t bestCount = bestTriangles.count();
for (uint32_t i = 0; i < bestCount; i++) {
const halfedge::Face *face = mesh->faceAt(bestTriangles.pairs[i].face);
Vector3 faceCentroid = face->triangleCenter();
float distance = length(centroid - faceCentroid);
if (distance > maxDistance) {
maxDistance = distance;
mostCentral = bestTriangles.pairs[i].face;
}
}
xaDebugAssert(maxDistance >= 0);
// In order to prevent k-means cyles we record all the previously chosen seeds.
uint32_t index = std::find(chart->seeds.begin(), chart->seeds.end(), mostCentral) - chart->seeds.begin();
if (index < chart->seeds.size()) {
// Move new seed to the end of the seed array.
uint32_t last = chart->seeds.size() - 1;
std::swap(chart->seeds[index], chart->seeds[last]);
return false;
} else {
// Append new seed.
chart->seeds.push_back(mostCentral);
return true;
}
}
void updatePriorities(ChartBuildData *chart) {
// Re-evaluate candidate priorities.
uint32_t candidateCount = chart->candidates.count();
for (uint32_t i = 0; i < candidateCount; i++) {
chart->candidates.pairs[i].priority = evaluatePriority(chart, chart->candidates.pairs[i].face);
if (faceChartArray[chart->candidates.pairs[i].face] == -1) {
updateCandidate(chart, chart->candidates.pairs[i].face, chart->candidates.pairs[i].priority);
}
}
// Sort candidates.
chart->candidates.sort();
}
// Evaluate combined metric.
float evaluatePriority(ChartBuildData *chart, uint32_t face) {
// Estimate boundary length and area:
float newBoundaryLength = evaluateBoundaryLength(chart, face);
float newChartArea = evaluateChartArea(chart, face);
float F = evaluateProxyFitMetric(chart, face);
float C = evaluateRoundnessMetric(chart, face, newBoundaryLength, newChartArea);
float P = evaluateStraightnessMetric(chart, face);
// Penalize faces that cross seams, reward faces that close seams or reach boundaries.
float N = evaluateNormalSeamMetric(chart, face);
float T = evaluateTextureSeamMetric(chart, face);
//float R = evaluateCompletenessMetric(chart, face);
//float D = evaluateDihedralAngleMetric(chart, face);
// @@ Add a metric based on local dihedral angle.
// @@ Tweaking the normal and texture seam metrics.
// - Cause more impedance. Never cross 90 degree edges.
// -
float cost = float(
options.proxyFitMetricWeight * F +
options.roundnessMetricWeight * C +
options.straightnessMetricWeight * P +
options.normalSeamMetricWeight * N +
options.textureSeamMetricWeight * T);
// Enforce limits strictly:
if (newChartArea > options.maxChartArea) cost = FLT_MAX;
if (newBoundaryLength > options.maxBoundaryLength) cost = FLT_MAX;
// Make sure normal seams are fully respected:
if (options.normalSeamMetricWeight >= 1000 && N != 0) cost = FLT_MAX;
xaAssert(std::isfinite(cost));
return cost;
}
// Returns a value in [0-1].
float evaluateProxyFitMetric(ChartBuildData *chart, uint32_t f) {
const halfedge::Face *face = mesh->faceAt(f);
Vector3 faceNormal = face->triangleNormal();
// Use plane fitting metric for now:
return 1 - dot(faceNormal, chart->planeNormal); // @@ normal deviations should be weighted by face area
}
float evaluateRoundnessMetric(ChartBuildData *chart, uint32_t /*face*/, float newBoundaryLength, float newChartArea) {
float roundness = square(chart->boundaryLength) / chart->area;
float newRoundness = square(newBoundaryLength) / newChartArea;
if (newRoundness > roundness) {
return square(newBoundaryLength) / (newChartArea * 4 * PI);
} else {
// Offer no impedance to faces that improve roundness.
return 0;
}
}
float evaluateStraightnessMetric(ChartBuildData *chart, uint32_t f) {
float l_out = 0.0f;
float l_in = 0.0f;
const halfedge::Face *face = mesh->faceAt(f);
for (halfedge::Face::ConstEdgeIterator it(face->edges()); !it.isDone(); it.advance()) {
const halfedge::Edge *edge = it.current();
float l = edgeLengths[edge->id / 2];
if (edge->isBoundary()) {
l_out += l;
} else {
uint32_t neighborFaceId = edge->pair->face->id;
if (faceChartArray[neighborFaceId] != chart->id) {
l_out += l;
} else {
l_in += l;
}
}
}
xaDebugAssert(l_in != 0.0f); // Candidate face must be adjacent to chart. @@ This is not true if the input mesh has zero-length edges.
float ratio = (l_out - l_in) / (l_out + l_in);
return std::min(ratio, 0.0f); // Only use the straightness metric to close gaps.
}
float evaluateNormalSeamMetric(ChartBuildData *chart, uint32_t f) {
float seamFactor = 0.0f;
float totalLength = 0.0f;
const halfedge::Face *face = mesh->faceAt(f);
for (halfedge::Face::ConstEdgeIterator it(face->edges()); !it.isDone(); it.advance()) {
const halfedge::Edge *edge = it.current();
if (edge->isBoundary()) {
continue;
}
const uint32_t neighborFaceId = edge->pair->face->id;
if (faceChartArray[neighborFaceId] != chart->id) {
continue;
}
//float l = edge->length();
float l = edgeLengths[edge->id / 2];
totalLength += l;
if (!edge->isSeam()) {
continue;
}
// Make sure it's a normal seam.
if (edge->isNormalSeam()) {
float d0 = clamp(dot(edge->vertex->nor, edge->pair->next->vertex->nor), 0.0f, 1.0f);
float d1 = clamp(dot(edge->next->vertex->nor, edge->pair->vertex->nor), 0.0f, 1.0f);
l *= 1 - (d0 + d1) * 0.5f;
seamFactor += l;
}
}
if (seamFactor == 0) return 0.0f;
return seamFactor / totalLength;
}
float evaluateTextureSeamMetric(ChartBuildData *chart, uint32_t f) {
float seamLength = 0.0f;
float totalLength = 0.0f;
const halfedge::Face *face = mesh->faceAt(f);
for (halfedge::Face::ConstEdgeIterator it(face->edges()); !it.isDone(); it.advance()) {
const halfedge::Edge *edge = it.current();
if (edge->isBoundary()) {
continue;
}
const uint32_t neighborFaceId = edge->pair->face->id;
if (faceChartArray[neighborFaceId] != chart->id) {
continue;
}
//float l = edge->length();
float l = edgeLengths[edge->id / 2];
totalLength += l;
if (!edge->isSeam()) {
continue;
}
// Make sure it's a texture seam.
if (edge->isTextureSeam()) {
seamLength += l;
}
}
if (seamLength == 0.0f) {
return 0.0f; // Avoid division by zero.
}
return seamLength / totalLength;
}
float evaluateChartArea(ChartBuildData *chart, uint32_t f) {
const halfedge::Face *face = mesh->faceAt(f);
return chart->area + faceAreas[face->id];
}
float evaluateBoundaryLength(ChartBuildData *chart, uint32_t f) {
float boundaryLength = chart->boundaryLength;
// Add new edges, subtract edges shared with the chart.
const halfedge::Face *face = mesh->faceAt(f);
for (halfedge::Face::ConstEdgeIterator it(face->edges()); !it.isDone(); it.advance()) {
const halfedge::Edge *edge = it.current();
//float edgeLength = edge->length();
float edgeLength = edgeLengths[edge->id / 2];
if (edge->isBoundary()) {
boundaryLength += edgeLength;
} else {
uint32_t neighborFaceId = edge->pair->face->id;
if (faceChartArray[neighborFaceId] != chart->id) {
boundaryLength += edgeLength;
} else {
boundaryLength -= edgeLength;
}
}
}
return std::max(0.0f, boundaryLength); // @@ Hack!
}
Vector3 evaluateChartNormalSum(ChartBuildData *chart, uint32_t f) {
const halfedge::Face *face = mesh->faceAt(f);
return chart->normalSum + face->triangleNormalAreaScaled();
}
Vector3 evaluateChartCentroidSum(ChartBuildData *chart, uint32_t f) {
const halfedge::Face *face = mesh->faceAt(f);
return chart->centroidSum + face->centroid();
}
Vector3 computeChartCentroid(const ChartBuildData *chart) {
Vector3 centroid(0);
const uint32_t faceCount = chart->faces.size();
for (uint32_t i = 0; i < faceCount; i++) {
const halfedge::Face *face = mesh->faceAt(chart->faces[i]);
centroid += face->triangleCenter();
}
return centroid / float(faceCount);
}
void fillHoles(float threshold) {
while (facesLeft > 0)
createRandomChart(threshold);
}
void mergeCharts() {
std::vector<float> sharedBoundaryLengths;
const uint32_t chartCount = chartArray.size();
for (int c = chartCount - 1; c >= 0; c--) {
sharedBoundaryLengths.clear();
sharedBoundaryLengths.resize(chartCount, 0.0f);
ChartBuildData *chart = chartArray[c];
float externalBoundary = 0.0f;
const uint32_t faceCount = chart->faces.size();
for (uint32_t i = 0; i < faceCount; i++) {
uint32_t f = chart->faces[i];
const halfedge::Face *face = mesh->faceAt(f);
for (halfedge::Face::ConstEdgeIterator it(face->edges()); !it.isDone(); it.advance()) {
const halfedge::Edge *edge = it.current();
//float l = edge->length();
float l = edgeLengths[edge->id / 2];
if (edge->isBoundary()) {
externalBoundary += l;
} else {
uint32_t neighborFace = edge->pair->face->id;
uint32_t neighborChart = faceChartArray[neighborFace];
if (neighborChart != (uint32_t)c) {
if ((edge->isSeam() && (edge->isNormalSeam() || edge->isTextureSeam())) || neighborChart == -2) {
externalBoundary += l;
} else {
sharedBoundaryLengths[neighborChart] += l;
}
}
}
}
}
for (int cc = chartCount - 1; cc >= 0; cc--) {
if (cc == c)
continue;
ChartBuildData *chart2 = chartArray[cc];
if (chart2 == NULL)
continue;
if (sharedBoundaryLengths[cc] > 0.8 * std::max(0.0f, chart->boundaryLength - externalBoundary)) {
// Try to avoid degenerate configurations.
if (chart2->boundaryLength > sharedBoundaryLengths[cc]) {
if (dot(chart2->planeNormal, chart->planeNormal) > -0.25) {
mergeChart(chart2, chart, sharedBoundaryLengths[cc]);
delete chart;
chartArray[c] = NULL;
break;
}
}
}
if (sharedBoundaryLengths[cc] > 0.20 * std::max(0.0f, chart->boundaryLength - externalBoundary)) {
// Compare proxies.
if (dot(chart2->planeNormal, chart->planeNormal) > 0) {
mergeChart(chart2, chart, sharedBoundaryLengths[cc]);
delete chart;
chartArray[c] = NULL;
break;
}
}
}
}
// Remove deleted charts.
for (int c = 0; c < int32_t(chartArray.size()); /*do not increment if removed*/) {
if (chartArray[c] == NULL) {
chartArray.erase(chartArray.begin() + c);
// Update faceChartArray.
const uint32_t faceCount = faceChartArray.size();
for (uint32_t i = 0; i < faceCount; i++) {
xaDebugAssert(faceChartArray[i] != -1);
xaDebugAssert(faceChartArray[i] != c);
xaDebugAssert(faceChartArray[i] <= int32_t(chartArray.size()));
if (faceChartArray[i] > c) {
faceChartArray[i]--;
}
}
} else {
chartArray[c]->id = c;
c++;
}
}
}
// @@ Cleanup.
struct Candidate {
uint32_t face;
ChartBuildData *chart;
float metric;
};
// @@ Get N best candidates in one pass.
const Candidate &getBestCandidate() const {
uint32_t best = 0;
float bestCandidateMetric = FLT_MAX;
const uint32_t candidateCount = candidateArray.size();
xaAssert(candidateCount > 0);
for (uint32_t i = 0; i < candidateCount; i++) {
const Candidate &candidate = candidateArray[i];
if (candidate.metric < bestCandidateMetric) {
bestCandidateMetric = candidate.metric;
best = i;
}
}
return candidateArray[best];
}
void removeCandidate(uint32_t f) {
int c = faceCandidateArray[f];
if (c != -1) {
faceCandidateArray[f] = (uint32_t)-1;
if (c == int(candidateArray.size() - 1)) {
candidateArray.pop_back();
} else {
// Replace with last.
candidateArray[c] = candidateArray[candidateArray.size() - 1];
candidateArray.pop_back();
faceCandidateArray[candidateArray[c].face] = c;
}
}
}
void updateCandidate(ChartBuildData *chart, uint32_t f, float metric) {
if (faceCandidateArray[f] == -1) {
const uint32_t index = candidateArray.size();
faceCandidateArray[f] = index;
candidateArray.resize(index + 1);
candidateArray[index].face = f;
candidateArray[index].chart = chart;
candidateArray[index].metric = metric;
} else {
int c = faceCandidateArray[f];
xaDebugAssert(c != -1);
Candidate &candidate = candidateArray[c];
xaDebugAssert(candidate.face == f);
if (metric < candidate.metric || chart == candidate.chart) {
candidate.metric = metric;
candidate.chart = chart;
}
}
}
void mergeChart(ChartBuildData *owner, ChartBuildData *chart, float sharedBoundaryLength) {
const uint32_t faceCount = chart->faces.size();
for (uint32_t i = 0; i < faceCount; i++) {
uint32_t f = chart->faces[i];
xaDebugAssert(faceChartArray[f] == chart->id);
faceChartArray[f] = owner->id;
owner->faces.push_back(f);
}
// Update adjacencies?
owner->area += chart->area;
owner->boundaryLength += chart->boundaryLength - sharedBoundaryLength;
owner->normalSum += chart->normalSum;
owner->centroidSum += chart->centroidSum;
updateProxy(owner);
}
uint32_t chartCount() const { return chartArray.size(); }
const std::vector<uint32_t> &chartFaces(uint32_t i) const { return chartArray[i]->faces; }
const halfedge::Mesh *mesh;
uint32_t facesLeft;
std::vector<int> faceChartArray;
std::vector<ChartBuildData *> chartArray;
std::vector<float> shortestPaths;
std::vector<float> edgeLengths;
std::vector<float> faceAreas;
std::vector<Candidate> candidateArray; //
std::vector<uint32_t> faceCandidateArray; // Map face index to candidate index.
MTRand rand;
CharterOptions options;
};
/// A chart is a connected set of faces with a certain topology (usually a disk).
class Chart {
public:
Chart() :
m_isDisk(false),
m_isVertexMapped(false) {}
void build(const halfedge::Mesh *originalMesh, const std::vector<uint32_t> &faceArray) {
// Copy face indices.
m_faceArray = faceArray;
const uint32_t meshVertexCount = originalMesh->vertexCount();
m_chartMesh.reset(new halfedge::Mesh());
m_unifiedMesh.reset(new halfedge::Mesh());
std::vector<uint32_t> chartMeshIndices(meshVertexCount, (uint32_t)~0);
std::vector<uint32_t> unifiedMeshIndices(meshVertexCount, (uint32_t)~0);
// Add vertices.
const uint32_t faceCount = faceArray.size();
for (uint32_t f = 0; f < faceCount; f++) {
const halfedge::Face *face = originalMesh->faceAt(faceArray[f]);
xaDebugAssert(face != NULL);
for (halfedge::Face::ConstEdgeIterator it(face->edges()); !it.isDone(); it.advance()) {
const halfedge::Vertex *vertex = it.current()->vertex;
const halfedge::Vertex *unifiedVertex = vertex->firstColocal();
if (unifiedMeshIndices[unifiedVertex->id] == ~0) {
unifiedMeshIndices[unifiedVertex->id] = m_unifiedMesh->vertexCount();
xaDebugAssert(vertex->pos == unifiedVertex->pos);
m_unifiedMesh->addVertex(vertex->pos);
}
if (chartMeshIndices[vertex->id] == ~0) {
chartMeshIndices[vertex->id] = m_chartMesh->vertexCount();
m_chartToOriginalMap.push_back(vertex->original_id);
m_chartToUnifiedMap.push_back(unifiedMeshIndices[unifiedVertex->id]);
halfedge::Vertex *v = m_chartMesh->addVertex(vertex->pos);
v->nor = vertex->nor;
v->tex = vertex->tex;
}
}
}
// This is ignoring the canonical map:
// - Is it really necessary to link colocals?
m_chartMesh->linkColocals();
//m_unifiedMesh->linkColocals(); // Not strictly necessary, no colocals in the unified mesh. # Wrong.
// This check is not valid anymore, if the original mesh vertices were linked with a canonical map, then it might have
// some colocal vertices that were unlinked. So, the unified mesh might have some duplicate vertices, because firstColocal()
// is not guaranteed to return the same vertex for two colocal vertices.
//xaAssert(m_chartMesh->colocalVertexCount() == m_unifiedMesh->vertexCount());
// Is that OK? What happens in meshes were that happens? Does anything break? Apparently not...
std::vector<uint32_t> faceIndices;
faceIndices.reserve(7);
// Add faces.
for (uint32_t f = 0; f < faceCount; f++) {
const halfedge::Face *face = originalMesh->faceAt(faceArray[f]);
xaDebugAssert(face != NULL);
faceIndices.clear();
for (halfedge::Face::ConstEdgeIterator it(face->edges()); !it.isDone(); it.advance()) {
const halfedge::Vertex *vertex = it.current()->vertex;
xaDebugAssert(vertex != NULL);
faceIndices.push_back(chartMeshIndices[vertex->id]);
}
m_chartMesh->addFace(faceIndices);
faceIndices.clear();
for (halfedge::Face::ConstEdgeIterator it(face->edges()); !it.isDone(); it.advance()) {
const halfedge::Vertex *vertex = it.current()->vertex;
xaDebugAssert(vertex != NULL);
vertex = vertex->firstColocal();
faceIndices.push_back(unifiedMeshIndices[vertex->id]);
}
m_unifiedMesh->addFace(faceIndices);
}
m_chartMesh->linkBoundary();
m_unifiedMesh->linkBoundary();
//exportMesh(m_unifiedMesh.ptr(), "debug_input.obj");
if (m_unifiedMesh->splitBoundaryEdges()) {
m_unifiedMesh.reset(halfedge::unifyVertices(m_unifiedMesh.get()));
}
//exportMesh(m_unifiedMesh.ptr(), "debug_split.obj");
// Closing the holes is not always the best solution and does not fix all the problems.
// We need to do some analysis of the holes and the genus to:
// - Find cuts that reduce genus.
// - Find cuts to connect holes.
// - Use minimal spanning trees or seamster.
if (!closeHoles()) {
/*static int pieceCount = 0;
StringBuilder fileName;
fileName.format("debug_hole_%d.obj", pieceCount++);
exportMesh(m_unifiedMesh.ptr(), fileName.str());*/
}
m_unifiedMesh.reset(halfedge::triangulate(m_unifiedMesh.get()));
//exportMesh(m_unifiedMesh.ptr(), "debug_triangulated.obj");
// Analyze chart topology.
halfedge::MeshTopology topology(m_unifiedMesh.get());
m_isDisk = topology.isDisk();
}
void buildVertexMap(const halfedge::Mesh *originalMesh, const std::vector<uint32_t> &unchartedMaterialArray) {
xaAssert(m_chartMesh.get() == NULL && m_unifiedMesh.get() == NULL);
m_isVertexMapped = true;
// Build face indices.
m_faceArray.clear();
const uint32_t meshFaceCount = originalMesh->faceCount();
for (uint32_t f = 0; f < meshFaceCount; f++) {
const halfedge::Face *face = originalMesh->faceAt(f);
if (std::find(unchartedMaterialArray.begin(), unchartedMaterialArray.end(), face->material) != unchartedMaterialArray.end()) {
m_faceArray.push_back(f);
}
}
const uint32_t faceCount = m_faceArray.size();
if (faceCount == 0) {
return;
}
// @@ The chartMesh construction is basically the same as with regular charts, don't duplicate!
const uint32_t meshVertexCount = originalMesh->vertexCount();
m_chartMesh.reset(new halfedge::Mesh());
std::vector<uint32_t> chartMeshIndices(meshVertexCount, (uint32_t)~0);
// Vertex map mesh only has disconnected vertices.
for (uint32_t f = 0; f < faceCount; f++) {
const halfedge::Face *face = originalMesh->faceAt(m_faceArray[f]);
xaDebugAssert(face != NULL);
for (halfedge::Face::ConstEdgeIterator it(face->edges()); !it.isDone(); it.advance()) {
const halfedge::Vertex *vertex = it.current()->vertex;
if (chartMeshIndices[vertex->id] == ~0) {
chartMeshIndices[vertex->id] = m_chartMesh->vertexCount();
m_chartToOriginalMap.push_back(vertex->original_id);
halfedge::Vertex *v = m_chartMesh->addVertex(vertex->pos);
v->nor = vertex->nor;
v->tex = vertex->tex; // @@ Not necessary.
}
}
}
// @@ Link colocals using the original mesh canonical map? Build canonical map on the fly? Do we need to link colocals at all for this?
//m_chartMesh->linkColocals();
std::vector<uint32_t> faceIndices;
faceIndices.reserve(7);
// Add faces.
for (uint32_t f = 0; f < faceCount; f++) {
const halfedge::Face *face = originalMesh->faceAt(m_faceArray[f]);
xaDebugAssert(face != NULL);
faceIndices.clear();
for (halfedge::Face::ConstEdgeIterator it(face->edges()); !it.isDone(); it.advance()) {
const halfedge::Vertex *vertex = it.current()->vertex;
xaDebugAssert(vertex != NULL);
xaDebugAssert(chartMeshIndices[vertex->id] != ~0);
faceIndices.push_back(chartMeshIndices[vertex->id]);
}
halfedge::Face *new_face = m_chartMesh->addFace(faceIndices);
xaDebugAssert(new_face != NULL);
#ifdef NDEBUG
new_face = NULL; // silence unused parameter warning
#endif
}
m_chartMesh->linkBoundary();
const uint32_t chartVertexCount = m_chartMesh->vertexCount();
Box bounds;
bounds.clearBounds();
for (uint32_t i = 0; i < chartVertexCount; i++) {
halfedge::Vertex *vertex = m_chartMesh->vertexAt(i);
bounds.addPointToBounds(vertex->pos);
}
ProximityGrid grid;
grid.init(bounds, chartVertexCount);
for (uint32_t i = 0; i < chartVertexCount; i++) {
halfedge::Vertex *vertex = m_chartMesh->vertexAt(i);
grid.add(vertex->pos, i);
}
uint32_t texelCount = 0;
const float positionThreshold = 0.01f;
const float normalThreshold = 0.01f;
uint32_t verticesVisited = 0;
uint32_t cellsVisited = 0;
std::vector<int> vertexIndexArray(chartVertexCount, -1); // Init all indices to -1.
// Traverse vertices in morton order. @@ It may be more interesting to sort them based on orientation.
const uint32_t cellCodeCount = grid.mortonCount();
for (uint32_t cellCode = 0; cellCode < cellCodeCount; cellCode++) {
int cell = grid.mortonIndex(cellCode);
if (cell < 0) continue;
cellsVisited++;
const std::vector<uint32_t> &indexArray = grid.cellArray[cell].indexArray;
for (uint32_t i = 0; i < indexArray.size(); i++) {
uint32_t idx = indexArray[i];
halfedge::Vertex *vertex = m_chartMesh->vertexAt(idx);
xaDebugAssert(vertexIndexArray[idx] == -1);
std::vector<uint32_t> neighbors;
grid.gather(vertex->pos, positionThreshold, /*ref*/ neighbors);
// Compare against all nearby vertices, cluster greedily.
for (uint32_t j = 0; j < neighbors.size(); j++) {
uint32_t otherIdx = neighbors[j];
if (vertexIndexArray[otherIdx] != -1) {
halfedge::Vertex *otherVertex = m_chartMesh->vertexAt(otherIdx);
if (distance(vertex->pos, otherVertex->pos) < positionThreshold &&
distance(vertex->nor, otherVertex->nor) < normalThreshold) {
vertexIndexArray[idx] = vertexIndexArray[otherIdx];
break;
}
}
}
// If index not assigned, assign new one.
if (vertexIndexArray[idx] == -1) {
vertexIndexArray[idx] = texelCount++;
}
verticesVisited++;
}
}
xaDebugAssert(cellsVisited == grid.cellArray.size());
xaDebugAssert(verticesVisited == chartVertexCount);
vertexMapWidth = ftoi_ceil(sqrtf(float(texelCount)));
vertexMapWidth = (vertexMapWidth + 3) & ~3; // Width aligned to 4.
vertexMapHeight = vertexMapWidth == 0 ? 0 : (texelCount + vertexMapWidth - 1) / vertexMapWidth;
//vertexMapHeight = (vertexMapHeight + 3) & ~3; // Height aligned to 4.
xaDebugAssert(vertexMapWidth >= vertexMapHeight);
xaPrint("Reduced vertex count from %d to %d.\n", chartVertexCount, texelCount);
// Lay down the clustered vertices in morton order.
std::vector<uint32_t> texelCodes(texelCount);
// For each texel, assign one morton code.
uint32_t texelCode = 0;
for (uint32_t i = 0; i < texelCount; i++) {
uint32_t x, y;
do {
x = morton::decodeMorton2X(texelCode);
y = morton::decodeMorton2Y(texelCode);
texelCode++;
} while (x >= uint32_t(vertexMapWidth) || y >= uint32_t(vertexMapHeight));
texelCodes[i] = texelCode - 1;
}
for (uint32_t i = 0; i < chartVertexCount; i++) {
halfedge::Vertex *vertex = m_chartMesh->vertexAt(i);
int idx = vertexIndexArray[i];
if (idx != -1) {
uint32_t tc = texelCodes[idx];
uint32_t x = morton::decodeMorton2X(tc);
uint32_t y = morton::decodeMorton2Y(tc);
vertex->tex.x = float(x);
vertex->tex.y = float(y);
}
}
}
bool closeHoles() {
xaDebugAssert(!m_isVertexMapped);
std::vector<halfedge::Edge *> boundaryEdges;
getBoundaryEdges(m_unifiedMesh.get(), boundaryEdges);
uint32_t boundaryCount = boundaryEdges.size();
if (boundaryCount <= 1) {
// Nothing to close.
return true;
}
// Compute lengths and areas.
std::vector<float> boundaryLengths;
for (uint32_t i = 0; i < boundaryCount; i++) {
const halfedge::Edge *startEdge = boundaryEdges[i];
xaAssert(startEdge->face == NULL);
//float boundaryEdgeCount = 0;
float boundaryLength = 0.0f;
//Vector3 boundaryCentroid(zero);
const halfedge::Edge *edge = startEdge;
do {
Vector3 t0 = edge->from()->pos;
Vector3 t1 = edge->to()->pos;
//boundaryEdgeCount++;
boundaryLength += length(t1 - t0);
//boundaryCentroid += edge->vertex()->pos;
edge = edge->next;
} while (edge != startEdge);
boundaryLengths.push_back(boundaryLength);
//boundaryCentroids.append(boundaryCentroid / boundaryEdgeCount);
}
// Find disk boundary.
uint32_t diskBoundary = 0;
float maxLength = boundaryLengths[0];
for (uint32_t i = 1; i < boundaryCount; i++) {
if (boundaryLengths[i] > maxLength) {
maxLength = boundaryLengths[i];
diskBoundary = i;
}
}
// Close holes.
for (uint32_t i = 0; i < boundaryCount; i++) {
if (diskBoundary == i) {
// Skip disk boundary.
continue;
}
halfedge::Edge *startEdge = boundaryEdges[i];
xaDebugAssert(startEdge != NULL);
xaDebugAssert(startEdge->face == NULL);
std::vector<halfedge::Vertex *> vertexLoop;
std::vector<halfedge::Edge *> edgeLoop;
halfedge::Edge *edge = startEdge;
do {
halfedge::Vertex *vertex = edge->next->vertex; // edge->to()
uint32_t j;
for (j = 0; j < vertexLoop.size(); j++) {
if (vertex->isColocal(vertexLoop[j])) {
break;
}
}
bool isCrossing = (j != vertexLoop.size());
if (isCrossing) {
halfedge::Edge *prev = edgeLoop[j]; // Previous edge before the loop.
halfedge::Edge *next = edge->next; // Next edge after the loop.
xaDebugAssert(prev->to()->isColocal(next->from()));
// Close loop.
edgeLoop.push_back(edge);
closeLoop(j + 1, edgeLoop);
// Link boundary loop.
prev->setNext(next);
vertex->setEdge(next);
// Start over again.
vertexLoop.clear();
edgeLoop.clear();
edge = startEdge;
vertex = edge->to();
}
vertexLoop.push_back(vertex);
edgeLoop.push_back(edge);
edge = edge->next;
} while (edge != startEdge);
closeLoop(0, edgeLoop);
}
getBoundaryEdges(m_unifiedMesh.get(), boundaryEdges);
boundaryCount = boundaryEdges.size();
xaDebugAssert(boundaryCount == 1);
return boundaryCount == 1;
}
bool isDisk() const {
return m_isDisk;
}
bool isVertexMapped() const {
return m_isVertexMapped;
}
uint32_t vertexCount() const {
return m_chartMesh->vertexCount();
}
uint32_t colocalVertexCount() const {
return m_unifiedMesh->vertexCount();
}
uint32_t faceCount() const {
return m_faceArray.size();
}
uint32_t faceAt(uint32_t i) const {
return m_faceArray[i];
}
const halfedge::Mesh *chartMesh() const {
return m_chartMesh.get();
}
halfedge::Mesh *chartMesh() {
return m_chartMesh.get();
}
const halfedge::Mesh *unifiedMesh() const {
return m_unifiedMesh.get();
}
halfedge::Mesh *unifiedMesh() {
return m_unifiedMesh.get();
}
//uint32_t vertexIndex(uint32_t i) const { return m_vertexIndexArray[i]; }
uint32_t mapChartVertexToOriginalVertex(uint32_t i) const {
return m_chartToOriginalMap[i];
}
uint32_t mapChartVertexToUnifiedVertex(uint32_t i) const {
return m_chartToUnifiedMap[i];
}
const std::vector<uint32_t> &faceArray() const {
return m_faceArray;
}
// Transfer parameterization from unified mesh to chart mesh.
void transferParameterization() {
xaDebugAssert(!m_isVertexMapped);
uint32_t vertexCount = m_chartMesh->vertexCount();
for (uint32_t v = 0; v < vertexCount; v++) {
halfedge::Vertex *vertex = m_chartMesh->vertexAt(v);
halfedge::Vertex *unifiedVertex = m_unifiedMesh->vertexAt(mapChartVertexToUnifiedVertex(v));
vertex->tex = unifiedVertex->tex;
}
}
float computeSurfaceArea() const {
return halfedge::computeSurfaceArea(m_chartMesh.get()) * scale;
}
float computeParametricArea() const {
// This only makes sense in parameterized meshes.
xaDebugAssert(m_isDisk);
xaDebugAssert(!m_isVertexMapped);
return halfedge::computeParametricArea(m_chartMesh.get());
}
Vector2 computeParametricBounds() const {
// This only makes sense in parameterized meshes.
xaDebugAssert(m_isDisk);
xaDebugAssert(!m_isVertexMapped);
Box bounds;
bounds.clearBounds();
uint32_t vertexCount = m_chartMesh->vertexCount();
for (uint32_t v = 0; v < vertexCount; v++) {
halfedge::Vertex *vertex = m_chartMesh->vertexAt(v);
bounds.addPointToBounds(Vector3(vertex->tex, 0));
}
return bounds.extents().xy();
}
float scale = 1.0f;
uint32_t vertexMapWidth;
uint32_t vertexMapHeight;
bool blockAligned = true;
private:
bool closeLoop(uint32_t start, const std::vector<halfedge::Edge *> &loop) {
const uint32_t vertexCount = loop.size() - start;
xaDebugAssert(vertexCount >= 3);
if (vertexCount < 3) return false;
xaDebugAssert(loop[start]->vertex->isColocal(loop[start + vertexCount - 1]->to()));
// If the hole is planar, then we add a single face that will be properly triangulated later.
// If the hole is not planar, we add a triangle fan with a vertex at the hole centroid.
// This is still a bit of a hack. There surely are better hole filling algorithms out there.
std::vector<Vector3> points(vertexCount);
for (uint32_t i = 0; i < vertexCount; i++) {
points[i] = loop[start + i]->vertex->pos;
}
bool isPlanar = Fit::isPlanar(vertexCount, points.data());
if (isPlanar) {
// Add face and connect edges.
halfedge::Face *face = m_unifiedMesh->addFace();
for (uint32_t i = 0; i < vertexCount; i++) {
halfedge::Edge *edge = loop[start + i];
edge->face = face;
edge->setNext(loop[start + (i + 1) % vertexCount]);
}
face->edge = loop[start];
xaDebugAssert(face->isValid());
} else {
// If the polygon is not planar, we just cross our fingers, and hope this will work:
// Compute boundary centroid:
Vector3 centroidPos(0);
for (uint32_t i = 0; i < vertexCount; i++) {
centroidPos += points[i];
}
centroidPos *= (1.0f / vertexCount);
halfedge::Vertex *centroid = m_unifiedMesh->addVertex(centroidPos);
// Add one pair of edges for each boundary vertex.
for (uint32_t j = vertexCount - 1, i = 0; i < vertexCount; j = i++) {
halfedge::Face *face = m_unifiedMesh->addFace(centroid->id, loop[start + j]->vertex->id, loop[start + i]->vertex->id);
xaDebugAssert(face != NULL);
#ifdef NDEBUG
face = NULL; // silence unused parameter warning
#endif
}
}
return true;
}
static void getBoundaryEdges(halfedge::Mesh *mesh, std::vector<halfedge::Edge *> &boundaryEdges) {
xaDebugAssert(mesh != NULL);
const uint32_t edgeCount = mesh->edgeCount();
BitArray bitFlags(edgeCount);
bitFlags.clearAll();
boundaryEdges.clear();
// Search for boundary edges. Mark all the edges that belong to the same boundary.
for (uint32_t e = 0; e < edgeCount; e++) {
halfedge::Edge *startEdge = mesh->edgeAt(e);
if (startEdge != NULL && startEdge->isBoundary() && bitFlags.bitAt(e) == false) {
xaDebugAssert(startEdge->face != NULL);
xaDebugAssert(startEdge->pair->face == NULL);
startEdge = startEdge->pair;
const halfedge::Edge *edge = startEdge;
do {
xaDebugAssert(edge->face == NULL);
xaDebugAssert(bitFlags.bitAt(edge->id / 2) == false);
bitFlags.setBitAt(edge->id / 2);
edge = edge->next;
} while (startEdge != edge);
boundaryEdges.push_back(startEdge);
}
}
}
// Chart mesh.
std::auto_ptr<halfedge::Mesh> m_chartMesh;
std::auto_ptr<halfedge::Mesh> m_unifiedMesh;
bool m_isDisk;
bool m_isVertexMapped;
// List of faces of the original mesh that belong to this chart.
std::vector<uint32_t> m_faceArray;
// Map vertices of the chart mesh to vertices of the original mesh.
std::vector<uint32_t> m_chartToOriginalMap;
std::vector<uint32_t> m_chartToUnifiedMap;
};
// Estimate quality of existing parameterization.
class ParameterizationQuality {
public:
ParameterizationQuality() {
m_totalTriangleCount = 0;
m_flippedTriangleCount = 0;
m_zeroAreaTriangleCount = 0;
m_parametricArea = 0.0f;
m_geometricArea = 0.0f;
m_stretchMetric = 0.0f;
m_maxStretchMetric = 0.0f;
m_conformalMetric = 0.0f;
m_authalicMetric = 0.0f;
}
ParameterizationQuality(const halfedge::Mesh *mesh) {
xaDebugAssert(mesh != NULL);
m_totalTriangleCount = 0;
m_flippedTriangleCount = 0;
m_zeroAreaTriangleCount = 0;
m_parametricArea = 0.0f;
m_geometricArea = 0.0f;
m_stretchMetric = 0.0f;
m_maxStretchMetric = 0.0f;
m_conformalMetric = 0.0f;
m_authalicMetric = 0.0f;
const uint32_t faceCount = mesh->faceCount();
for (uint32_t f = 0; f < faceCount; f++) {
const halfedge::Face *face = mesh->faceAt(f);
const halfedge::Vertex *vertex0 = NULL;
Vector3 p[3];
Vector2 t[3];
for (halfedge::Face::ConstEdgeIterator it(face->edges()); !it.isDone(); it.advance()) {
const halfedge::Edge *edge = it.current();
if (vertex0 == NULL) {
vertex0 = edge->vertex;
p[0] = vertex0->pos;
t[0] = vertex0->tex;
} else if (edge->to() != vertex0) {
p[1] = edge->from()->pos;
p[2] = edge->to()->pos;
t[1] = edge->from()->tex;
t[2] = edge->to()->tex;
processTriangle(p, t);
}
}
}
if (m_flippedTriangleCount + m_zeroAreaTriangleCount == faceCount) {
// If all triangles are flipped, then none is.
m_flippedTriangleCount = 0;
}
xaDebugAssert(std::isfinite(m_parametricArea) && m_parametricArea >= 0);
xaDebugAssert(std::isfinite(m_geometricArea) && m_geometricArea >= 0);
xaDebugAssert(std::isfinite(m_stretchMetric));
xaDebugAssert(std::isfinite(m_maxStretchMetric));
xaDebugAssert(std::isfinite(m_conformalMetric));
xaDebugAssert(std::isfinite(m_authalicMetric));
}
bool isValid() const {
return m_flippedTriangleCount == 0; // @@ Does not test for self-overlaps.
}
float rmsStretchMetric() const {
if (m_geometricArea == 0) return 0.0f;
float normFactor = sqrtf(m_parametricArea / m_geometricArea);
return sqrtf(m_stretchMetric / m_geometricArea) * normFactor;
}
float maxStretchMetric() const {
if (m_geometricArea == 0) return 0.0f;
float normFactor = sqrtf(m_parametricArea / m_geometricArea);
return m_maxStretchMetric * normFactor;
}
float rmsConformalMetric() const {
if (m_geometricArea == 0) return 0.0f;
return sqrtf(m_conformalMetric / m_geometricArea);
}
float maxAuthalicMetric() const {
if (m_geometricArea == 0) return 0.0f;
return sqrtf(m_authalicMetric / m_geometricArea);
}
void operator+=(const ParameterizationQuality &pq) {
m_totalTriangleCount += pq.m_totalTriangleCount;
m_flippedTriangleCount += pq.m_flippedTriangleCount;
m_zeroAreaTriangleCount += pq.m_zeroAreaTriangleCount;
m_parametricArea += pq.m_parametricArea;
m_geometricArea += pq.m_geometricArea;
m_stretchMetric += pq.m_stretchMetric;
m_maxStretchMetric = std::max(m_maxStretchMetric, pq.m_maxStretchMetric);
m_conformalMetric += pq.m_conformalMetric;
m_authalicMetric += pq.m_authalicMetric;
}
private:
void processTriangle(Vector3 q[3], Vector2 p[3]) {
m_totalTriangleCount++;
// Evaluate texture stretch metric. See:
// - "Texture Mapping Progressive Meshes", Sander, Snyder, Gortler & Hoppe
// - "Mesh Parameterization: Theory and Practice", Siggraph'07 Course Notes, Hormann, Levy & Sheffer.
float t1 = p[0].x;
float s1 = p[0].y;
float t2 = p[1].x;
float s2 = p[1].y;
float t3 = p[2].x;
float s3 = p[2].y;
float geometricArea = length(cross(q[1] - q[0], q[2] - q[0])) / 2;
float parametricArea = ((s2 - s1) * (t3 - t1) - (s3 - s1) * (t2 - t1)) / 2;
if (isZero(parametricArea)) {
m_zeroAreaTriangleCount++;
return;
}
Vector3 Ss = (q[0] * (t2 - t3) + q[1] * (t3 - t1) + q[2] * (t1 - t2)) / (2 * parametricArea);
Vector3 St = (q[0] * (s3 - s2) + q[1] * (s1 - s3) + q[2] * (s2 - s1)) / (2 * parametricArea);
float a = dot(Ss, Ss); // E
float b = dot(Ss, St); // F
float c = dot(St, St); // G
// Compute eigen-values of the first fundamental form:
float sigma1 = sqrtf(0.5f * std::max(0.0f, a + c - sqrtf(square(a - c) + 4 * square(b)))); // gamma uppercase, min eigenvalue.
float sigma2 = sqrtf(0.5f * std::max(0.0f, a + c + sqrtf(square(a - c) + 4 * square(b)))); // gamma lowercase, max eigenvalue.
xaAssert(sigma2 >= sigma1);
// isometric: sigma1 = sigma2 = 1
// conformal: sigma1 / sigma2 = 1
// authalic: sigma1 * sigma2 = 1
float rmsStretch = sqrtf((a + c) * 0.5f);
float rmsStretch2 = sqrtf((square(sigma1) + square(sigma2)) * 0.5f);
xaDebugAssert(equal(rmsStretch, rmsStretch2, 0.01f));
#ifdef NDEBUG
rmsStretch2 = 0; // silence unused parameter warning
#endif
if (parametricArea < 0.0f) {
// Count flipped triangles.
m_flippedTriangleCount++;
parametricArea = fabsf(parametricArea);
}
m_stretchMetric += square(rmsStretch) * geometricArea;
m_maxStretchMetric = std::max(m_maxStretchMetric, sigma2);
if (!isZero(sigma1, 0.000001f)) {
// sigma1 is zero when geometricArea is zero.
m_conformalMetric += (sigma2 / sigma1) * geometricArea;
}
m_authalicMetric += (sigma1 * sigma2) * geometricArea;
// Accumulate total areas.
m_geometricArea += geometricArea;
m_parametricArea += parametricArea;
//triangleConformalEnergy(q, p);
}
uint32_t m_totalTriangleCount;
uint32_t m_flippedTriangleCount;
uint32_t m_zeroAreaTriangleCount;
float m_parametricArea;
float m_geometricArea;
float m_stretchMetric;
float m_maxStretchMetric;
float m_conformalMetric;
float m_authalicMetric;
};
// Set of charts corresponding to a single mesh.
class MeshCharts {
public:
MeshCharts(const halfedge::Mesh *mesh) :
m_mesh(mesh) {}
~MeshCharts() {
for (size_t i = 0; i < m_chartArray.size(); i++)
delete m_chartArray[i];
}
uint32_t chartCount() const {
return m_chartArray.size();
}
uint32_t vertexCount() const {
return m_totalVertexCount;
}
const Chart *chartAt(uint32_t i) const {
return m_chartArray[i];
}
Chart *chartAt(uint32_t i) {
return m_chartArray[i];
}
// Extract the charts of the input mesh.
void extractCharts() {
const uint32_t faceCount = m_mesh->faceCount();
int first = 0;
std::vector<uint32_t> queue;
queue.reserve(faceCount);
BitArray bitFlags(faceCount);
bitFlags.clearAll();
for (uint32_t f = 0; f < faceCount; f++) {
if (bitFlags.bitAt(f) == false) {
// Start new patch. Reset queue.
first = 0;
queue.clear();
queue.push_back(f);
bitFlags.setBitAt(f);
while (first != (int)queue.size()) {
const halfedge::Face *face = m_mesh->faceAt(queue[first]);
// Visit face neighbors of queue[first]
for (halfedge::Face::ConstEdgeIterator it(face->edges()); !it.isDone(); it.advance()) {
const halfedge::Edge *edge = it.current();
xaDebugAssert(edge->pair != NULL);
if (!edge->isBoundary() && /*!edge->isSeam()*/
//!(edge->from()->tex() != edge->pair()->to()->tex() || edge->to()->tex() != edge->pair()->from()->tex()))
!(edge->from() != edge->pair->to() || edge->to() != edge->pair->from())) { // Preserve existing seams (not just texture seams).
const halfedge::Face *neighborFace = edge->pair->face;
xaDebugAssert(neighborFace != NULL);
if (bitFlags.bitAt(neighborFace->id) == false) {
queue.push_back(neighborFace->id);
bitFlags.setBitAt(neighborFace->id);
}
}
}
first++;
}
Chart *chart = new Chart();
chart->build(m_mesh, queue);
m_chartArray.push_back(chart);
}
}
}
/*
Compute charts using a simple segmentation algorithm.
LSCM:
- identify sharp features using local dihedral angles.
- identify seed faces farthest from sharp features.
- grow charts from these seeds.
MCGIM:
- phase 1: chart growth
- grow all charts simultaneously using dijkstra search on the dual graph of the mesh.
- graph edges are weighted based on planarity metric.
- metric uses distance to global chart normal.
- terminate when all faces have been assigned.
- phase 2: seed computation:
- place new seed of the chart at the most interior face.
- most interior is evaluated using distance metric only.
- method repeates the two phases, until the location of the seeds does not change.
- cycles are detected by recording all the previous seeds and chartification terminates.
D-Charts:
- Uniaxial conic metric:
- N_c = axis of the generalized cone that best fits the chart. (cone can a be cylinder or a plane).
- omega_c = angle between the face normals and the axis.
- Fitting error between chart C and tringle t: F(c,t) = (N_c*n_t - cos(omega_c))^2
- Compactness metrics:
- Roundness:
- C(c,t) = pi * D(S_c,t)^2 / A_c
- S_c = chart seed.
- D(S_c,t) = length of the shortest path inside the chart betwen S_c and t.
- A_c = chart area.
- Straightness:
- P(c,t) = l_out(c,t) / l_in(c,t)
- l_out(c,t) = lenght of the edges not shared between C and t.
- l_in(c,t) = lenght of the edges shared between C and t.
- Combined metric:
- Cost(c,t) = F(c,t)^alpha + C(c,t)^beta + P(c,t)^gamma
- alpha = 1, beta = 0.7, gamma = 0.5
Our basic approach:
- Just one iteration of k-means?
- Avoid dijkstra by greedily growing charts until a threshold is met. Increase threshold and repeat until no faces left.
- If distortion metric is too high, split chart, add two seeds.
- If chart size is low, try removing chart.
Postprocess:
- If topology is not disk:
- Fill holes, if new faces fit proxy.
- Find best cut, otherwise.
- After parameterization:
- If boundary self-intersects:
- cut chart along the closest two diametral boundary vertices, repeat parametrization.
- what if the overlap is on an appendix? How do we find that out and cut appropiately?
- emphasize roundness metrics to prevent those cases.
- If interior self-overlaps: preserve boundary parameterization and use mean-value map.
*/
void computeCharts(const CharterOptions &options, const std::vector<uint32_t> &unchartedMaterialArray) {
Chart *vertexMap = NULL;
if (unchartedMaterialArray.size() != 0) {
vertexMap = new Chart();
vertexMap->buildVertexMap(m_mesh, unchartedMaterialArray);
if (vertexMap->faceCount() == 0) {
delete vertexMap;
vertexMap = NULL;
}
}
AtlasBuilder builder(m_mesh);
if (vertexMap != NULL) {
// Mark faces that do not need to be charted.
builder.markUnchartedFaces(vertexMap->faceArray());
m_chartArray.push_back(vertexMap);
}
if (builder.facesLeft != 0) {
// Tweak these values:
const float maxThreshold = 2;
const uint32_t growFaceCount = 32;
const uint32_t maxIterations = 4;
builder.options = options;
//builder.options.proxyFitMetricWeight *= 0.75; // relax proxy fit weight during initial seed placement.
//builder.options.roundnessMetricWeight = 0;
//builder.options.straightnessMetricWeight = 0;
// This seems a reasonable estimate.
uint32_t maxSeedCount = std::max(6U, builder.facesLeft);
// Create initial charts greedely.
xaPrint("### Placing seeds\n");
builder.placeSeeds(maxThreshold, maxSeedCount);
xaPrint("### Placed %d seeds (max = %d)\n", builder.chartCount(), maxSeedCount);
builder.updateProxies();
builder.mergeCharts();
#if 1
xaPrint("### Relocating seeds\n");
builder.relocateSeeds();
xaPrint("### Reset charts\n");
builder.resetCharts();
if (vertexMap != NULL) {
builder.markUnchartedFaces(vertexMap->faceArray());
}
builder.options = options;
xaPrint("### Growing charts\n");
// Restart process growing charts in parallel.
uint32_t iteration = 0;
while (true) {
if (!builder.growCharts(maxThreshold, growFaceCount)) {
xaPrint("### Can't grow anymore\n");
// If charts cannot grow more: fill holes, merge charts, relocate seeds and start new iteration.
xaPrint("### Filling holes\n");
builder.fillHoles(maxThreshold);
xaPrint("### Using %d charts now\n", builder.chartCount());
builder.updateProxies();
xaPrint("### Merging charts\n");
builder.mergeCharts();
xaPrint("### Using %d charts now\n", builder.chartCount());
xaPrint("### Reseeding\n");
if (!builder.relocateSeeds()) {
xaPrint("### Cannot relocate seeds anymore\n");
// Done!
break;
}
if (iteration == maxIterations) {
xaPrint("### Reached iteration limit\n");
break;
}
iteration++;
xaPrint("### Reset charts\n");
builder.resetCharts();
if (vertexMap != NULL) {
builder.markUnchartedFaces(vertexMap->faceArray());
}
xaPrint("### Growing charts\n");
}
};
#endif
// Make sure no holes are left!
xaDebugAssert(builder.facesLeft == 0);
const uint32_t chartCount = builder.chartArray.size();
for (uint32_t i = 0; i < chartCount; i++) {
Chart *chart = new Chart();
m_chartArray.push_back(chart);
chart->build(m_mesh, builder.chartFaces(i));
}
}
const uint32_t chartCount = m_chartArray.size();
// Build face indices.
m_faceChart.resize(m_mesh->faceCount());
m_faceIndex.resize(m_mesh->faceCount());
for (uint32_t i = 0; i < chartCount; i++) {
const Chart *chart = m_chartArray[i];
const uint32_t faceCount = chart->faceCount();
for (uint32_t f = 0; f < faceCount; f++) {
uint32_t idx = chart->faceAt(f);
m_faceChart[idx] = i;
m_faceIndex[idx] = f;
}
}
// Build an exclusive prefix sum of the chart vertex counts.
m_chartVertexCountPrefixSum.resize(chartCount);
if (chartCount > 0) {
m_chartVertexCountPrefixSum[0] = 0;
for (uint32_t i = 1; i < chartCount; i++) {
const Chart *chart = m_chartArray[i - 1];
m_chartVertexCountPrefixSum[i] = m_chartVertexCountPrefixSum[i - 1] + chart->vertexCount();
}
m_totalVertexCount = m_chartVertexCountPrefixSum[chartCount - 1] + m_chartArray[chartCount - 1]->vertexCount();
} else {
m_totalVertexCount = 0;
}
}
void parameterizeCharts() {
ParameterizationQuality globalParameterizationQuality;
// Parameterize the charts.
uint32_t diskCount = 0;
const uint32_t chartCount = m_chartArray.size();
for (uint32_t i = 0; i < chartCount; i++) {
Chart *chart = m_chartArray[i];
bool isValid = false;
if (chart->isVertexMapped()) {
continue;
}
if (chart->isDisk()) {
diskCount++;
ParameterizationQuality chartParameterizationQuality;
if (chart->faceCount() == 1) {
computeSingleFaceMap(chart->unifiedMesh());
chartParameterizationQuality = ParameterizationQuality(chart->unifiedMesh());
} else {
computeOrthogonalProjectionMap(chart->unifiedMesh());
ParameterizationQuality orthogonalQuality(chart->unifiedMesh());
computeLeastSquaresConformalMap(chart->unifiedMesh());
ParameterizationQuality lscmQuality(chart->unifiedMesh());
chartParameterizationQuality = lscmQuality;
}
isValid = chartParameterizationQuality.isValid();
if (!isValid) {
xaPrint("*** Invalid parameterization.\n");
}
// @@ Check that parameterization quality is above a certain threshold.
// @@ Detect boundary self-intersections.
globalParameterizationQuality += chartParameterizationQuality;
}
// Transfer parameterization from unified mesh to chart mesh.
chart->transferParameterization();
}
xaPrint(" Parameterized %d/%d charts.\n", diskCount, chartCount);
xaPrint(" RMS stretch metric: %f\n", globalParameterizationQuality.rmsStretchMetric());
xaPrint(" MAX stretch metric: %f\n", globalParameterizationQuality.maxStretchMetric());
xaPrint(" RMS conformal metric: %f\n", globalParameterizationQuality.rmsConformalMetric());
xaPrint(" RMS authalic metric: %f\n", globalParameterizationQuality.maxAuthalicMetric());
}
uint32_t faceChartAt(uint32_t i) const {
return m_faceChart[i];
}
uint32_t faceIndexWithinChartAt(uint32_t i) const {
return m_faceIndex[i];
}
uint32_t vertexCountBeforeChartAt(uint32_t i) const {
return m_chartVertexCountPrefixSum[i];
}
private:
const halfedge::Mesh *m_mesh;
std::vector<Chart *> m_chartArray;
std::vector<uint32_t> m_chartVertexCountPrefixSum;
uint32_t m_totalVertexCount;
std::vector<uint32_t> m_faceChart; // the chart of every face of the input mesh.
std::vector<uint32_t> m_faceIndex; // the index within the chart for every face of the input mesh.
};
/// An atlas is a set of charts.
class Atlas {
public:
~Atlas() {
for (size_t i = 0; i < m_meshChartsArray.size(); i++)
delete m_meshChartsArray[i];
}
uint32_t meshCount() const {
return m_meshChartsArray.size();
}
const MeshCharts *meshAt(uint32_t i) const {
return m_meshChartsArray[i];
}
MeshCharts *meshAt(uint32_t i) {
return m_meshChartsArray[i];
}
uint32_t chartCount() const {
uint32_t count = 0;
for (uint32_t c = 0; c < m_meshChartsArray.size(); c++) {
count += m_meshChartsArray[c]->chartCount();
}
return count;
}
const Chart *chartAt(uint32_t i) const {
for (uint32_t c = 0; c < m_meshChartsArray.size(); c++) {
uint32_t count = m_meshChartsArray[c]->chartCount();
if (i < count) {
return m_meshChartsArray[c]->chartAt(i);
}
i -= count;
}
return NULL;
}
Chart *chartAt(uint32_t i) {
for (uint32_t c = 0; c < m_meshChartsArray.size(); c++) {
uint32_t count = m_meshChartsArray[c]->chartCount();
if (i < count) {
return m_meshChartsArray[c]->chartAt(i);
}
i -= count;
}
return NULL;
}
// Add mesh charts and takes ownership.
// Extract the charts and add to this atlas.
void addMeshCharts(MeshCharts *meshCharts) {
m_meshChartsArray.push_back(meshCharts);
}
void extractCharts(const halfedge::Mesh *mesh) {
MeshCharts *meshCharts = new MeshCharts(mesh);
meshCharts->extractCharts();
addMeshCharts(meshCharts);
}
void computeCharts(const halfedge::Mesh *mesh, const CharterOptions &options, const std::vector<uint32_t> &unchartedMaterialArray) {
MeshCharts *meshCharts = new MeshCharts(mesh);
meshCharts->computeCharts(options, unchartedMaterialArray);
addMeshCharts(meshCharts);
}
void parameterizeCharts() {
for (uint32_t i = 0; i < m_meshChartsArray.size(); i++) {
m_meshChartsArray[i]->parameterizeCharts();
}
}
private:
std::vector<MeshCharts *> m_meshChartsArray;
};
struct AtlasPacker {
AtlasPacker(Atlas *atlas) :
m_atlas(atlas),
m_width(0),
m_height(0) {
// Save the original uvs.
m_originalChartUvs.resize(m_atlas->chartCount());
for (uint32_t i = 0; i < m_atlas->chartCount(); i++) {
const halfedge::Mesh *mesh = atlas->chartAt(i)->chartMesh();
m_originalChartUvs[i].resize(mesh->vertexCount());
for (uint32_t j = 0; j < mesh->vertexCount(); j++)
m_originalChartUvs[i][j] = mesh->vertexAt(j)->tex;
}
}
uint32_t getWidth() const { return m_width; }
uint32_t getHeight() const { return m_height; }
// Pack charts in the smallest possible rectangle.
void packCharts(const PackerOptions &options) {
const uint32_t chartCount = m_atlas->chartCount();
if (chartCount == 0) return;
float texelsPerUnit = 1;
if (options.method == PackMethod::TexelArea)
texelsPerUnit = options.texelArea;
for (int iteration = 0;; iteration++) {
m_rand = MTRand();
std::vector<float> chartOrderArray(chartCount);
std::vector<Vector2> chartExtents(chartCount);
float meshArea = 0;
for (uint32_t c = 0; c < chartCount; c++) {
Chart *chart = m_atlas->chartAt(c);
if (!chart->isVertexMapped() && !chart->isDisk()) {
chartOrderArray[c] = 0;
// Skip non-disks.
continue;
}
Vector2 extents(0.0f);
if (chart->isVertexMapped()) {
// Arrange vertices in a rectangle.
extents.x = float(chart->vertexMapWidth);
extents.y = float(chart->vertexMapHeight);
} else {
// Compute surface area to sort charts.
float chartArea = chart->computeSurfaceArea();
meshArea += chartArea;
//chartOrderArray[c] = chartArea;
// Compute chart scale
float parametricArea = fabsf(chart->computeParametricArea()); // @@ There doesn't seem to be anything preventing parametric area to be negative.
if (parametricArea < NV_EPSILON) {
// When the parametric area is too small we use a rough approximation to prevent divisions by very small numbers.
Vector2 bounds = chart->computeParametricBounds();
parametricArea = bounds.x * bounds.y;
}
float scale = (chartArea / parametricArea) * texelsPerUnit;
if (parametricArea == 0) { // < NV_EPSILON)
scale = 0;
}
xaAssert(std::isfinite(scale));
// Compute bounding box of chart.
Vector2 majorAxis, minorAxis, origin, end;
computeBoundingBox(chart, &majorAxis, &minorAxis, &origin, &end);
xaAssert(isFinite(majorAxis) && isFinite(minorAxis) && isFinite(origin));
// Sort charts by perimeter. @@ This is sometimes producing somewhat unexpected results. Is this right?
//chartOrderArray[c] = ((end.x - origin.x) + (end.y - origin.y)) * scale;
// Translate, rotate and scale vertices. Compute extents.
halfedge::Mesh *mesh = chart->chartMesh();
const uint32_t vertexCount = mesh->vertexCount();
for (uint32_t i = 0; i < vertexCount; i++) {
halfedge::Vertex *vertex = mesh->vertexAt(i);
//Vector2 t = vertex->tex - origin;
Vector2 tmp;
tmp.x = dot(vertex->tex, majorAxis);
tmp.y = dot(vertex->tex, minorAxis);
tmp -= origin;
tmp *= scale;
if (tmp.x < 0 || tmp.y < 0) {
xaPrint("tmp: %f %f\n", tmp.x, tmp.y);
xaPrint("scale: %f\n", scale);
xaPrint("origin: %f %f\n", origin.x, origin.y);
xaPrint("majorAxis: %f %f\n", majorAxis.x, majorAxis.y);
xaPrint("minorAxis: %f %f\n", minorAxis.x, minorAxis.y);
xaDebugAssert(false);
}
//xaAssert(tmp.x >= 0 && tmp.y >= 0);
vertex->tex = tmp;
xaAssert(std::isfinite(vertex->tex.x) && std::isfinite(vertex->tex.y));
extents = max(extents, tmp);
}
xaDebugAssert(extents.x >= 0 && extents.y >= 0);
// Limit chart size.
if (extents.x > 1024 || extents.y > 1024) {
float limit = std::max(extents.x, extents.y);
scale = 1024 / (limit + 1);
for (uint32_t i = 0; i < vertexCount; i++) {
halfedge::Vertex *vertex = mesh->vertexAt(i);
vertex->tex *= scale;
}
extents *= scale;
xaDebugAssert(extents.x <= 1024 && extents.y <= 1024);
}
// Scale the charts to use the entire texel area available. So, if the width is 0.1 we could scale it to 1 without increasing the lightmap usage and making a better
// use of it. In many cases this also improves the look of the seams, since vertices on the chart boundaries have more chances of being aligned with the texel centers.
float scale_x = 1.0f;
float scale_y = 1.0f;
float divide_x = 1.0f;
float divide_y = 1.0f;
if (extents.x > 0) {
int cw = ftoi_ceil(extents.x);
if (options.blockAlign && chart->blockAligned) {
// Align all chart extents to 4x4 blocks, but taking padding into account.
if (options.conservative) {
cw = align(cw + 2, 4) - 2;
} else {
cw = align(cw + 1, 4) - 1;
}
}
scale_x = (float(cw) - NV_EPSILON);
divide_x = extents.x;
extents.x = float(cw);
}
if (extents.y > 0) {
int ch = ftoi_ceil(extents.y);
if (options.blockAlign && chart->blockAligned) {
// Align all chart extents to 4x4 blocks, but taking padding into account.
if (options.conservative) {
ch = align(ch + 2, 4) - 2;
} else {
ch = align(ch + 1, 4) - 1;
}
}
scale_y = (float(ch) - NV_EPSILON);
divide_y = extents.y;
extents.y = float(ch);
}
for (uint32_t v = 0; v < vertexCount; v++) {
halfedge::Vertex *vertex = mesh->vertexAt(v);
vertex->tex.x /= divide_x;
vertex->tex.y /= divide_y;
vertex->tex.x *= scale_x;
vertex->tex.y *= scale_y;
xaAssert(std::isfinite(vertex->tex.x) && std::isfinite(vertex->tex.y));
}
}
chartExtents[c] = extents;
// Sort charts by perimeter.
chartOrderArray[c] = extents.x + extents.y;
}
// @@ We can try to improve compression of small charts by sorting them by proximity like we do with vertex samples.
// @@ How to do that? One idea: compute chart centroid, insert into grid, compute morton index of the cell, sort based on morton index.
// @@ We would sort by morton index, first, then quantize the chart sizes, so that all small charts have the same size, and sort by size preserving the morton order.
//xaPrint("Sorting charts.\n");
// Sort charts by area.
m_radix = RadixSort();
m_radix.sort(chartOrderArray);
const uint32_t *ranks = m_radix.ranks();
// First iteration - guess texelsPerUnit.
if (options.method != PackMethod::TexelArea && iteration == 0) {
// Estimate size of the map based on the mesh surface area and given texel scale.
const float texelCount = std::max(1.0f, meshArea * square(texelsPerUnit) / 0.75f); // Assume 75% utilization.
texelsPerUnit = sqrt((options.resolution * options.resolution) / texelCount);
resetUvs();
continue;
}
// Init bit map.
m_bitmap.clearAll();
m_bitmap.resize(options.resolution, options.resolution, false);
int w = 0;
int h = 0;
// Add sorted charts to bitmap.
for (uint32_t i = 0; i < chartCount; i++) {
uint32_t c = ranks[chartCount - i - 1]; // largest chart first
Chart *chart = m_atlas->chartAt(c);
if (!chart->isVertexMapped() && !chart->isDisk()) continue;
//float scale_x = 1;
//float scale_y = 1;
BitMap chart_bitmap;
if (chart->isVertexMapped()) {
chart->blockAligned = false;
// Init all bits to 1.
chart_bitmap.resize(ftoi_ceil(chartExtents[c].x), ftoi_ceil(chartExtents[c].y), /*initValue=*/true);
// @@ Another alternative would be to try to map each vertex to a different texel trying to fill all the available unused texels.
} else {
// @@ Add special cases for dot and line charts. @@ Lightmap rasterizer also needs to handle these special cases.
// @@ We could also have a special case for chart quads. If the quad surface <= 4 texels, align vertices with texel centers and do not add padding. May be very useful for foliage.
// @@ In general we could reduce the padding of all charts by one texel by using a rasterizer that takes into account the 2-texel footprint of the tent bilinear filter. For example,
// if we have a chart that is less than 1 texel wide currently we add one texel to the left and one texel to the right creating a 3-texel-wide bitmap. However, if we know that the
// chart is only 1 texel wide we could align it so that it only touches the footprint of two texels:
// | | <- Touches texels 0, 1 and 2.
// | | <- Only touches texels 0 and 1.
// \ \ / \ / /
// \ X X /
// \ / \ / \ /
// V V V
// 0 1 2
if (options.conservative) {
// Init all bits to 0.
chart_bitmap.resize(ftoi_ceil(chartExtents[c].x) + 1 + options.padding, ftoi_ceil(chartExtents[c].y) + 1 + options.padding, /*initValue=*/false); // + 2 to add padding on both sides.
// Rasterize chart and dilate.
drawChartBitmapDilate(chart, &chart_bitmap, options.padding);
} else {
// Init all bits to 0.
chart_bitmap.resize(ftoi_ceil(chartExtents[c].x) + 1, ftoi_ceil(chartExtents[c].y) + 1, /*initValue=*/false); // Add half a texels on each side.
// Rasterize chart and dilate.
drawChartBitmap(chart, &chart_bitmap, Vector2(1), Vector2(0.5));
}
}
int best_x, best_y;
int best_cw, best_ch; // Includes padding now.
int best_r;
findChartLocation(options.quality, &chart_bitmap, chartExtents[c], w, h, &best_x, &best_y, &best_cw, &best_ch, &best_r, chart->blockAligned);
/*if (w < best_x + best_cw || h < best_y + best_ch)
{
xaPrint("Resize extents to (%d, %d).\n", best_x + best_cw, best_y + best_ch);
}*/
// Update parametric extents.
w = std::max(w, best_x + best_cw);
h = std::max(h, best_y + best_ch);
w = align(w, 4);
h = align(h, 4);
// Resize bitmap if necessary.
if (uint32_t(w) > m_bitmap.width() || uint32_t(h) > m_bitmap.height()) {
//xaPrint("Resize bitmap (%d, %d).\n", nextPowerOfTwo(w), nextPowerOfTwo(h));
m_bitmap.resize(nextPowerOfTwo(uint32_t(w)), nextPowerOfTwo(uint32_t(h)), false);
}
//xaPrint("Add chart at (%d, %d).\n", best_x, best_y);
addChart(&chart_bitmap, w, h, best_x, best_y, best_r);
//float best_angle = 2 * PI * best_r;
// Translate and rotate chart texture coordinates.
halfedge::Mesh *mesh = chart->chartMesh();
const uint32_t vertexCount = mesh->vertexCount();
for (uint32_t v = 0; v < vertexCount; v++) {
halfedge::Vertex *vertex = mesh->vertexAt(v);
Vector2 t = vertex->tex;
if (best_r) std::swap(t.x, t.y);
//vertex->tex.x = best_x + t.x * cosf(best_angle) - t.y * sinf(best_angle);
//vertex->tex.y = best_y + t.x * sinf(best_angle) + t.y * cosf(best_angle);
vertex->tex.x = best_x + t.x + 0.5f;
vertex->tex.y = best_y + t.y + 0.5f;
xaAssert(vertex->tex.x >= 0 && vertex->tex.y >= 0);
xaAssert(std::isfinite(vertex->tex.x) && std::isfinite(vertex->tex.y));
}
}
//w -= padding - 1; // Leave one pixel border!
//h -= padding - 1;
m_width = std::max(0, w);
m_height = std::max(0, h);
xaAssert(isAligned(m_width, 4));
xaAssert(isAligned(m_height, 4));
if (options.method == PackMethod::ExactResolution) {
texelsPerUnit *= sqrt((options.resolution * options.resolution) / (float)(m_width * m_height));
if (iteration > 1 && m_width <= options.resolution && m_height <= options.resolution) {
m_width = m_height = options.resolution;
return;
}
resetUvs();
} else {
return;
}
}
}
float computeAtlasUtilization() const {
const uint32_t w = m_width;
const uint32_t h = m_height;
xaDebugAssert(w <= m_bitmap.width());
xaDebugAssert(h <= m_bitmap.height());
uint32_t count = 0;
for (uint32_t y = 0; y < h; y++) {
for (uint32_t x = 0; x < w; x++) {
count += m_bitmap.bitAt(x, y);
}
}
return float(count) / (w * h);
}
private:
void resetUvs() {
for (uint32_t i = 0; i < m_atlas->chartCount(); i++) {
halfedge::Mesh *mesh = m_atlas->chartAt(i)->chartMesh();
for (uint32_t j = 0; j < mesh->vertexCount(); j++)
mesh->vertexAt(j)->tex = m_originalChartUvs[i][j];
}
}
// IC: Brute force is slow, and random may take too much time to converge. We start inserting large charts in a small atlas. Using brute force is lame, because most of the space
// is occupied at this point. At the end we have many small charts and a large atlas with sparse holes. Finding those holes randomly is slow. A better approach would be to
// start stacking large charts as if they were tetris pieces. Once charts get small try to place them randomly. It may be interesting to try a intermediate strategy, first try
// along one axis and then try exhaustively along that axis.
void findChartLocation(int quality, const BitMap *bitmap, Vector2::Arg extents, int w, int h, int *best_x, int *best_y, int *best_w, int *best_h, int *best_r, bool blockAligned) {
int attempts = 256;
if (quality == 1) attempts = 4096;
if (quality == 2) attempts = 2048;
if (quality == 3) attempts = 1024;
if (quality == 4) attempts = 512;
if (quality == 0 || w * h < attempts) {
findChartLocation_bruteForce(bitmap, extents, w, h, best_x, best_y, best_w, best_h, best_r, blockAligned);
} else {
findChartLocation_random(bitmap, extents, w, h, best_x, best_y, best_w, best_h, best_r, attempts, blockAligned);
}
}
void findChartLocation_bruteForce(const BitMap *bitmap, Vector2::Arg /*extents*/, int w, int h, int *best_x, int *best_y, int *best_w, int *best_h, int *best_r, bool blockAligned) {
const int BLOCK_SIZE = 4;
int best_metric = INT_MAX;
int step_size = blockAligned ? BLOCK_SIZE : 1;
// Try two different orientations.
for (int r = 0; r < 2; r++) {
int cw = bitmap->width();
int ch = bitmap->height();
if (r & 1) std::swap(cw, ch);
for (int y = 0; y <= h + 1; y += step_size) { // + 1 to extend atlas in case atlas full.
for (int x = 0; x <= w + 1; x += step_size) { // + 1 not really necessary here.
// Early out.
int area = std::max(w, x + cw) * std::max(h, y + ch);
//int perimeter = max(w, x+cw) + max(h, y+ch);
int extents = std::max(std::max(w, x + cw), std::max(h, y + ch));
int metric = extents * extents + area;
if (metric > best_metric) {
continue;
}
if (metric == best_metric && std::max(x, y) >= std::max(*best_x, *best_y)) {
// If metric is the same, pick the one closest to the origin.
continue;
}
if (canAddChart(bitmap, w, h, x, y, r)) {
best_metric = metric;
*best_x = x;
*best_y = y;
*best_w = cw;
*best_h = ch;
*best_r = r;
if (area == w * h) {
// Chart is completely inside, do not look at any other location.
goto done;
}
}
}
}
}
done:
xaDebugAssert(best_metric != INT_MAX);
}
void findChartLocation_random(const BitMap *bitmap, Vector2::Arg /*extents*/, int w, int h, int *best_x, int *best_y, int *best_w, int *best_h, int *best_r, int minTrialCount, bool blockAligned) {
const int BLOCK_SIZE = 4;
int best_metric = INT_MAX;
for (int i = 0; i < minTrialCount || best_metric == INT_MAX; i++) {
int r = m_rand.getRange(1);
int x = m_rand.getRange(w + 1); // + 1 to extend atlas in case atlas full. We may want to use a higher number to increase probability of extending atlas.
int y = m_rand.getRange(h + 1); // + 1 to extend atlas in case atlas full.
if (blockAligned) {
x = align(x, BLOCK_SIZE);
y = align(y, BLOCK_SIZE);
}
int cw = bitmap->width();
int ch = bitmap->height();
if (r & 1) std::swap(cw, ch);
// Early out.
int area = std::max(w, x + cw) * std::max(h, y + ch);
//int perimeter = max(w, x+cw) + max(h, y+ch);
int extents = std::max(std::max(w, x + cw), std::max(h, y + ch));
int metric = extents * extents + area;
if (metric > best_metric) {
continue;
}
if (metric == best_metric && std::min(x, y) > std::min(*best_x, *best_y)) {
// If metric is the same, pick the one closest to the origin.
continue;
}
if (canAddChart(bitmap, w, h, x, y, r)) {
best_metric = metric;
*best_x = x;
*best_y = y;
*best_w = cw;
*best_h = ch;
*best_r = r;
if (area == w * h) {
// Chart is completely inside, do not look at any other location.
break;
}
}
}
}
void drawChartBitmapDilate(const Chart *chart, BitMap *bitmap, int padding) {
const int w = bitmap->width();
const int h = bitmap->height();
const Vector2 extents = Vector2(float(w), float(h));
// Rasterize chart faces, check that all bits are not set.
const uint32_t faceCount = chart->faceCount();
for (uint32_t f = 0; f < faceCount; f++) {
const halfedge::Face *face = chart->chartMesh()->faceAt(f);
Vector2 vertices[4];
uint32_t edgeCount = 0;
for (halfedge::Face::ConstEdgeIterator it(face->edges()); !it.isDone(); it.advance()) {
if (edgeCount < 4) {
vertices[edgeCount] = it.vertex()->tex + Vector2(0.5) + Vector2(float(padding), float(padding));
}
edgeCount++;
}
if (edgeCount == 3) {
raster::drawTriangle(raster::Mode_Antialiased, extents, true, vertices, AtlasPacker::setBitsCallback, bitmap);
} else {
raster::drawQuad(raster::Mode_Antialiased, extents, true, vertices, AtlasPacker::setBitsCallback, bitmap);
}
}
// Expand chart by padding pixels. (dilation)
BitMap tmp(w, h);
for (int i = 0; i < padding; i++) {
tmp.clearAll();
for (int y = 0; y < h; y++) {
for (int x = 0; x < w; x++) {
bool b = bitmap->bitAt(x, y);
if (!b) {
if (x > 0) {
b |= bitmap->bitAt(x - 1, y);
if (y > 0) b |= bitmap->bitAt(x - 1, y - 1);
if (y < h - 1) b |= bitmap->bitAt(x - 1, y + 1);
}
if (y > 0) b |= bitmap->bitAt(x, y - 1);
if (y < h - 1) b |= bitmap->bitAt(x, y + 1);
if (x < w - 1) {
b |= bitmap->bitAt(x + 1, y);
if (y > 0) b |= bitmap->bitAt(x + 1, y - 1);
if (y < h - 1) b |= bitmap->bitAt(x + 1, y + 1);
}
}
if (b) tmp.setBitAt(x, y);
}
}
std::swap(tmp, *bitmap);
}
}
void drawChartBitmap(const Chart *chart, BitMap *bitmap, const Vector2 &scale, const Vector2 &offset) {
const int w = bitmap->width();
const int h = bitmap->height();
const Vector2 extents = Vector2(float(w), float(h));
static const Vector2 pad[4] = {
Vector2(-0.5, -0.5),
Vector2(0.5, -0.5),
Vector2(-0.5, 0.5),
Vector2(0.5, 0.5)
};
// Rasterize 4 times to add proper padding.
for (int i = 0; i < 4; i++) {
// Rasterize chart faces, check that all bits are not set.
const uint32_t faceCount = chart->chartMesh()->faceCount();
for (uint32_t f = 0; f < faceCount; f++) {
const halfedge::Face *face = chart->chartMesh()->faceAt(f);
Vector2 vertices[4];
uint32_t edgeCount = 0;
for (halfedge::Face::ConstEdgeIterator it(face->edges()); !it.isDone(); it.advance()) {
if (edgeCount < 4) {
vertices[edgeCount] = it.vertex()->tex * scale + offset + pad[i];
xaAssert(ftoi_ceil(vertices[edgeCount].x) >= 0);
xaAssert(ftoi_ceil(vertices[edgeCount].y) >= 0);
xaAssert(ftoi_ceil(vertices[edgeCount].x) <= w);
xaAssert(ftoi_ceil(vertices[edgeCount].y) <= h);
}
edgeCount++;
}
if (edgeCount == 3) {
raster::drawTriangle(raster::Mode_Antialiased, extents, /*enableScissors=*/true, vertices, AtlasPacker::setBitsCallback, bitmap);
} else {
raster::drawQuad(raster::Mode_Antialiased, extents, /*enableScissors=*/true, vertices, AtlasPacker::setBitsCallback, bitmap);
}
}
}
// Expand chart by padding pixels. (dilation)
BitMap tmp(w, h);
tmp.clearAll();
for (int y = 0; y < h; y++) {
for (int x = 0; x < w; x++) {
bool b = bitmap->bitAt(x, y);
if (!b) {
if (x > 0) {
b |= bitmap->bitAt(x - 1, y);
if (y > 0) b |= bitmap->bitAt(x - 1, y - 1);
if (y < h - 1) b |= bitmap->bitAt(x - 1, y + 1);
}
if (y > 0) b |= bitmap->bitAt(x, y - 1);
if (y < h - 1) b |= bitmap->bitAt(x, y + 1);
if (x < w - 1) {
b |= bitmap->bitAt(x + 1, y);
if (y > 0) b |= bitmap->bitAt(x + 1, y - 1);
if (y < h - 1) b |= bitmap->bitAt(x + 1, y + 1);
}
}
if (b) tmp.setBitAt(x, y);
}
}
std::swap(tmp, *bitmap);
}
bool canAddChart(const BitMap *bitmap, int atlas_w, int atlas_h, int offset_x, int offset_y, int r) {
xaDebugAssert(r == 0 || r == 1);
// Check whether the two bitmaps overlap.
const int w = bitmap->width();
const int h = bitmap->height();
if (r == 0) {
for (int y = 0; y < h; y++) {
int yy = y + offset_y;
if (yy >= 0) {
for (int x = 0; x < w; x++) {
int xx = x + offset_x;
if (xx >= 0) {
if (bitmap->bitAt(x, y)) {
if (xx < atlas_w && yy < atlas_h) {
if (m_bitmap.bitAt(xx, yy)) return false;
}
}
}
}
}
}
} else if (r == 1) {
for (int y = 0; y < h; y++) {
int xx = y + offset_x;
if (xx >= 0) {
for (int x = 0; x < w; x++) {
int yy = x + offset_y;
if (yy >= 0) {
if (bitmap->bitAt(x, y)) {
if (xx < atlas_w && yy < atlas_h) {
if (m_bitmap.bitAt(xx, yy)) return false;
}
}
}
}
}
}
}
return true;
}
void addChart(const BitMap *bitmap, int atlas_w, int atlas_h, int offset_x, int offset_y, int r) {
xaDebugAssert(r == 0 || r == 1);
// Check whether the two bitmaps overlap.
const int w = bitmap->width();
const int h = bitmap->height();
if (r == 0) {
for (int y = 0; y < h; y++) {
int yy = y + offset_y;
if (yy >= 0) {
for (int x = 0; x < w; x++) {
int xx = x + offset_x;
if (xx >= 0) {
if (bitmap->bitAt(x, y)) {
if (xx < atlas_w && yy < atlas_h) {
xaDebugAssert(m_bitmap.bitAt(xx, yy) == false);
m_bitmap.setBitAt(xx, yy);
}
}
}
}
}
}
} else if (r == 1) {
for (int y = 0; y < h; y++) {
int xx = y + offset_x;
if (xx >= 0) {
for (int x = 0; x < w; x++) {
int yy = x + offset_y;
if (yy >= 0) {
if (bitmap->bitAt(x, y)) {
if (xx < atlas_w && yy < atlas_h) {
xaDebugAssert(m_bitmap.bitAt(xx, yy) == false);
m_bitmap.setBitAt(xx, yy);
}
}
}
}
}
}
}
}
static bool setBitsCallback(void *param, int x, int y, Vector3::Arg, Vector3::Arg, Vector3::Arg, float area) {
BitMap *bitmap = (BitMap *)param;
if (area > 0.0) {
bitmap->setBitAt(x, y);
}
return true;
}
// Compute the convex hull using Graham Scan.
static void convexHull(const std::vector<Vector2> &input, std::vector<Vector2> &output, float epsilon) {
const uint32_t inputCount = input.size();
std::vector<float> coords(inputCount);
for (uint32_t i = 0; i < inputCount; i++) {
coords[i] = input[i].x;
}
RadixSort radix;
radix.sort(coords);
const uint32_t *ranks = radix.ranks();
std::vector<Vector2> top;
top.reserve(inputCount);
std::vector<Vector2> bottom;
bottom.reserve(inputCount);
Vector2 P = input[ranks[0]];
Vector2 Q = input[ranks[inputCount - 1]];
float topy = std::max(P.y, Q.y);
float boty = std::min(P.y, Q.y);
for (uint32_t i = 0; i < inputCount; i++) {
Vector2 p = input[ranks[i]];
if (p.y >= boty) top.push_back(p);
}
for (uint32_t i = 0; i < inputCount; i++) {
Vector2 p = input[ranks[inputCount - 1 - i]];
if (p.y <= topy) bottom.push_back(p);
}
// Filter top list.
output.clear();
output.push_back(top[0]);
output.push_back(top[1]);
for (uint32_t i = 2; i < top.size();) {
Vector2 a = output[output.size() - 2];
Vector2 b = output[output.size() - 1];
Vector2 c = top[i];
float area = triangleArea(a, b, c);
if (area >= -epsilon) {
output.pop_back();
}
if (area < -epsilon || output.size() == 1) {
output.push_back(c);
i++;
}
}
uint32_t top_count = output.size();
output.push_back(bottom[1]);
// Filter bottom list.
for (uint32_t i = 2; i < bottom.size();) {
Vector2 a = output[output.size() - 2];
Vector2 b = output[output.size() - 1];
Vector2 c = bottom[i];
float area = triangleArea(a, b, c);
if (area >= -epsilon) {
output.pop_back();
}
if (area < -epsilon || output.size() == top_count) {
output.push_back(c);
i++;
}
}
// Remove duplicate element.
xaDebugAssert(output.front() == output.back());
output.pop_back();
}
// This should compute convex hull and use rotating calipers to find the best box. Currently it uses a brute force method.
static void computeBoundingBox(Chart *chart, Vector2 *majorAxis, Vector2 *minorAxis, Vector2 *minCorner, Vector2 *maxCorner) {
// Compute list of boundary points.
std::vector<Vector2> points;
points.reserve(16);
halfedge::Mesh *mesh = chart->chartMesh();
const uint32_t vertexCount = mesh->vertexCount();
for (uint32_t i = 0; i < vertexCount; i++) {
halfedge::Vertex *vertex = mesh->vertexAt(i);
if (vertex->isBoundary()) {
points.push_back(vertex->tex);
}
}
xaDebugAssert(points.size() > 0);
std::vector<Vector2> hull;
convexHull(points, hull, 0.00001f);
// @@ Ideally I should use rotating calipers to find the best box. Using brute force for now.
float best_area = FLT_MAX;
Vector2 best_min;
Vector2 best_max;
Vector2 best_axis;
const uint32_t hullCount = hull.size();
for (uint32_t i = 0, j = hullCount - 1; i < hullCount; j = i, i++) {
if (equal(hull[i], hull[j])) {
continue;
}
Vector2 axis = normalize(hull[i] - hull[j], 0.0f);
xaDebugAssert(isFinite(axis));
// Compute bounding box.
Vector2 box_min(FLT_MAX, FLT_MAX);
Vector2 box_max(-FLT_MAX, -FLT_MAX);
for (uint32_t v = 0; v < hullCount; v++) {
Vector2 point = hull[v];
float x = dot(axis, point);
if (x < box_min.x) box_min.x = x;
if (x > box_max.x) box_max.x = x;
float y = dot(Vector2(-axis.y, axis.x), point);
if (y < box_min.y) box_min.y = y;
if (y > box_max.y) box_max.y = y;
}
// Compute box area.
float area = (box_max.x - box_min.x) * (box_max.y - box_min.y);
if (area < best_area) {
best_area = area;
best_min = box_min;
best_max = box_max;
best_axis = axis;
}
}
// Consider all points, not only boundary points, in case the input chart is malformed.
for (uint32_t i = 0; i < vertexCount; i++) {
halfedge::Vertex *vertex = mesh->vertexAt(i);
Vector2 point = vertex->tex;
float x = dot(best_axis, point);
if (x < best_min.x) best_min.x = x;
if (x > best_max.x) best_max.x = x;
float y = dot(Vector2(-best_axis.y, best_axis.x), point);
if (y < best_min.y) best_min.y = y;
if (y > best_max.y) best_max.y = y;
}
*majorAxis = best_axis;
*minorAxis = Vector2(-best_axis.y, best_axis.x);
*minCorner = best_min;
*maxCorner = best_max;
}
Atlas *m_atlas;
BitMap m_bitmap;
RadixSort m_radix;
uint32_t m_width;
uint32_t m_height;
MTRand m_rand;
std::vector<std::vector<Vector2> > m_originalChartUvs;
};
} // namespace param
} // namespace internal
struct Atlas {
internal::param::Atlas atlas;
std::vector<internal::halfedge::Mesh *> heMeshes;
uint32_t width = 0;
uint32_t height = 0;
OutputMesh **outputMeshes = NULL;
};
void SetPrint(PrintFunc print) {
internal::s_print = print;
}
Atlas *Create() {
Atlas *atlas = new Atlas();
return atlas;
}
void Destroy(Atlas *atlas) {
xaAssert(atlas);
for (int i = 0; i < (int)atlas->heMeshes.size(); i++) {
delete atlas->heMeshes[i];
if (atlas->outputMeshes) {
OutputMesh *outputMesh = atlas->outputMeshes[i];
for (uint32_t j = 0; j < outputMesh->chartCount; j++)
delete[] outputMesh->chartArray[j].indexArray;
delete[] outputMesh->chartArray;
delete[] outputMesh->vertexArray;
delete[] outputMesh->indexArray;
delete outputMesh;
}
}
delete[] atlas->outputMeshes;
delete atlas;
}
static internal::Vector3 DecodePosition(const InputMesh &mesh, uint32_t index) {
xaAssert(mesh.vertexPositionData);
return *((const internal::Vector3 *)&((const uint8_t *)mesh.vertexPositionData)[mesh.vertexPositionStride * index]);
}
static internal::Vector3 DecodeNormal(const InputMesh &mesh, uint32_t index) {
xaAssert(mesh.vertexNormalData);
return *((const internal::Vector3 *)&((const uint8_t *)mesh.vertexNormalData)[mesh.vertexNormalStride * index]);
}
static internal::Vector2 DecodeUv(const InputMesh &mesh, uint32_t index) {
xaAssert(mesh.vertexUvData);
return *((const internal::Vector2 *)&((const uint8_t *)mesh.vertexUvData)[mesh.vertexUvStride * index]);
}
static uint32_t DecodeIndex(IndexFormat::Enum format, const void *indexData, uint32_t i) {
if (format == IndexFormat::HalfFloat)
return (uint32_t)((const uint16_t *)indexData)[i];
return ((const uint32_t *)indexData)[i];
}
static float EdgeLength(internal::Vector3 pos1, internal::Vector3 pos2) {
return internal::length(pos2 - pos1);
}
AddMeshError AddMesh(Atlas *atlas, const InputMesh &mesh, bool useColocalVertices) {
xaAssert(atlas);
AddMeshError error;
error.code = AddMeshErrorCode::Success;
error.face = error.index0 = error.index1 = UINT32_MAX;
// Expecting triangle faces.
if ((mesh.indexCount % 3) != 0) {
error.code = AddMeshErrorCode::InvalidIndexCount;
return error;
}
// Check if any index is out of range.
for (uint32_t j = 0; j < mesh.indexCount; j++) {
const uint32_t index = DecodeIndex(mesh.indexFormat, mesh.indexData, j);
if (index < 0 || index >= mesh.vertexCount) {
error.code = AddMeshErrorCode::IndexOutOfRange;
error.index0 = index;
return error;
}
}
// Build half edge mesh.
internal::halfedge::Mesh *heMesh = new internal::halfedge::Mesh;
std::vector<uint32_t> canonicalMap;
canonicalMap.reserve(mesh.vertexCount);
for (uint32_t i = 0; i < mesh.vertexCount; i++) {
internal::halfedge::Vertex *vertex = heMesh->addVertex(DecodePosition(mesh, i));
if (mesh.vertexNormalData)
vertex->nor = DecodeNormal(mesh, i);
if (mesh.vertexUvData)
vertex->tex = DecodeUv(mesh, i);
// Link colocals. You probably want to do this more efficiently! Sort by one axis or use a hash or grid.
uint32_t firstColocal = i;
if (useColocalVertices) {
for (uint32_t j = 0; j < i; j++) {
if (vertex->pos != DecodePosition(mesh, j))
continue;
#if 0
if (mesh.vertexNormalData && vertex->nor != DecodeNormal(mesh, j))
continue;
#endif
if (mesh.vertexUvData && vertex->tex != DecodeUv(mesh, j))
continue;
firstColocal = j;
break;
}
}
canonicalMap.push_back(firstColocal);
}
heMesh->linkColocalsWithCanonicalMap(canonicalMap);
for (uint32_t i = 0; i < mesh.indexCount / 3; i++) {
uint32_t tri[3];
for (int j = 0; j < 3; j++)
tri[j] = DecodeIndex(mesh.indexFormat, mesh.indexData, i * 3 + j);
// Check for zero length edges.
for (int j = 0; j < 3; j++) {
const uint32_t edges[6] = { 0, 1, 1, 2, 2, 0 };
const uint32_t index1 = tri[edges[j * 2 + 0]];
const uint32_t index2 = tri[edges[j * 2 + 1]];
const internal::Vector3 pos1 = DecodePosition(mesh, index1);
const internal::Vector3 pos2 = DecodePosition(mesh, index2);
if (EdgeLength(pos1, pos2) <= 0.0f) {
delete heMesh;
error.code = AddMeshErrorCode::ZeroLengthEdge;
error.face = i;
error.index0 = index1;
error.index1 = index2;
return error;
}
}
// Check for zero area faces.
{
const internal::Vector3 a = DecodePosition(mesh, tri[0]);
const internal::Vector3 b = DecodePosition(mesh, tri[1]);
const internal::Vector3 c = DecodePosition(mesh, tri[2]);
const float area = internal::length(internal::cross(b - a, c - a)) * 0.5f;
if (area <= 0.0f) {
delete heMesh;
error.code = AddMeshErrorCode::ZeroAreaFace;
error.face = i;
return error;
}
}
internal::halfedge::Face *face = heMesh->addFace(tri[0], tri[1], tri[2]);
if (!face && heMesh->errorCode == internal::halfedge::Mesh::ErrorCode::AlreadyAddedEdge) {
//there is still hope for this, no reason to not add, at least add as separate
face = heMesh->addUniqueFace(tri[0], tri[1], tri[2]);
}
if (!face) {
//continue;
if (heMesh->errorCode == internal::halfedge::Mesh::ErrorCode::AlreadyAddedEdge) {
error.code = AddMeshErrorCode::AlreadyAddedEdge;
} else if (heMesh->errorCode == internal::halfedge::Mesh::ErrorCode::DegenerateColocalEdge) {
error.code = AddMeshErrorCode::DegenerateColocalEdge;
} else if (heMesh->errorCode == internal::halfedge::Mesh::ErrorCode::DegenerateEdge) {
error.code = AddMeshErrorCode::DegenerateEdge;
} else if (heMesh->errorCode == internal::halfedge::Mesh::ErrorCode::DuplicateEdge) {
error.code = AddMeshErrorCode::DuplicateEdge;
}
error.face = i;
error.index0 = heMesh->errorIndex0;
error.index1 = heMesh->errorIndex1;
delete heMesh;
return error;
}
if (mesh.faceMaterialData)
face->material = mesh.faceMaterialData[i];
}
heMesh->linkBoundary();
atlas->heMeshes.push_back(heMesh);
return error;
}
void Generate(Atlas *atlas, CharterOptions charterOptions, PackerOptions packerOptions) {
xaAssert(atlas);
xaAssert(packerOptions.texelArea > 0);
// Chart meshes.
for (int i = 0; i < (int)atlas->heMeshes.size(); i++) {
std::vector<uint32_t> uncharted_materials;
atlas->atlas.computeCharts(atlas->heMeshes[i], charterOptions, uncharted_materials);
}
atlas->atlas.parameterizeCharts();
internal::param::AtlasPacker packer(&atlas->atlas);
packer.packCharts(packerOptions);
//float utilization = return packer.computeAtlasUtilization();
atlas->width = packer.getWidth();
atlas->height = packer.getHeight();
// Build output meshes.
atlas->outputMeshes = new OutputMesh *[atlas->heMeshes.size()];
for (int i = 0; i < (int)atlas->heMeshes.size(); i++) {
const internal::halfedge::Mesh *heMesh = atlas->heMeshes[i];
OutputMesh *outputMesh = atlas->outputMeshes[i] = new OutputMesh;
const internal::param::MeshCharts *charts = atlas->atlas.meshAt(i);
// Vertices.
outputMesh->vertexCount = charts->vertexCount();
outputMesh->vertexArray = new OutputVertex[outputMesh->vertexCount];
for (uint32_t i = 0; i < charts->chartCount(); i++) {
const internal::param::Chart *chart = charts->chartAt(i);
const uint32_t vertexOffset = charts->vertexCountBeforeChartAt(i);
for (uint32_t v = 0; v < chart->vertexCount(); v++) {
OutputVertex &output_vertex = outputMesh->vertexArray[vertexOffset + v];
output_vertex.xref = chart->mapChartVertexToOriginalVertex(v);
internal::Vector2 uv = chart->chartMesh()->vertexAt(v)->tex;
output_vertex.uv[0] = uv.x;
output_vertex.uv[1] = uv.y;
}
}
// Indices.
outputMesh->indexCount = heMesh->faceCount() * 3;
outputMesh->indexArray = new uint32_t[outputMesh->indexCount];
for (uint32_t f = 0; f < heMesh->faceCount(); f++) {
const uint32_t c = charts->faceChartAt(f);
const uint32_t i = charts->faceIndexWithinChartAt(f);
const uint32_t vertexOffset = charts->vertexCountBeforeChartAt(c);
const internal::param::Chart *chart = charts->chartAt(c);
xaDebugAssert(i < chart->chartMesh()->faceCount());
xaDebugAssert(chart->faceAt(i) == f);
const internal::halfedge::Face *face = chart->chartMesh()->faceAt(i);
const internal::halfedge::Edge *edge = face->edge;
outputMesh->indexArray[3 * f + 0] = vertexOffset + edge->vertex->id;
outputMesh->indexArray[3 * f + 1] = vertexOffset + edge->next->vertex->id;
outputMesh->indexArray[3 * f + 2] = vertexOffset + edge->next->next->vertex->id;
}
// Charts.
outputMesh->chartCount = charts->chartCount();
outputMesh->chartArray = new OutputChart[outputMesh->chartCount];
for (uint32_t i = 0; i < charts->chartCount(); i++) {
OutputChart *outputChart = &outputMesh->chartArray[i];
const internal::param::Chart *chart = charts->chartAt(i);
const uint32_t vertexOffset = charts->vertexCountBeforeChartAt(i);
const internal::halfedge::Mesh *mesh = chart->chartMesh();
outputChart->indexCount = mesh->faceCount() * 3;
outputChart->indexArray = new uint32_t[outputChart->indexCount];
for (uint32_t j = 0; j < mesh->faceCount(); j++) {
const internal::halfedge::Face *face = mesh->faceAt(j);
const internal::halfedge::Edge *edge = face->edge;
outputChart->indexArray[3 * j + 0] = vertexOffset + edge->vertex->id;
outputChart->indexArray[3 * j + 1] = vertexOffset + edge->next->vertex->id;
outputChart->indexArray[3 * j + 2] = vertexOffset + edge->next->next->vertex->id;
}
}
}
}
uint32_t GetWidth(const Atlas *atlas) {
xaAssert(atlas);
return atlas->width;
}
uint32_t GetHeight(const Atlas *atlas) {
xaAssert(atlas);
return atlas->height;
}
uint32_t GetNumCharts(const Atlas *atlas) {
xaAssert(atlas);
return atlas->atlas.chartCount();
}
const OutputMesh *const *GetOutputMeshes(const Atlas *atlas) {
xaAssert(atlas);
return atlas->outputMeshes;
}
const char *StringForEnum(AddMeshErrorCode::Enum error) {
if (error == AddMeshErrorCode::AlreadyAddedEdge)
return "already added edge";
if (error == AddMeshErrorCode::DegenerateColocalEdge)
return "degenerate colocal edge";
if (error == AddMeshErrorCode::DegenerateEdge)
return "degenerate edge";
if (error == AddMeshErrorCode::DuplicateEdge)
return "duplicate edge";
if (error == AddMeshErrorCode::IndexOutOfRange)
return "index out of range";
if (error == AddMeshErrorCode::InvalidIndexCount)
return "invalid index count";
if (error == AddMeshErrorCode::ZeroAreaFace)
return "zero area face";
if (error == AddMeshErrorCode::ZeroLengthEdge)
return "zero length edge";
return "success";
}
} // namespace xatlas