summaryrefslogtreecommitdiff
path: root/thirdparty/thekla_atlas/nvmesh
diff options
context:
space:
mode:
Diffstat (limited to 'thirdparty/thekla_atlas/nvmesh')
-rw-r--r--thirdparty/thekla_atlas/nvmesh/BaseMesh.cpp19
-rw-r--r--thirdparty/thekla_atlas/nvmesh/BaseMesh.h72
-rw-r--r--thirdparty/thekla_atlas/nvmesh/MeshBuilder.cpp1000
-rw-r--r--thirdparty/thekla_atlas/nvmesh/MeshBuilder.h119
-rw-r--r--thirdparty/thekla_atlas/nvmesh/MeshTopology.cpp122
-rw-r--r--thirdparty/thekla_atlas/nvmesh/MeshTopology.h66
-rw-r--r--thirdparty/thekla_atlas/nvmesh/QuadTriMesh.cpp36
-rw-r--r--thirdparty/thekla_atlas/nvmesh/QuadTriMesh.h60
-rw-r--r--thirdparty/thekla_atlas/nvmesh/TriMesh.cpp25
-rw-r--r--thirdparty/thekla_atlas/nvmesh/TriMesh.h51
-rw-r--r--thirdparty/thekla_atlas/nvmesh/geometry/Bounds.cpp54
-rw-r--r--thirdparty/thekla_atlas/nvmesh/geometry/Bounds.h28
-rw-r--r--thirdparty/thekla_atlas/nvmesh/geometry/Measurements.cpp36
-rw-r--r--thirdparty/thekla_atlas/nvmesh/geometry/Measurements.h18
-rw-r--r--thirdparty/thekla_atlas/nvmesh/halfedge/Edge.cpp57
-rw-r--r--thirdparty/thekla_atlas/nvmesh/halfedge/Edge.h70
-rw-r--r--thirdparty/thekla_atlas/nvmesh/halfedge/Face.cpp268
-rw-r--r--thirdparty/thekla_atlas/nvmesh/halfedge/Face.h106
-rw-r--r--thirdparty/thekla_atlas/nvmesh/halfedge/Mesh.cpp1284
-rw-r--r--thirdparty/thekla_atlas/nvmesh/halfedge/Mesh.h274
-rw-r--r--thirdparty/thekla_atlas/nvmesh/halfedge/Vertex.cpp94
-rw-r--r--thirdparty/thekla_atlas/nvmesh/halfedge/Vertex.h221
-rw-r--r--thirdparty/thekla_atlas/nvmesh/nvmesh.cpp2
-rw-r--r--thirdparty/thekla_atlas/nvmesh/nvmesh.h34
-rw-r--r--thirdparty/thekla_atlas/nvmesh/param/Atlas.cpp1515
-rw-r--r--thirdparty/thekla_atlas/nvmesh/param/Atlas.h183
-rw-r--r--thirdparty/thekla_atlas/nvmesh/param/AtlasBuilder.cpp1320
-rw-r--r--thirdparty/thekla_atlas/nvmesh/param/AtlasBuilder.h111
-rw-r--r--thirdparty/thekla_atlas/nvmesh/param/AtlasPacker.cpp1379
-rw-r--r--thirdparty/thekla_atlas/nvmesh/param/AtlasPacker.h63
-rw-r--r--thirdparty/thekla_atlas/nvmesh/param/LeastSquaresConformalMap.cpp483
-rw-r--r--thirdparty/thekla_atlas/nvmesh/param/LeastSquaresConformalMap.h15
-rw-r--r--thirdparty/thekla_atlas/nvmesh/param/OrthogonalProjectionMap.cpp99
-rw-r--r--thirdparty/thekla_atlas/nvmesh/param/OrthogonalProjectionMap.h15
-rw-r--r--thirdparty/thekla_atlas/nvmesh/param/ParameterizationQuality.cpp323
-rw-r--r--thirdparty/thekla_atlas/nvmesh/param/ParameterizationQuality.h56
-rw-r--r--thirdparty/thekla_atlas/nvmesh/param/SingleFaceMap.cpp53
-rw-r--r--thirdparty/thekla_atlas/nvmesh/param/SingleFaceMap.h18
-rw-r--r--thirdparty/thekla_atlas/nvmesh/param/Util.cpp326
-rw-r--r--thirdparty/thekla_atlas/nvmesh/param/Util.h18
-rw-r--r--thirdparty/thekla_atlas/nvmesh/raster/ClippedTriangle.h159
-rw-r--r--thirdparty/thekla_atlas/nvmesh/raster/Raster.cpp626
-rw-r--r--thirdparty/thekla_atlas/nvmesh/raster/Raster.h49
-rw-r--r--thirdparty/thekla_atlas/nvmesh/weld/Snap.cpp100
-rw-r--r--thirdparty/thekla_atlas/nvmesh/weld/Snap.h18
-rw-r--r--thirdparty/thekla_atlas/nvmesh/weld/VertexWeld.cpp205
-rw-r--r--thirdparty/thekla_atlas/nvmesh/weld/VertexWeld.h19
-rw-r--r--thirdparty/thekla_atlas/nvmesh/weld/Weld.h171
48 files changed, 11440 insertions, 0 deletions
diff --git a/thirdparty/thekla_atlas/nvmesh/BaseMesh.cpp b/thirdparty/thekla_atlas/nvmesh/BaseMesh.cpp
new file mode 100644
index 0000000000..f17d3b46fd
--- /dev/null
+++ b/thirdparty/thekla_atlas/nvmesh/BaseMesh.cpp
@@ -0,0 +1,19 @@
+// This code is in the public domain -- Ignacio Castaņo <castano@gmail.com>
+
+#include "BaseMesh.h"
+#include "Stream.h"
+#include "nvmath/TypeSerialization.h"
+
+
+namespace nv
+{
+ static Stream & operator<< (Stream & s, BaseMesh::Vertex & vertex)
+ {
+ return s << vertex.id << vertex.pos << vertex.nor << vertex.tex;
+ }
+
+ Stream & operator<< (Stream & s, BaseMesh & mesh)
+ {
+ return s << mesh.m_vertexArray;
+ }
+}
diff --git a/thirdparty/thekla_atlas/nvmesh/BaseMesh.h b/thirdparty/thekla_atlas/nvmesh/BaseMesh.h
new file mode 100644
index 0000000000..c8559511f1
--- /dev/null
+++ b/thirdparty/thekla_atlas/nvmesh/BaseMesh.h
@@ -0,0 +1,72 @@
+// This code is in the public domain -- Ignacio Castaņo <castano@gmail.com>
+
+#pragma once
+#ifndef NV_MESH_BASEMESH_H
+#define NV_MESH_BASEMESH_H
+
+#include "nvmesh.h"
+#include "nvmath/Vector.h"
+#include "nvcore/Array.h"
+#include "nvcore/Hash.h"
+
+namespace nv
+{
+
+ /// Base mesh without connectivity.
+ class BaseMesh
+ {
+ public:
+ struct Vertex;
+
+ BaseMesh() {}
+
+ BaseMesh(uint vertexNum) :
+ m_vertexArray(vertexNum) {}
+
+ // Vertex methods.
+ uint vertexCount() const { return m_vertexArray.count(); }
+ const Vertex & vertexAt(uint i) const { return m_vertexArray[i]; }
+ Vertex & vertexAt(uint i) { return m_vertexArray[i]; }
+ const Array<Vertex> & vertices() const { return m_vertexArray; }
+ Array<Vertex> & vertices() { return m_vertexArray; }
+
+ friend Stream & operator<< (Stream & s, BaseMesh & obj);
+
+ protected:
+
+ Array<Vertex> m_vertexArray;
+ };
+
+
+ /// BaseMesh vertex.
+ struct BaseMesh::Vertex
+ {
+ Vertex() : id(NIL), pos(0.0f), nor(0.0f), tex(0.0f) {}
+
+ uint id; // @@ Vertex should be an index into the vertex data.
+ Vector3 pos;
+ Vector3 nor;
+ Vector2 tex;
+ };
+
+ inline bool operator==(const BaseMesh::Vertex & a, const BaseMesh::Vertex & b)
+ {
+ return a.pos == b.pos && a.nor == b.nor && a.tex == b.tex;
+ }
+
+ inline bool operator!=(const BaseMesh::Vertex & a, const BaseMesh::Vertex & b)
+ {
+ return a.pos != b.pos && a.nor != b.nor && a.tex != b.tex;
+ }
+
+ template <> struct Hash<BaseMesh::Vertex>
+ {
+ uint operator()(const BaseMesh::Vertex & v) const
+ {
+ return Hash<Vector3>()(v.pos);
+ }
+ };
+
+} // nv namespace
+
+#endif // NV_MESH_BASEMESH_H
diff --git a/thirdparty/thekla_atlas/nvmesh/MeshBuilder.cpp b/thirdparty/thekla_atlas/nvmesh/MeshBuilder.cpp
new file mode 100644
index 0000000000..24d8ddff89
--- /dev/null
+++ b/thirdparty/thekla_atlas/nvmesh/MeshBuilder.cpp
@@ -0,0 +1,1000 @@
+// This code is in the public domain -- castanyo@yahoo.es
+
+#include "nvmesh.h" // pch
+
+#include "MeshBuilder.h"
+#include "TriMesh.h"
+#include "QuadTriMesh.h"
+#include "halfedge/Mesh.h"
+#include "halfedge/Vertex.h"
+#include "halfedge/Face.h"
+
+#include "weld/Weld.h"
+
+#include "nvmath/Box.h"
+#include "nvmath/Vector.inl"
+
+#include "nvcore/StrLib.h"
+#include "nvcore/RadixSort.h"
+#include "nvcore/Ptr.h"
+#include "nvcore/Array.inl"
+#include "nvcore/HashMap.inl"
+
+
+using namespace nv;
+
+/*
+By default the mesh builder creates 3 streams (position, normal, texcoord), I'm planning to add support for extra streams as follows:
+
+enum StreamType { StreamType_Float, StreamType_Vector2, StreamType_Vector3, StreamType_Vector4 };
+
+uint addStream(const char *, uint idx, StreamType);
+
+uint addAttribute(float)
+uint addAttribute(Vector2)
+uint addAttribute(Vector3)
+uint addAttribute(Vector4)
+
+struct Vertex
+{
+ uint pos;
+ uint nor;
+ uint tex;
+ uint * attribs; // NULL or NIL terminated array?
+};
+
+All streams must be added before hand, so that you know the size of the attribs array.
+
+The vertex hash function could be kept as is, but the == operator should be extended to test
+the extra atributes when available.
+
+That might require a custom hash implementation, or an extension of the current one. How to
+handle the variable number of attributes in the attribs array?
+
+bool operator()(const Vertex & a, const Vertex & b) const
+{
+ if (a.pos != b.pos || a.nor != b.nor || a.tex != b.tex) return false;
+ if (a.attribs == NULL && b.attribs == NULL) return true;
+ return 0 == memcmp(a.attribs, b.attribs, ???);
+}
+
+We could use a NIL terminated array, or provide custom user data to the equals functor.
+
+vertexMap.setUserData((void *)vertexAttribCount);
+
+bool operator()(const Vertex & a, const Vertex & b, void * userData) const { ... }
+
+*/
+
+
+
+namespace
+{
+ struct Material
+ {
+ Material() : faceCount(0) {}
+ Material(const String & str) : name(str), faceCount(0) {}
+
+ String name;
+ uint faceCount;
+ };
+
+ struct Vertex
+ {
+ //Vertex() {}
+ //Vertex(uint p, uint n, uint t0, uint t1, uint c) : pos(p), nor(n), tex0(t0), tex1(t1), col(c) {}
+
+ friend bool operator==(const Vertex & a, const Vertex & b)
+ {
+ return a.pos == b.pos && a.nor == b.nor && a.tex[0] == b.tex[0] && a.tex[1] == b.tex[1] && a.col[0] == b.col[0] && a.col[1] == b.col[1] && a.col[2] == b.col[2];
+ }
+
+ uint pos;
+ uint nor;
+ uint tex[2];
+ uint col[3];
+ };
+
+ struct Face
+ {
+ uint id;
+ uint firstIndex;
+ uint indexCount;
+ uint material;
+ uint group;
+ };
+
+} // namespace
+
+
+namespace nv
+{
+ // This is a much better hash than the default and greatly improves performance!
+ template <> struct Hash<Vertex>
+ {
+ uint operator()(const Vertex & v) const { return v.pos + v.nor + v.tex[0]/* + v.col*/; }
+ };
+}
+
+struct MeshBuilder::PrivateData
+{
+ PrivateData() : currentGroup(NIL), currentMaterial(NIL), maxFaceIndexCount(0) {}
+
+ uint pushVertex(uint p, uint n, uint t0, uint t1, uint c0, uint c1, uint c2);
+ uint pushVertex(const Vertex & v);
+
+ Array<Vector3> posArray;
+ Array<Vector3> norArray;
+ Array<Vector2> texArray[2];
+ Array<Vector4> colArray[3];
+
+ Array<Vertex> vertexArray;
+ HashMap<Vertex, uint> vertexMap;
+
+ HashMap<String, uint> materialMap;
+ Array<Material> materialArray;
+
+ uint currentGroup;
+ uint currentMaterial;
+
+ Array<uint> indexArray;
+ Array<Face> faceArray;
+
+ uint maxFaceIndexCount;
+};
+
+
+uint MeshBuilder::PrivateData::pushVertex(uint p, uint n, uint t0, uint t1, uint c0, uint c1, uint c2)
+{
+ Vertex v;
+ v.pos = p;
+ v.nor = n;
+ v.tex[0] = t0;
+ v.tex[1] = t1;
+ v.col[0] = c0;
+ v.col[1] = c1;
+ v.col[2] = c2;
+ return pushVertex(v);
+}
+
+uint MeshBuilder::PrivateData::pushVertex(const Vertex & v)
+{
+ // Lookup vertex v in map.
+ uint idx;
+ if (vertexMap.get(v, &idx))
+ {
+ return idx;
+ }
+
+ idx = vertexArray.count();
+ vertexArray.pushBack(v);
+ vertexMap.add(v, idx);
+
+ return idx;
+}
+
+
+MeshBuilder::MeshBuilder() : d(new PrivateData())
+{
+}
+
+MeshBuilder::~MeshBuilder()
+{
+ nvDebugCheck(d != NULL);
+ delete d;
+}
+
+
+// Builder methods.
+uint MeshBuilder::addPosition(const Vector3 & v)
+{
+ d->posArray.pushBack(validate(v));
+ return d->posArray.count() - 1;
+}
+
+uint MeshBuilder::addNormal(const Vector3 & v)
+{
+ d->norArray.pushBack(validate(v));
+ return d->norArray.count() - 1;
+}
+
+uint MeshBuilder::addTexCoord(const Vector2 & v, uint set/*=0*/)
+{
+ d->texArray[set].pushBack(validate(v));
+ return d->texArray[set].count() - 1;
+}
+
+uint MeshBuilder::addColor(const Vector4 & v, uint set/*=0*/)
+{
+ d->colArray[set].pushBack(validate(v));
+ return d->colArray[set].count() - 1;
+}
+
+void MeshBuilder::beginGroup(uint id)
+{
+ d->currentGroup = id;
+}
+
+void MeshBuilder::endGroup()
+{
+ d->currentGroup = NIL;
+}
+
+// Add named material, check for uniquenes.
+uint MeshBuilder::addMaterial(const char * name)
+{
+ uint index;
+ if (d->materialMap.get(name, &index)) {
+ nvDebugCheck(d->materialArray[index].name == name);
+ }
+ else {
+ index = d->materialArray.count();
+ d->materialMap.add(name, index);
+
+ Material material(name);
+ d->materialArray.append(material);
+ }
+ return index;
+}
+
+void MeshBuilder::beginMaterial(uint id)
+{
+ d->currentMaterial = id;
+}
+
+void MeshBuilder::endMaterial()
+{
+ d->currentMaterial = NIL;
+}
+
+void MeshBuilder::beginPolygon(uint id/*=0*/)
+{
+ Face face;
+ face.id = id;
+ face.firstIndex = d->indexArray.count();
+ face.indexCount = 0;
+ face.material = d->currentMaterial;
+ face.group = d->currentGroup;
+
+ d->faceArray.pushBack(face);
+}
+
+uint MeshBuilder::addVertex(uint p, uint n/*= NIL*/, uint t0/*= NIL*/, uint t1/*= NIL*/, uint c0/*= NIL*/, uint c1/*= NIL*/, uint c2/*= NIL*/)
+{
+ // @@ In theory there's no need to add vertices before faces, but I'm adding this to debug problems in our maya exporter:
+ nvDebugCheck(p < d->posArray.count());
+ nvDebugCheck(n == NIL || n < d->norArray.count());
+ nvDebugCheck(t0 == NIL || t0 < d->texArray[0].count());
+ nvDebugCheck(t1 == NIL || t1 < d->texArray[1].count());
+ //nvDebugCheck(c0 == NIL || c0 < d->colArray[0].count());
+ if (c0 > d->colArray[0].count()) c0 = NIL; // @@ This seems to be happening in loc_swamp_catwalk.mb! No idea why.
+ nvDebugCheck(c1 == NIL || c1 < d->colArray[1].count());
+ nvDebugCheck(c2 == NIL || c2 < d->colArray[2].count());
+
+ uint idx = d->pushVertex(p, n, t0, t1, c0, c1, c2);
+ d->indexArray.pushBack(idx);
+ d->faceArray.back().indexCount++;
+ return idx;
+}
+
+uint MeshBuilder::addVertex(const Vector3 & pos)
+{
+ uint p = addPosition(pos);
+ return addVertex(p);
+}
+
+#if 0
+uint MeshBuilder::addVertex(const Vector3 & pos, const Vector3 & nor, const Vector2 & tex0, const Vector2 & tex1, const Vector4 & col0, const Vector4 & col1)
+{
+ uint p = addPosition(pos);
+ uint n = addNormal(nor);
+ uint t0 = addTexCoord(tex0, 0);
+ uint t1 = addTexCoord(tex1, 1);
+ uint c0 = addColor(col0);
+ uint c1 = addColor(col1);
+ return addVertex(p, n, t0, t1, c0, c1);
+}
+#endif
+
+// Return true if the face is valid and was added to the mesh.
+bool MeshBuilder::endPolygon()
+{
+ const Face & face = d->faceArray.back();
+ const uint count = face.indexCount;
+
+ // Validate polygon here.
+ bool invalid = count <= 2;
+
+ if (!invalid) {
+ // Skip zero area polygons. Or polygons with degenerate edges (which will result in zero-area triangles).
+ const uint first = face.firstIndex;
+ for (uint j = count - 1, i = 0; i < count; j = i, i++) {
+ uint v0 = d->indexArray[first + i];
+ uint v1 = d->indexArray[first + j];
+
+ uint p0 = d->vertexArray[v0].pos;
+ uint p1 = d->vertexArray[v1].pos;
+
+ if (p0 == p1) {
+ invalid = true;
+ break;
+ }
+
+ if (equal(d->posArray[p0], d->posArray[p1], FLT_EPSILON)) {
+ invalid = true;
+ break;
+ }
+ }
+
+ uint v0 = d->indexArray[first];
+ uint p0 = d->vertexArray[v0].pos;
+ Vector3 x0 = d->posArray[p0];
+
+ float area = 0.0f;
+ for (uint j = 1, i = 2; i < count; j = i, i++) {
+ uint v1 = d->indexArray[first + i];
+ uint v2 = d->indexArray[first + j];
+
+ uint p1 = d->vertexArray[v1].pos;
+ uint p2 = d->vertexArray[v2].pos;
+
+ Vector3 x1 = d->posArray[p1];
+ Vector3 x2 = d->posArray[p2];
+
+ area += length(cross(x1-x0, x2-x0));
+ }
+
+ if (0.5 * area < 1e-6) { // Reduce this threshold if artists have legitimate complains.
+ invalid = true;
+ }
+
+ // @@ This is not complete. We may still get zero area triangles after triangulation.
+ // However, our plugin triangulates before building the mesh, so hopefully that's not a problem.
+
+ }
+
+ if (invalid)
+ {
+ d->indexArray.resize(d->indexArray.size() - count);
+ d->faceArray.popBack();
+ return false;
+ }
+ else
+ {
+ if (d->currentMaterial != NIL) {
+ d->materialArray[d->currentMaterial].faceCount++;
+ }
+
+ d->maxFaceIndexCount = max(d->maxFaceIndexCount, count);
+ return true;
+ }
+}
+
+
+uint MeshBuilder::weldPositions()
+{
+ Array<uint> xrefs;
+ Weld<Vector3> weldVector3;
+
+ if (d->posArray.count()) {
+ // Weld vertex attributes.
+ weldVector3(d->posArray, xrefs);
+
+ // Remap vertex indices.
+ const uint vertexCount = d->vertexArray.count();
+ for (uint v = 0; v < vertexCount; v++)
+ {
+ Vertex & vertex = d->vertexArray[v];
+ if (vertex.pos != NIL) vertex.pos = xrefs[vertex.pos];
+ }
+ }
+
+ return d->posArray.count();
+}
+
+uint MeshBuilder::weldNormals()
+{
+ Array<uint> xrefs;
+ Weld<Vector3> weldVector3;
+
+ if (d->norArray.count()) {
+ // Weld vertex attributes.
+ weldVector3(d->norArray, xrefs);
+
+ // Remap vertex indices.
+ const uint vertexCount = d->vertexArray.count();
+ for (uint v = 0; v < vertexCount; v++)
+ {
+ Vertex & vertex = d->vertexArray[v];
+ if (vertex.nor != NIL) vertex.nor = xrefs[vertex.nor];
+ }
+ }
+
+ return d->norArray.count();
+}
+
+uint MeshBuilder::weldTexCoords(uint set/*=0*/)
+{
+ Array<uint> xrefs;
+ Weld<Vector2> weldVector2;
+
+ if (d->texArray[set].count()) {
+ // Weld vertex attributes.
+ weldVector2(d->texArray[set], xrefs);
+
+ // Remap vertex indices.
+ const uint vertexCount = d->vertexArray.count();
+ for (uint v = 0; v < vertexCount; v++)
+ {
+ Vertex & vertex = d->vertexArray[v];
+ if (vertex.tex[set] != NIL) vertex.tex[set] = xrefs[vertex.tex[set]];
+ }
+ }
+
+ return d->texArray[set].count();
+}
+
+uint MeshBuilder::weldColors(uint set/*=0*/)
+{
+ Array<uint> xrefs;
+ Weld<Vector4> weldVector4;
+
+ if (d->colArray[set].count()) {
+ // Weld vertex attributes.
+ weldVector4(d->colArray[set], xrefs);
+
+ // Remap vertex indices.
+ const uint vertexCount = d->vertexArray.count();
+ for (uint v = 0; v < vertexCount; v++)
+ {
+ Vertex & vertex = d->vertexArray[v];
+ if (vertex.col[set] != NIL) vertex.col[set] = xrefs[vertex.col[set]];
+ }
+ }
+
+ return d->colArray[set].count();
+}
+
+void MeshBuilder::weldVertices() {
+
+ if (d->vertexArray.count() == 0) {
+ // Nothing to do.
+ return;
+ }
+
+ Array<uint> xrefs;
+ Weld<Vertex> weldVertex;
+
+ // Weld vertices.
+ weldVertex(d->vertexArray, xrefs);
+
+ // Remap face indices.
+ const uint indexCount = d->indexArray.count();
+ for (uint i = 0; i < indexCount; i++)
+ {
+ d->indexArray[i] = xrefs[d->indexArray[i]];
+ }
+
+ // Remap vertex map.
+ foreach(i, d->vertexMap)
+ {
+ d->vertexMap[i].value = xrefs[d->vertexMap[i].value];
+ }
+}
+
+
+void MeshBuilder::optimize()
+{
+ if (d->vertexArray.count() == 0)
+ {
+ return;
+ }
+
+ weldPositions();
+ weldNormals();
+ weldTexCoords(0);
+ weldTexCoords(1);
+ weldColors();
+
+ weldVertices();
+}
+
+
+
+
+
+
+void MeshBuilder::removeUnusedMaterials(Array<uint> & newMaterialId)
+{
+ uint materialCount = d->materialArray.count();
+
+ // Reset face counts.
+ for (uint i = 0; i < materialCount; i++) {
+ d->materialArray[i].faceCount = 0;
+ }
+
+ // Count faces.
+ foreach(i, d->faceArray) {
+ Face & face = d->faceArray[i];
+
+ if (face.material != NIL) {
+ nvDebugCheck(face.material < materialCount);
+
+ d->materialArray[face.material].faceCount++;
+ }
+ }
+
+ // Remove unused materials.
+ newMaterialId.resize(materialCount);
+
+ for (uint i = 0, m = 0; i < materialCount; i++)
+ {
+ if (d->materialArray[m].faceCount > 0)
+ {
+ newMaterialId[i] = m++;
+ }
+ else
+ {
+ newMaterialId[i] = NIL;
+ d->materialArray.removeAt(m);
+ }
+ }
+
+ materialCount = d->materialArray.count();
+
+ // Update face material ids.
+ foreach(i, d->faceArray) {
+ Face & face = d->faceArray[i];
+
+ if (face.material != NIL) {
+ uint id = newMaterialId[face.material];
+ nvDebugCheck(id != NIL && id < materialCount);
+
+ face.material = id;
+ }
+ }
+}
+
+void MeshBuilder::sortFacesByGroup()
+{
+ const uint faceCount = d->faceArray.count();
+
+ Array<uint> faceGroupArray;
+ faceGroupArray.resize(faceCount);
+
+ for (uint i = 0; i < faceCount; i++) {
+ faceGroupArray[i] = d->faceArray[i].group;
+ }
+
+ RadixSort radix;
+ radix.sort(faceGroupArray);
+
+ Array<Face> newFaceArray;
+ newFaceArray.resize(faceCount);
+
+ for (uint i = 0; i < faceCount; i++) {
+ newFaceArray[i] = d->faceArray[radix.rank(i)];
+ }
+
+ swap(newFaceArray, d->faceArray);
+}
+
+void MeshBuilder::sortFacesByMaterial()
+{
+ const uint faceCount = d->faceArray.count();
+
+ Array<uint> faceMaterialArray;
+ faceMaterialArray.resize(faceCount);
+
+ for (uint i = 0; i < faceCount; i++) {
+ faceMaterialArray[i] = d->faceArray[i].material;
+ }
+
+ RadixSort radix;
+ radix.sort(faceMaterialArray);
+
+ Array<Face> newFaceArray;
+ newFaceArray.resize(faceCount);
+
+ for (uint i = 0; i < faceCount; i++) {
+ newFaceArray[i] = d->faceArray[radix.rank(i)];
+ }
+
+ swap(newFaceArray, d->faceArray);
+}
+
+
+void MeshBuilder::reset()
+{
+ nvDebugCheck(d != NULL);
+ delete d;
+ d = new PrivateData();
+}
+
+void MeshBuilder::done()
+{
+ if (d->currentGroup != NIL) {
+ endGroup();
+ }
+
+ if (d->currentMaterial != NIL) {
+ endMaterial();
+ }
+}
+
+// Hints.
+void MeshBuilder::hintTriangleCount(uint count)
+{
+ d->indexArray.reserve(d->indexArray.count() + count * 4);
+}
+
+void MeshBuilder::hintVertexCount(uint count)
+{
+ d->vertexArray.reserve(d->vertexArray.count() + count);
+ d->vertexMap.resize(d->vertexMap.count() + count);
+}
+
+void MeshBuilder::hintPositionCount(uint count)
+{
+ d->posArray.reserve(d->posArray.count() + count);
+}
+
+void MeshBuilder::hintNormalCount(uint count)
+{
+ d->norArray.reserve(d->norArray.count() + count);
+}
+
+void MeshBuilder::hintTexCoordCount(uint count, uint set/*=0*/)
+{
+ d->texArray[set].reserve(d->texArray[set].count() + count);
+}
+
+void MeshBuilder::hintColorCount(uint count, uint set/*=0*/)
+{
+ d->colArray[set].reserve(d->colArray[set].count() + count);
+}
+
+
+// Helpers.
+void MeshBuilder::addTriangle(uint v0, uint v1, uint v2)
+{
+ beginPolygon();
+ addVertex(v0);
+ addVertex(v1);
+ addVertex(v2);
+ endPolygon();
+}
+
+void MeshBuilder::addQuad(uint v0, uint v1, uint v2, uint v3)
+{
+ beginPolygon();
+ addVertex(v0);
+ addVertex(v1);
+ addVertex(v2);
+ addVertex(v3);
+ endPolygon();
+}
+
+
+// Get tri mesh.
+TriMesh * MeshBuilder::buildTriMesh() const
+{
+ const uint faceCount = d->faceArray.count();
+ uint triangleCount = 0;
+ for (uint f = 0; f < faceCount; f++) {
+ triangleCount += d->faceArray[f].indexCount - 2;
+ }
+
+ const uint vertexCount = d->vertexArray.count();
+ TriMesh * mesh = new TriMesh(triangleCount, vertexCount);
+
+ // Build faces.
+ Array<TriMesh::Face> & faces = mesh->faces();
+
+ for(uint f = 0; f < faceCount; f++)
+ {
+ int firstIndex = d->faceArray[f].firstIndex;
+ int indexCount = d->faceArray[f].indexCount;
+
+ int v0 = d->indexArray[firstIndex + 0];
+ int v1 = d->indexArray[firstIndex + 1];
+
+ for(int t = 0; t < indexCount - 2; t++) {
+ int v2 = d->indexArray[firstIndex + t + 2];
+
+ TriMesh::Face face;
+ face.id = faces.count();
+ face.v[0] = v0;
+ face.v[1] = v1;
+ face.v[2] = v2;
+ faces.append(face);
+
+ v1 = v2;
+ }
+ }
+
+ // Build vertices.
+ Array<BaseMesh::Vertex> & vertices = mesh->vertices();
+
+ for(uint i = 0; i < vertexCount; i++)
+ {
+ BaseMesh::Vertex vertex;
+ vertex.id = i;
+ if (d->vertexArray[i].pos != NIL) vertex.pos = d->posArray[d->vertexArray[i].pos];
+ if (d->vertexArray[i].nor != NIL) vertex.nor = d->norArray[d->vertexArray[i].nor];
+ if (d->vertexArray[i].tex[0] != NIL) vertex.tex = d->texArray[0][d->vertexArray[i].tex[0]];
+
+ vertices.append(vertex);
+ }
+
+ return mesh;
+}
+
+// Get quad/tri mesh.
+QuadTriMesh * MeshBuilder::buildQuadTriMesh() const
+{
+ const uint faceCount = d->faceArray.count();
+ const uint vertexCount = d->vertexArray.count();
+ QuadTriMesh * mesh = new QuadTriMesh(faceCount, vertexCount);
+
+ // Build faces.
+ Array<QuadTriMesh::Face> & faces = mesh->faces();
+
+ for (uint f = 0; f < faceCount; f++)
+ {
+ int firstIndex = d->faceArray[f].firstIndex;
+ int indexCount = d->faceArray[f].indexCount;
+
+ QuadTriMesh::Face face;
+ face.id = f;
+
+ face.v[0] = d->indexArray[firstIndex + 0];
+ face.v[1] = d->indexArray[firstIndex + 1];
+ face.v[2] = d->indexArray[firstIndex + 2];
+
+ // Only adds triangles and quads. Ignores polygons.
+ if (indexCount == 3) {
+ face.v[3] = NIL;
+ faces.append(face);
+ }
+ else if (indexCount == 4) {
+ face.v[3] = d->indexArray[firstIndex + 3];
+ faces.append(face);
+ }
+ }
+
+ // Build vertices.
+ Array<BaseMesh::Vertex> & vertices = mesh->vertices();
+
+ for(uint i = 0; i < vertexCount; i++)
+ {
+ BaseMesh::Vertex vertex;
+ vertex.id = i;
+ if (d->vertexArray[i].pos != NIL) vertex.pos = d->posArray[d->vertexArray[i].pos];
+ if (d->vertexArray[i].nor != NIL) vertex.nor = d->norArray[d->vertexArray[i].nor];
+ if (d->vertexArray[i].tex[0] != NIL) vertex.tex = d->texArray[0][d->vertexArray[i].tex[0]];
+
+ vertices.append(vertex);
+ }
+
+ return mesh;
+}
+
+// Get half edge mesh.
+HalfEdge::Mesh * MeshBuilder::buildHalfEdgeMesh(bool weldPositions, Error * error/*=NULL*/, Array<uint> * badFaces/*=NULL*/) const
+{
+ if (error != NULL) *error = Error_None;
+
+ const uint vertexCount = d->vertexArray.count();
+ AutoPtr<HalfEdge::Mesh> mesh(new HalfEdge::Mesh());
+
+ for(uint v = 0; v < vertexCount; v++)
+ {
+ HalfEdge::Vertex * vertex = mesh->addVertex(d->posArray[d->vertexArray[v].pos]);
+ if (d->vertexArray[v].nor != NIL) vertex->nor = d->norArray[d->vertexArray[v].nor];
+ if (d->vertexArray[v].tex[0] != NIL) vertex->tex = Vector2(d->texArray[0][d->vertexArray[v].tex[0]]);
+ if (d->vertexArray[v].col[0] != NIL) vertex->col = d->colArray[0][d->vertexArray[v].col[0]];
+ }
+
+ if (weldPositions) {
+ mesh->linkColocals();
+ }
+ else {
+ // Build canonical map from position indices.
+ Array<uint> canonicalMap(vertexCount);
+
+ foreach (i, d->vertexArray) {
+ canonicalMap.append(d->vertexArray[i].pos);
+ }
+
+ mesh->linkColocalsWithCanonicalMap(canonicalMap);
+ }
+
+ const uint faceCount = d->faceArray.count();
+ for (uint f = 0; f < faceCount; f++)
+ {
+ const uint firstIndex = d->faceArray[f].firstIndex;
+ const uint indexCount = d->faceArray[f].indexCount;
+
+ HalfEdge::Face * face = mesh->addFace(d->indexArray, firstIndex, indexCount);
+
+ // @@ This is too late, removing the face here will leave the mesh improperly connected.
+ /*if (face->area() <= FLT_EPSILON) {
+ mesh->remove(face);
+ face = NULL;
+ }*/
+
+ if (face == NULL) {
+ // Non manifold mesh.
+ if (error != NULL) *error = Error_NonManifoldEdge;
+ if (badFaces != NULL) {
+ badFaces->append(d->faceArray[f].id);
+ }
+ //return NULL; // IC: Ignore error and continue building the mesh.
+ }
+
+ if (face != NULL) {
+ face->group = d->faceArray[f].group;
+ face->material = d->faceArray[f].material;
+ }
+ }
+
+ mesh->linkBoundary();
+
+ // We cannot fix functions here, because this would introduce new vertices and these vertices won't have the corresponding builder data.
+
+ // Maybe the builder should perform the search for T-junctions and update the vertex data directly.
+
+ // For now, we don't fix T-junctions at export time, but only during parameterization.
+
+ //mesh->fixBoundaryJunctions();
+
+ //mesh->sewBoundary();
+
+ return mesh.release();
+}
+
+
+bool MeshBuilder::buildPositions(Array<Vector3> & positionArray)
+{
+ const uint vertexCount = d->vertexArray.count();
+ positionArray.resize(vertexCount);
+
+ for (uint v = 0; v < vertexCount; v++)
+ {
+ nvDebugCheck(d->vertexArray[v].pos != NIL);
+ positionArray[v] = d->posArray[d->vertexArray[v].pos];
+ }
+
+ return true;
+}
+
+bool MeshBuilder::buildNormals(Array<Vector3> & normalArray)
+{
+ bool anyNormal = false;
+
+ const uint vertexCount = d->vertexArray.count();
+ normalArray.resize(vertexCount);
+
+ for (uint v = 0; v < vertexCount; v++)
+ {
+ if (d->vertexArray[v].nor == NIL) {
+ normalArray[v] = Vector3(0, 0, 1);
+ }
+ else {
+ anyNormal = true;
+ normalArray[v] = d->norArray[d->vertexArray[v].nor];
+ }
+ }
+
+ return anyNormal;
+}
+
+bool MeshBuilder::buildTexCoords(Array<Vector2> & texCoordArray, uint set/*=0*/)
+{
+ bool anyTexCoord = false;
+
+ const uint vertexCount = d->vertexArray.count();
+ texCoordArray.resize(vertexCount);
+
+ for (uint v = 0; v < vertexCount; v++)
+ {
+ if (d->vertexArray[v].tex[set] == NIL) {
+ texCoordArray[v] = Vector2(0, 0);
+ }
+ else {
+ anyTexCoord = true;
+ texCoordArray[v] = d->texArray[set][d->vertexArray[v].tex[set]];
+ }
+ }
+
+ return anyTexCoord;
+}
+
+bool MeshBuilder::buildColors(Array<Vector4> & colorArray, uint set/*=0*/)
+{
+ bool anyColor = false;
+
+ const uint vertexCount = d->vertexArray.count();
+ colorArray.resize(vertexCount);
+
+ for (uint v = 0; v < vertexCount; v++)
+ {
+ if (d->vertexArray[v].col[set] == NIL) {
+ colorArray[v] = Vector4(0, 0, 0, 1);
+ }
+ else {
+ anyColor = true;
+ colorArray[v] = d->colArray[set][d->vertexArray[v].col[set]];
+ }
+ }
+
+ return anyColor;
+}
+
+void MeshBuilder::buildVertexToPositionMap(Array<int> &map)
+{
+ const uint vertexCount = d->vertexArray.count();
+ map.resize(vertexCount);
+
+ foreach (i, d->vertexArray) {
+ map[i] = d->vertexArray[i].pos;
+ }
+}
+
+
+
+uint MeshBuilder::vertexCount() const
+{
+ return d->vertexArray.count();
+}
+
+
+uint MeshBuilder::positionCount() const
+{
+ return d->posArray.count();
+}
+
+uint MeshBuilder::normalCount() const
+{
+ return d->norArray.count();
+}
+
+uint MeshBuilder::texCoordCount(uint set/*=0*/) const
+{
+ return d->texArray[set].count();
+}
+
+uint MeshBuilder::colorCount(uint set/*=0*/) const
+{
+ return d->colArray[set].count();
+}
+
+
+uint MeshBuilder::materialCount() const
+{
+ return d->materialArray.count();
+}
+
+const char * MeshBuilder::material(uint i) const
+{
+ return d->materialArray[i].name;
+}
+
+
+uint MeshBuilder::positionIndex(uint vertex) const
+{
+ return d->vertexArray[vertex].pos;
+}
+uint MeshBuilder::normalIndex(uint vertex) const
+{
+ return d->vertexArray[vertex].nor;
+}
+uint MeshBuilder::texCoordIndex(uint vertex, uint set/*=0*/) const
+{
+ return d->vertexArray[vertex].tex[set];
+}
+uint MeshBuilder::colorIndex(uint vertex, uint set/*=0*/) const
+{
+ return d->vertexArray[vertex].col[set];
+}
diff --git a/thirdparty/thekla_atlas/nvmesh/MeshBuilder.h b/thirdparty/thekla_atlas/nvmesh/MeshBuilder.h
new file mode 100644
index 0000000000..5b3af3fc1d
--- /dev/null
+++ b/thirdparty/thekla_atlas/nvmesh/MeshBuilder.h
@@ -0,0 +1,119 @@
+// This code is in the public domain -- castanyo@yahoo.es
+
+#pragma once
+#ifndef NV_MESH_MESHBUILDER_H
+#define NV_MESH_MESHBUILDER_H
+
+#include "nvmesh.h"
+#include "nvcore/Array.h"
+#include "nvmath/Vector.h"
+
+namespace nv
+{
+ class String;
+ class TriMesh;
+ class QuadTriMesh;
+ namespace HalfEdge { class Mesh; }
+
+
+ /// Mesh builder is a helper class for importers.
+ /// Ideally it should handle any vertex data, but for now it only accepts positions,
+ /// normals and texcoords.
+ class MeshBuilder
+ {
+ NV_FORBID_COPY(MeshBuilder);
+ NV_FORBID_HEAPALLOC();
+ public:
+ MeshBuilder();
+ ~MeshBuilder();
+
+ // Builder methods.
+ uint addPosition(const Vector3 & v);
+ uint addNormal(const Vector3 & v);
+ uint addTexCoord(const Vector2 & v, uint set = 0);
+ uint addColor(const Vector4 & v, uint set = 0);
+
+ void beginGroup(uint id);
+ void endGroup();
+
+ uint addMaterial(const char * name);
+ void beginMaterial(uint id);
+ void endMaterial();
+
+ void beginPolygon(uint id = 0);
+ uint addVertex(uint p, uint n = NIL, uint t0 = NIL, uint t1 = NIL, uint c0 = NIL, uint c1 = NIL, uint c2 = NIL);
+ uint addVertex(const Vector3 & p);
+ //uint addVertex(const Vector3 & p, const Vector3 & n, const Vector2 & t0 = Vector2(0), const Vector2 & t1 = Vector2(0), const Vector4 & c0 = Vector4(0), const Vector4 & c1 = Vector4(0));
+ bool endPolygon();
+
+ uint weldPositions();
+ uint weldNormals();
+ uint weldTexCoords(uint set = 0);
+ uint weldColors(uint set = 0);
+ void weldVertices();
+
+ void optimize(); // eliminate duplicate components and duplicate vertices.
+ void removeUnusedMaterials(Array<uint> & newMaterialId);
+ void sortFacesByGroup();
+ void sortFacesByMaterial();
+
+ void done();
+ void reset();
+
+ // Hints.
+ void hintTriangleCount(uint count);
+ void hintVertexCount(uint count);
+ void hintPositionCount(uint count);
+ void hintNormalCount(uint count);
+ void hintTexCoordCount(uint count, uint set = 0);
+ void hintColorCount(uint count, uint set = 0);
+
+ // Helpers.
+ void addTriangle(uint v0, uint v1, uint v2);
+ void addQuad(uint v0, uint v1, uint v2, uint v3);
+
+ // Get result.
+ TriMesh * buildTriMesh() const;
+ QuadTriMesh * buildQuadTriMesh() const;
+
+ enum Error {
+ Error_None,
+ Error_NonManifoldEdge,
+ Error_NonManifoldVertex,
+ };
+
+ HalfEdge::Mesh * buildHalfEdgeMesh(bool weldPositions, Error * error = NULL, Array<uint> * badFaces = NULL) const;
+
+ bool buildPositions(Array<Vector3> & positionArray);
+ bool buildNormals(Array<Vector3> & normalArray);
+ bool buildTexCoords(Array<Vector2> & texCoordArray, uint set = 0);
+ bool buildColors(Array<Vector4> & colorArray, uint set = 0);
+ void buildVertexToPositionMap(Array<int> & map);
+
+
+ // Expose attribute indices of the unified vertex array.
+ uint vertexCount() const;
+
+ uint positionCount() const;
+ uint normalCount() const;
+ uint texCoordCount(uint set = 0) const;
+ uint colorCount(uint set = 0) const;
+
+ uint materialCount() const;
+ const char * material(uint i) const;
+
+ uint positionIndex(uint vertex) const;
+ uint normalIndex(uint vertex) const;
+ uint texCoordIndex(uint vertex, uint set = 0) const;
+ uint colorIndex(uint vertex, uint set = 0) const;
+
+ private:
+
+ struct PrivateData;
+ PrivateData * d;
+
+ };
+
+} // nv namespace
+
+#endif // NV_MESH_MESHBUILDER_H
diff --git a/thirdparty/thekla_atlas/nvmesh/MeshTopology.cpp b/thirdparty/thekla_atlas/nvmesh/MeshTopology.cpp
new file mode 100644
index 0000000000..e7e1dce421
--- /dev/null
+++ b/thirdparty/thekla_atlas/nvmesh/MeshTopology.cpp
@@ -0,0 +1,122 @@
+// This code is in the public domain -- castanyo@yahoo.es
+
+#include "nvmesh.h" // pch
+
+#include "nvcore/Array.h"
+#include "nvcore/BitArray.h"
+
+#include "nvmesh/MeshTopology.h"
+#include "nvmesh/halfedge/Mesh.h"
+#include "nvmesh/halfedge/Edge.h"
+#include "nvmesh/halfedge/Face.h"
+
+using namespace nv;
+
+void MeshTopology::buildTopologyInfo(const HalfEdge::Mesh * mesh)
+{
+ const uint vertexCount = mesh->colocalVertexCount();
+ const uint faceCount = mesh->faceCount();
+ const uint edgeCount = mesh->edgeCount();
+
+ nvDebug( "--- Building mesh topology:\n" );
+
+ Array<uint> stack(faceCount);
+
+ BitArray bitFlags(faceCount);
+ bitFlags.clearAll();
+
+ // Compute connectivity.
+ nvDebug( "--- Computing connectivity.\n" );
+
+ m_connectedCount = 0;
+
+ for(uint f = 0; f < faceCount; f++ ) {
+ if( bitFlags.bitAt(f) == false ) {
+ m_connectedCount++;
+
+ stack.pushBack( f );
+ while( !stack.isEmpty() ) {
+
+ const uint top = stack.back();
+ nvCheck(top != NIL);
+ stack.popBack();
+
+ if( bitFlags.bitAt(top) == false ) {
+ bitFlags.setBitAt(top);
+
+ const HalfEdge::Face * face = mesh->faceAt(top);
+ const HalfEdge::Edge * firstEdge = face->edge;
+ const HalfEdge::Edge * edge = firstEdge;
+
+ do {
+ const HalfEdge::Face * neighborFace = edge->pair->face;
+ if (neighborFace != NULL) {
+ stack.pushBack(neighborFace->id);
+ }
+ edge = edge->next;
+ } while(edge != firstEdge);
+ }
+ }
+ }
+ }
+ nvCheck(stack.isEmpty());
+ nvDebug( "--- %d connected components.\n", m_connectedCount );
+
+
+ // Count boundary loops.
+ nvDebug( "--- 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 (uint e = 0; e < edgeCount; e++)
+ {
+ const HalfEdge::Edge * startEdge = mesh->edgeAt(e);
+ if (startEdge != NULL && startEdge->isBoundary() && bitFlags.bitAt(e) == false)
+ {
+ nvDebugCheck(startEdge->face != NULL);
+ nvDebugCheck(startEdge->pair->face == NULL);
+
+ startEdge = startEdge->pair;
+
+ m_boundaryCount++;
+
+ const HalfEdge::Edge * edge = startEdge;
+ do {
+ bitFlags.setBitAt(edge->id / 2);
+ edge = edge->next;
+ } while(startEdge != edge);
+ }
+ }
+ nvDebug("--- %d boundary loops found.\n", m_boundaryCount );
+
+
+ // Compute euler number.
+ m_eulerNumber = vertexCount - edgeCount + faceCount;
+ nvDebug("--- 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;
+ nvDebug("--- Genus: %d.\n", m_genus);
+ }
+}
+
+
+/*static*/ bool MeshTopology::isQuadOnly(const HalfEdge::Mesh * mesh)
+{
+ const uint faceCount = mesh->faceCount();
+ for(uint f = 0; f < faceCount; f++)
+ {
+ const HalfEdge::Face * face = mesh->faceAt(f);
+ if (face->edgeCount() != 4) {
+ return false;
+ }
+ }
+
+ return true;
+}
diff --git a/thirdparty/thekla_atlas/nvmesh/MeshTopology.h b/thirdparty/thekla_atlas/nvmesh/MeshTopology.h
new file mode 100644
index 0000000000..c3d7477b15
--- /dev/null
+++ b/thirdparty/thekla_atlas/nvmesh/MeshTopology.h
@@ -0,0 +1,66 @@
+// This code is in the public domain -- castanyo@yahoo.es
+
+#pragma once
+#ifndef NV_MESH_MESHTOPOLOGY_H
+#define NV_MESH_MESHTOPOLOGY_H
+
+#include <nvmesh/nvmesh.h>
+
+namespace nv
+{
+ namespace HalfEdge { class Mesh; }
+ class MeshAdjacency;
+
+ /// Mesh topology information.
+ class MeshTopology
+ {
+ public:
+ MeshTopology(const HalfEdge::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*/; }
+
+ /// Return the number of connected components.
+ int connectedCount() const { return m_connectedCount; }
+
+ /// Return the number of open holes.
+ int holeCount() const { return m_boundaryCount; }
+
+ /// Return the genus of the mesh.
+ int genus() const { return m_genus; }
+
+ /// Return the euler number of the mesh.
+ int euler() const { return m_eulerNumber; }
+
+
+ static bool isQuadOnly(const HalfEdge::Mesh * mesh);
+
+
+ private:
+
+ NVMESH_API void buildTopologyInfo(const HalfEdge::Mesh * mesh);
+
+ 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;
+ };
+
+} // nv namespace
+
+#endif // NV_MESH_MESHTOPOLOGY_H
diff --git a/thirdparty/thekla_atlas/nvmesh/QuadTriMesh.cpp b/thirdparty/thekla_atlas/nvmesh/QuadTriMesh.cpp
new file mode 100644
index 0000000000..64a071abe9
--- /dev/null
+++ b/thirdparty/thekla_atlas/nvmesh/QuadTriMesh.cpp
@@ -0,0 +1,36 @@
+// This code is in the public domain -- Ignacio Castaņo <castano@gmail.com>
+
+#include "QuadTriMesh.h"
+#include "Stream.h"
+
+using namespace nv;
+
+
+bool QuadTriMesh::isQuadFace(uint i) const
+{
+ return m_faceArray[i].isQuadFace();
+}
+
+const QuadTriMesh::Vertex & QuadTriMesh::faceVertex(uint f, uint v) const
+{
+ if (isQuadFace(f)) nvDebugCheck(v < 4);
+ else nvDebugCheck(v < 3);
+
+ const Face & face = this->faceAt(f);
+ return this->vertexAt(face.v[v]);
+}
+
+
+namespace nv
+{
+ static Stream & operator<< (Stream & s, QuadTriMesh::Face & face)
+ {
+ return s << face.id << face.v[0] << face.v[1] << face.v[2] << face.v[3];
+ }
+
+ Stream & operator<< (Stream & s, QuadTriMesh & mesh)
+ {
+ return s << mesh.m_faceArray << (BaseMesh &) mesh;
+ }
+}
+
diff --git a/thirdparty/thekla_atlas/nvmesh/QuadTriMesh.h b/thirdparty/thekla_atlas/nvmesh/QuadTriMesh.h
new file mode 100644
index 0000000000..b8465f2db0
--- /dev/null
+++ b/thirdparty/thekla_atlas/nvmesh/QuadTriMesh.h
@@ -0,0 +1,60 @@
+// This code is in the public domain -- Ignacio Castaņo <castano@gmail.com>
+
+#pragma once
+#ifndef NV_MESH_QUADTRIMESH_H
+#define NV_MESH_QUADTRIMESH_H
+
+#include "nvcore/Array.h"
+#include "nvmath/Vector.h"
+#include "nvmesh/nvmesh.h"
+#include "nvmesh/BaseMesh.h"
+
+namespace nv
+{
+ class Stream;
+
+ /// Mixed quad/triangle mesh.
+ class QuadTriMesh : public BaseMesh
+ {
+ public:
+ struct Face;
+ typedef BaseMesh::Vertex Vertex;
+
+ QuadTriMesh() {};
+ QuadTriMesh(uint faceCount, uint vertexCount) : BaseMesh(vertexCount), m_faceArray(faceCount) {}
+
+ // Face methods.
+ uint faceCount() const { return m_faceArray.count(); }
+
+ const Face & faceAt(uint i) const { return m_faceArray[i]; }
+ Face & faceAt(uint i) { return m_faceArray[i]; }
+
+ const Array<Face> & faces() const { return m_faceArray; }
+ Array<Face> & faces() { return m_faceArray; }
+
+ bool isQuadFace(uint i) const;
+
+ const Vertex & faceVertex(uint f, uint v) const;
+
+ friend Stream & operator<< (Stream & s, QuadTriMesh & obj);
+
+ private:
+
+ Array<Face> m_faceArray;
+
+ };
+
+
+ /// QuadTriMesh face.
+ struct QuadTriMesh::Face
+ {
+ uint id;
+ uint v[4];
+
+ bool isQuadFace() const { return v[3] != NIL; }
+ };
+
+} // nv namespace
+
+
+#endif // NV_MESH_QUADTRIMESH_H
diff --git a/thirdparty/thekla_atlas/nvmesh/TriMesh.cpp b/thirdparty/thekla_atlas/nvmesh/TriMesh.cpp
new file mode 100644
index 0000000000..bf10a474fb
--- /dev/null
+++ b/thirdparty/thekla_atlas/nvmesh/TriMesh.cpp
@@ -0,0 +1,25 @@
+// This code is in the public domain -- Ignacio Castaņo <castano@gmail.com>
+
+#include "TriMesh.h"
+
+using namespace nv;
+
+
+/// Triangle mesh.
+Vector3 TriMesh::faceNormal(uint f) const
+{
+ const Face & face = this->faceAt(f);
+ const Vector3 & p0 = this->vertexAt(face.v[0]).pos;
+ const Vector3 & p1 = this->vertexAt(face.v[1]).pos;
+ const Vector3 & p2 = this->vertexAt(face.v[2]).pos;
+ return normalizeSafe(cross(p1 - p0, p2 - p0), Vector3(0.0f), 0.0f);
+}
+
+/// Get face vertex.
+const TriMesh::Vertex & TriMesh::faceVertex(uint f, uint v) const
+{
+ nvDebugCheck(v < 3);
+ const Face & face = this->faceAt(f);
+ return this->vertexAt(face.v[v]);
+}
+
diff --git a/thirdparty/thekla_atlas/nvmesh/TriMesh.h b/thirdparty/thekla_atlas/nvmesh/TriMesh.h
new file mode 100644
index 0000000000..bc5672c1ac
--- /dev/null
+++ b/thirdparty/thekla_atlas/nvmesh/TriMesh.h
@@ -0,0 +1,51 @@
+// This code is in the public domain -- Ignacio Castaņo <castano@gmail.com>
+
+#pragma once
+#ifndef NV_MESH_TRIMESH_H
+#define NV_MESH_TRIMESH_H
+
+#include "nvcore/Array.h"
+#include "nvmath/Vector.inl"
+#include "nvmesh/nvmesh.h"
+#include "nvmesh/BaseMesh.h"
+
+namespace nv
+{
+ /// Triangle mesh.
+ class TriMesh : public BaseMesh
+ {
+ public:
+ struct Face;
+ typedef BaseMesh::Vertex Vertex;
+
+ TriMesh(uint faceCount, uint vertexCount) : BaseMesh(vertexCount), m_faceArray(faceCount) {}
+
+ // Face methods.
+ uint faceCount() const { return m_faceArray.count(); }
+ const Face & faceAt(uint i) const { return m_faceArray[i]; }
+ Face & faceAt(uint i) { return m_faceArray[i]; }
+ const Array<Face> & faces() const { return m_faceArray; }
+ Array<Face> & faces() { return m_faceArray; }
+
+ NVMESH_API Vector3 faceNormal(uint f) const;
+ NVMESH_API const Vertex & faceVertex(uint f, uint v) const;
+
+ friend Stream & operator<< (Stream & s, BaseMesh & obj);
+
+ private:
+
+ Array<Face> m_faceArray;
+
+ };
+
+
+ /// TriMesh face.
+ struct TriMesh::Face
+ {
+ uint id;
+ uint v[3];
+ };
+
+} // nv namespace
+
+#endif // NV_MESH_TRIMESH_H
diff --git a/thirdparty/thekla_atlas/nvmesh/geometry/Bounds.cpp b/thirdparty/thekla_atlas/nvmesh/geometry/Bounds.cpp
new file mode 100644
index 0000000000..69fd1deb24
--- /dev/null
+++ b/thirdparty/thekla_atlas/nvmesh/geometry/Bounds.cpp
@@ -0,0 +1,54 @@
+// This code is in the public domain -- Ignacio CastaÃąo <castano@gmail.com>
+
+#include "nvmesh.h" // pch
+
+#include "Bounds.h"
+
+#include "nvmesh/BaseMesh.h"
+#include "nvmesh/halfedge/Mesh.h"
+#include "nvmesh/halfedge/Vertex.h"
+
+#include "nvmath/Box.inl"
+
+using namespace nv;
+
+Box MeshBounds::box(const BaseMesh * mesh)
+{
+ nvCheck(mesh != NULL);
+
+ Box bounds;
+ bounds.clearBounds();
+
+ const uint vertexCount = mesh->vertexCount();
+ for (uint v = 0; v < vertexCount; v++)
+ {
+ const BaseMesh::Vertex & vertex = mesh->vertexAt(v);
+ bounds.addPointToBounds( vertex.pos );
+ }
+
+ return bounds;
+}
+
+Box MeshBounds::box(const HalfEdge::Mesh * mesh)
+{
+ nvCheck(mesh != NULL);
+
+ Box bounds;
+ bounds.clearBounds();
+
+ const uint vertexCount = mesh->vertexCount();
+ for (uint v = 0; v < vertexCount; v++)
+ {
+ const HalfEdge::Vertex * vertex = mesh->vertexAt(v);
+ nvDebugCheck(vertex != NULL);
+ bounds.addPointToBounds( vertex->pos );
+ }
+
+ return bounds;
+}
+
+/*Sphere MeshBounds::sphere(const HalfEdge::Mesh * mesh)
+{
+ // @@ TODO
+ return Sphere();
+}*/
diff --git a/thirdparty/thekla_atlas/nvmesh/geometry/Bounds.h b/thirdparty/thekla_atlas/nvmesh/geometry/Bounds.h
new file mode 100644
index 0000000000..1cb5b7b905
--- /dev/null
+++ b/thirdparty/thekla_atlas/nvmesh/geometry/Bounds.h
@@ -0,0 +1,28 @@
+// This code is in the public domain -- Ignacio CastaÃąo <castano@gmail.com>
+
+#pragma once
+#ifndef NV_MESH_MESHBOUNDS_H
+#define NV_MESH_MESHBOUNDS_H
+
+#include <nvmath/Sphere.h>
+#include <nvmath/Box.h>
+
+#include <nvmesh/nvmesh.h>
+
+namespace nv
+{
+ class BaseMesh;
+ namespace HalfEdge { class Mesh; }
+
+ // Bounding volumes computation.
+ namespace MeshBounds
+ {
+ Box box(const BaseMesh * mesh);
+ Box box(const HalfEdge::Mesh * mesh);
+
+ Sphere sphere(const HalfEdge::Mesh * mesh);
+ }
+
+} // nv namespace
+
+#endif // NV_MESH_MESHBOUNDS_H
diff --git a/thirdparty/thekla_atlas/nvmesh/geometry/Measurements.cpp b/thirdparty/thekla_atlas/nvmesh/geometry/Measurements.cpp
new file mode 100644
index 0000000000..e0c271663b
--- /dev/null
+++ b/thirdparty/thekla_atlas/nvmesh/geometry/Measurements.cpp
@@ -0,0 +1,36 @@
+// This code is in the public domain -- castano@gmail.com
+
+#include "nvmesh.h" // pch
+
+#include "Measurements.h"
+#include "nvmesh/halfedge/Mesh.h"
+#include "nvmesh/halfedge/Face.h"
+
+using namespace nv;
+
+float nv::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();
+ }
+ nvDebugCheck(area >= 0);
+
+ return area;
+}
+
+float nv::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;
+}
diff --git a/thirdparty/thekla_atlas/nvmesh/geometry/Measurements.h b/thirdparty/thekla_atlas/nvmesh/geometry/Measurements.h
new file mode 100644
index 0000000000..0be863b79e
--- /dev/null
+++ b/thirdparty/thekla_atlas/nvmesh/geometry/Measurements.h
@@ -0,0 +1,18 @@
+// This code is in the public domain -- castano@gmail.com
+
+#pragma once
+#ifndef NV_MESH_MESHMEASUREMENTS_H
+#define NV_MESH_MESHMEASUREMENTS_H
+
+#include "nvmesh/nvmesh.h"
+
+namespace nv
+{
+ namespace HalfEdge { class Mesh; }
+
+ float computeSurfaceArea(const HalfEdge::Mesh * mesh);
+ float computeParametricArea(const HalfEdge::Mesh * mesh);
+
+} // nv namespace
+
+#endif // NV_MESH_MESHMEASUREMENTS_H
diff --git a/thirdparty/thekla_atlas/nvmesh/halfedge/Edge.cpp b/thirdparty/thekla_atlas/nvmesh/halfedge/Edge.cpp
new file mode 100644
index 0000000000..671650296c
--- /dev/null
+++ b/thirdparty/thekla_atlas/nvmesh/halfedge/Edge.cpp
@@ -0,0 +1,57 @@
+// This code is in the public domain -- castanyo@yahoo.es
+
+#include "nvmesh.h" // pch
+
+#include "Edge.h"
+#include "Vertex.h"
+
+#include "nvmath/Vector.inl"
+
+using namespace nv;
+using namespace HalfEdge;
+
+Vector3 Edge::midPoint() const
+{
+ return (to()->pos + from()->pos) * 0.5f;
+}
+
+float Edge::length() const
+{
+ return ::length(to()->pos - from()->pos);
+}
+
+// Return angle between this edge and the previous one.
+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) / (nv::length(v0) * nv::length(v1)));
+}
+
+bool Edge::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;
+}
+
+/*
+Edge * Edge::nextBoundary() {
+ nvDebugCheck(this->m_pair == NULL);
+
+}
+
+Edge * Edge::prevBoundary() {
+ nvDebugCheck(this->m_pair == NULL);
+
+}
+*/
+
+
diff --git a/thirdparty/thekla_atlas/nvmesh/halfedge/Edge.h b/thirdparty/thekla_atlas/nvmesh/halfedge/Edge.h
new file mode 100644
index 0000000000..25c47f4860
--- /dev/null
+++ b/thirdparty/thekla_atlas/nvmesh/halfedge/Edge.h
@@ -0,0 +1,70 @@
+// This code is in the public domain -- castanyo@yahoo.es
+
+#pragma once
+#ifndef NV_MESH_HALFEDGE_EDGE_H
+#define NV_MESH_HALFEDGE_EDGE_H
+
+#include "nvmath/Vector.h"
+
+namespace nv
+{
+ namespace HalfEdge { class Vertex; class Face; class Edge; }
+
+ /// Half edge edge.
+ class HalfEdge::Edge
+ {
+ NV_FORBID_COPY(Edge);
+ public:
+
+ uint 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(uint 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; }
+
+ // @@ Add these helpers:
+ //Edge * nextBoundary();
+ //Edge * prevBoundary();
+
+
+ // @@ 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 isValid() const;
+
+ // Geometric queries.
+ Vector3 midPoint() const;
+ float length() const;
+ float angle() const;
+
+ };
+
+} // nv namespace
+
+
+#endif // NV_MESH_HALFEDGE_EDGE_H
diff --git a/thirdparty/thekla_atlas/nvmesh/halfedge/Face.cpp b/thirdparty/thekla_atlas/nvmesh/halfedge/Face.cpp
new file mode 100644
index 0000000000..9f6987154e
--- /dev/null
+++ b/thirdparty/thekla_atlas/nvmesh/halfedge/Face.cpp
@@ -0,0 +1,268 @@
+// This code is in the public domain -- castanyo@yahoo.es
+
+#include "nvmesh.h" // pch
+
+#include "Face.h"
+#include "Vertex.h"
+
+#include "nvmath/Fitting.h"
+#include "nvmath/Plane.h"
+#include "nvmath/Vector.inl"
+
+#include "nvcore/Array.h"
+
+
+using namespace nv;
+using namespace HalfEdge;
+
+/// Get face area.
+float Face::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 Face::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;
+}
+
+
+/// Get boundary length.
+float Face::boundaryLength() const
+{
+ float bl = 0;
+
+ for (ConstEdgeIterator it(edges()); !it.isDone(); it.advance())
+ {
+ const Edge * edge = it.current();
+ bl += edge->length();
+ }
+
+ return bl;
+}
+
+
+/// Get face normal.
+Vector3 Face::normal() const
+{
+ Vector3 n(0);
+
+ const Vertex * vertex0 = NULL;
+
+ for (ConstEdgeIterator it(edges()); !it.isDone(); it.advance())
+ {
+ const Edge * edge = it.current();
+ nvCheck(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();
+
+ 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);
+
+
+ // Get face points eliminating duplicates.
+ /*Array<Vector3> points(4);
+
+ points.append(m_edge->prev()->from()->pos);
+
+ for (ConstEdgeIterator it(edges()); !it.isDone(); it.advance())
+ {
+ const Edge * edge = it.current();
+ nvDebugCheck(edge != NULL);
+
+ const Vector3 & p = edge->from()->pos;
+ if (points.back() != p)
+ {
+ points.append(edge->from()->pos);
+ }
+ }
+
+ points.popBack();
+
+ if (points.count() < 3)
+ {
+ // Invalid normal.
+ return Vector3(0.0f);
+ }
+ else
+ {
+ // Compute regular normal.
+ Vector3 normal = normalizeSafe(cross(points[1] - points[0], points[2] - points[0]), Vector3(0.0f), 0.0f);
+
+#pragma NV_MESSAGE("TODO: make sure these three points are not colinear")
+
+ if (points.count() > 3)
+ {
+ // Compute best fitting plane to the points.
+ Plane plane = Fit::bestPlane(points.count(), points.buffer());
+
+ // Adjust normal orientation.
+ if (dot(normal, plane.vector()) > 0) {
+ normal = plane.vector();
+ }
+ else {
+ normal = -plane.vector();
+ }
+ }
+
+ nvDebugCheck(isNormalized(normal));
+ return normal;
+ }*/
+}
+
+Vector3 Face::centroid() const
+{
+ Vector3 sum(0.0f);
+ uint count = 0;
+
+ for (ConstEdgeIterator it(edges()); !it.isDone(); it.advance())
+ {
+ const Edge * edge = it.current();
+ sum += edge->from()->pos;
+ count++;
+ }
+
+ return sum / float(count);
+}
+
+
+bool Face::isValid() const
+{
+ uint count = 0;
+
+ for (ConstEdgeIterator it(edges()); !it.isDone(); it.advance())
+ {
+ const Edge * edge = it.current();
+ if (edge->face != this) return false;
+ if (!edge->isValid()) return false;
+ if (!edge->pair->isValid()) return false;
+ count++;
+ }
+
+ if (count < 3) return false;
+
+ return true;
+}
+
+
+// Determine if this face contains the given edge.
+bool Face::contains(const Edge * e) const
+{
+ for (ConstEdgeIterator it(edges()); !it.isDone(); it.advance())
+ {
+ if(it.current() == e) return true;
+ }
+ return false;
+}
+
+// Returns index in this face of the given edge.
+uint Face::edgeIndex(const Edge * e) const
+{
+ int i = 0;
+ for (ConstEdgeIterator it(edges()); !it.isDone(); it.advance(), i++)
+ {
+ if(it.current() == e) return i;
+ }
+ return NIL;
+}
+
+
+Edge * Face::edgeAt(uint idx)
+{
+ int i = 0;
+ for(EdgeIterator it(edges()); !it.isDone(); it.advance(), i++) {
+ if (i == idx) return it.current();
+ }
+ return NULL;
+}
+const Edge * Face::edgeAt(uint idx) const
+{
+ int i = 0;
+ for(ConstEdgeIterator it(edges()); !it.isDone(); it.advance(), i++) {
+ if (i == idx) return it.current();
+ }
+ return NULL;
+}
+
+
+// Count the number of edges in this face.
+uint Face::edgeCount() const
+{
+ uint count = 0;
+ for (ConstEdgeIterator it(edges()); !it.isDone(); it.advance()) { ++count; }
+ return count;
+}
+
+// Determine if this is a boundary face.
+bool Face::isBoundary() const
+{
+ for (ConstEdgeIterator it(edges()); !it.isDone(); it.advance())
+ {
+ const Edge * edge = it.current();
+ nvDebugCheck(edge->pair != NULL);
+
+ if (edge->pair->face == NULL) {
+ return true;
+ }
+ }
+ return false;
+}
+
+// Count the number of boundary edges in the face.
+uint Face::boundaryCount() const
+{
+ uint count = 0;
+ for (ConstEdgeIterator it(edges()); !it.isDone(); it.advance())
+ {
+ const Edge * edge = it.current();
+ nvDebugCheck(edge->pair != NULL);
+
+ if (edge->pair->face == NULL) {
+ count++;
+ }
+ }
+ return count;
+}
diff --git a/thirdparty/thekla_atlas/nvmesh/halfedge/Face.h b/thirdparty/thekla_atlas/nvmesh/halfedge/Face.h
new file mode 100644
index 0000000000..677f8666f0
--- /dev/null
+++ b/thirdparty/thekla_atlas/nvmesh/halfedge/Face.h
@@ -0,0 +1,106 @@
+// This code is in the public domain -- castanyo@yahoo.es
+
+#pragma once
+#ifndef NV_MESH_HALFEDGE_FACE_H
+#define NV_MESH_HALFEDGE_FACE_H
+
+#include <nvmesh/halfedge/Edge.h>
+
+namespace nv
+{
+ namespace HalfEdge { class Vertex; class Face; class Edge; }
+
+ /// Face of a half-edge mesh.
+ class HalfEdge::Face
+ {
+ NV_FORBID_COPY(Face);
+ public:
+
+ uint id;
+ uint16 group;
+ uint16 material;
+ Edge * edge;
+
+
+ Face(uint id) : id(id), group(~0), material(~0), edge(NULL) {}
+
+ float area() const;
+ float parametricArea() const;
+ float boundaryLength() const;
+ Vector3 normal() const;
+ Vector3 centroid() const;
+
+ bool isValid() const;
+
+ bool contains(const Edge * e) const;
+ uint edgeIndex(const Edge * e) const;
+
+ Edge * edgeAt(uint idx);
+ const Edge * edgeAt(uint idx) const;
+
+ uint edgeCount() const;
+ bool isBoundary() const;
+ uint boundaryCount() const;
+
+
+ // 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)
+ {
+ nvDebugCheck(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
+ {
+ nvDebugCheck(contains(e));
+ return ConstEdgeIterator(e);
+ }
+ };
+
+} // nv namespace
+
+#endif // NV_MESH_HALFEDGE_FACE_H
diff --git a/thirdparty/thekla_atlas/nvmesh/halfedge/Mesh.cpp b/thirdparty/thekla_atlas/nvmesh/halfedge/Mesh.cpp
new file mode 100644
index 0000000000..0012513bce
--- /dev/null
+++ b/thirdparty/thekla_atlas/nvmesh/halfedge/Mesh.cpp
@@ -0,0 +1,1284 @@
+// This code is in the public domain -- castanyo@yahoo.es
+
+#include "nvmesh.h" // pch
+
+#include "Mesh.h"
+#include "Edge.h"
+#include "Vertex.h"
+#include "Face.h"
+
+#include "nvmesh/TriMesh.h"
+#include "nvmesh/QuadTriMesh.h"
+#include "nvmesh/MeshBuilder.h"
+
+#include "nvmath/Vector.inl"
+#include "nvcore/Array.inl"
+#include "nvcore/HashMap.inl"
+
+
+using namespace nv;
+using namespace HalfEdge;
+
+Mesh::Mesh() : m_colocalVertexCount(0)
+{
+ errorCount = 0;
+}
+
+Mesh::Mesh(const Mesh * mesh)
+{
+ errorCount = 0;
+
+ // Copy mesh vertices.
+ const uint vertexCount = mesh->vertexCount();
+ m_vertexArray.resize(vertexCount);
+
+ for (uint v = 0; v < vertexCount; v++)
+ {
+ const Vertex * vertex = mesh->vertexAt(v);
+ nvDebugCheck(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 uint faceCount = mesh->faceCount();
+
+ Array<uint> indexArray(3);
+
+ for (uint 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.append(vertex->id);
+ }
+
+ addFace(indexArray);
+ indexArray.clear();
+ }
+}
+
+Mesh::~Mesh()
+{
+ clear();
+}
+
+
+void Mesh::clear()
+{
+ deleteAll(m_vertexArray);
+ m_vertexArray.clear();
+
+ foreach(i, m_edgeMap)
+ {
+ delete m_edgeMap[i].value;
+ }
+ //deleteAll(m_edgeArray); // edgeArray only contains 1/2 of the edges!
+ m_edgeArray.clear();
+ m_edgeMap.clear();
+
+ deleteAll(m_faceArray);
+ m_faceArray.clear();
+}
+
+
+Vertex * Mesh::addVertex(const Vector3 & pos)
+{
+ nvDebugCheck(isFinite(pos));
+
+ Vertex * v = new Vertex(m_vertexArray.count());
+ v->pos = pos;
+ m_vertexArray.append(v);
+
+ return v;
+
+// return addVertex(m_vertexArray.count(), pos);
+}
+
+/*Vertex * Mesh::addVertex(uint id, const Vector3 & pos)
+{
+ nvDebugCheck(isFinite(pos));
+
+ Vertex * v = new Vertex(id);
+ v->pos = pos;
+ m_vertexArray.append(v);
+
+ return v;
+}*/
+
+/*void Mesh::addVertices(const Mesh * mesh)
+{
+nvCheck(mesh != NULL);
+
+// Add mesh vertices
+for (uint v = 0; v < vertexCount; v++)
+{
+const Vertex * vertex = mesh->vertexAt(v);
+nvDebugCheck(vertex != NULL);
+
+Vertex * v = addVertex(vertex->pos());
+nvDebugCheck(v != NULL);
+
+v->setNor(vertex->nor());
+v->setTex(vertex->tex());
+}
+}*/
+
+
+/// Link colocal vertices based on geometric location only.
+void Mesh::linkColocals()
+{
+ nvDebug("--- Linking colocals:\n");
+
+ const uint vertexCount = this->vertexCount();
+ HashMap<Vector3, Vertex *> vertexMap(vertexCount);
+
+ for (uint v = 0; v < vertexCount; v++)
+ {
+ Vertex * vertex = vertexAt(v);
+
+ Vertex * colocal;
+ if (vertexMap.get(vertex->pos, &colocal))
+ {
+ colocal->linkColocal(vertex);
+ }
+ else
+ {
+ vertexMap.add(vertex->pos, vertex);
+ }
+ }
+
+ m_colocalVertexCount = vertexMap.count();
+
+ nvDebug("--- %d vertex positions.\n", m_colocalVertexCount);
+
+ // @@ Remove duplicated vertices? or just leave them as colocals?
+}
+
+void Mesh::linkColocalsWithCanonicalMap(const Array<uint> & canonicalMap)
+{
+ nvDebug("--- Linking colocals:\n");
+
+ uint vertexMapSize = 0;
+ foreach(i, canonicalMap) {
+ vertexMapSize = max(vertexMapSize, canonicalMap[i] + 1);
+ }
+
+ Array<Vertex *> vertexMap;
+ vertexMap.resize(vertexMapSize, NULL);
+
+ m_colocalVertexCount = 0;
+
+ const uint vertexCount = this->vertexCount();
+ for (uint v = 0; v < vertexCount; v++)
+ {
+ Vertex * vertex = vertexAt(v);
+
+ Vertex * colocal = vertexMap[canonicalMap[v]];
+ if (colocal != NULL)
+ {
+ nvDebugCheck(vertex->pos == colocal->pos);
+ colocal->linkColocal(vertex);
+ }
+ else
+ {
+ vertexMap[canonicalMap[v]] = vertex;
+ m_colocalVertexCount++;
+ }
+ }
+
+ nvDebug("--- %d vertex positions.\n", m_colocalVertexCount);
+}
+
+
+Face * Mesh::addFace()
+{
+ Face * f = new Face(m_faceArray.count());
+ m_faceArray.append(f);
+ return f;
+}
+
+Face * Mesh::addFace(uint v0, uint v1, uint v2)
+{
+ Array<uint> indexArray(3);
+ indexArray << v0 << v1 << v2;
+ return addFace(indexArray, 0, 3);
+}
+
+Face * Mesh::addFace(uint v0, uint v1, uint v2, uint v3)
+{
+ Array<uint> indexArray(4);
+ indexArray << v0 << v1 << v2 << v3;
+ return addFace(indexArray, 0, 4);
+}
+
+Face * Mesh::addFace(const Array<uint> & indexArray)
+{
+ return addFace(indexArray, 0, indexArray.count());
+}
+
+
+Face * Mesh::addFace(const Array<uint> & indexArray, uint first, uint num)
+{
+ nvDebugCheck(first < indexArray.count());
+ nvDebugCheck(num <= indexArray.count()-first);
+ nvDebugCheck(num > 2);
+
+ if (!canAddFace(indexArray, first, num)) {
+ errorCount++;
+ return NULL;
+ }
+
+ Face * f = new Face(m_faceArray.count());
+
+ Edge * firstEdge = NULL;
+ Edge * last = NULL;
+ Edge * current = NULL;
+
+ for(uint i = 0; i < num-1; i++)
+ {
+ current = addEdge(indexArray[first+i], indexArray[first+i+1]);
+ nvCheck(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]);
+ nvCheck(current != NULL && current->face == NULL);
+
+ current->face = f;
+
+ last->setNext(current);
+ current->setNext(firstEdge);
+
+ f->edge = firstEdge;
+ m_faceArray.append(f);
+
+ return f;
+}
+
+/*void Mesh::addFaces(const Mesh * mesh)
+{
+nvCheck(mesh != NULL);
+
+Array indexArray;
+// Add faces
+
+}*/
+
+
+// Return true if the face can be added to the manifold mesh.
+bool Mesh::canAddFace(const Array<uint> & indexArray, uint first, uint num) const
+{
+ for (uint 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 (uint i = 0; i < num; i++) {
+
+ int i0 = indexArray[first + i + 0];
+ int i1 = indexArray[first + (i + 1)%num];
+
+ for (uint j = i + 1; j < num; j++) {
+ int j0 = indexArray[first + j + 0];
+ int j1 = indexArray[first + (j + 1)%num];
+
+ if (i0 == j0 && i1 == j1) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+// Return true if the edge doesn't exist or doesn't have any adjacent face.
+bool Mesh::canAddEdge(uint i, uint j) const
+{
+ if (i == j) {
+ // Skip degenerate edges.
+ 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.
+ return false;
+ }
+ }
+
+ // Make sure edge has not been added yet.
+ Edge * edge = findEdge(i, j);
+
+ return edge == NULL || edge->face == NULL; // We ignore edges that don't have an adjacent face yet, since this face could become the edge's face.
+}
+
+Edge * Mesh::addEdge(uint i, uint j)
+{
+ nvCheck(i != j);
+
+ Edge * edge = findEdge(i, j);
+
+ if (edge != NULL) {
+ // Edge may already exist, but its face must not be set.
+ nvDebugCheck(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.count());
+
+ // Add only unpaired edges.
+ m_edgeArray.append(edge);
+ }
+
+ edge->vertex = m_vertexArray[i];
+ m_edgeMap.add(Key(i,j), edge);
+ }
+
+ // Face and Next are set by addFace.
+
+ return edge;
+}
+
+
+/// Find edge, test all colocals.
+Edge * Mesh::findEdge(uint i, uint 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) {
+ m_edgeMap.get(key, &edge);
+#if !defined(_DEBUG)
+ if (edge != NULL) return edge;
+#endif
+ }
+ else {
+ // Make sure that only one edge is found.
+ nvDebugCheck(!m_edgeMap.get(key));
+ }
+ }
+ }
+
+ return edge;
+}
+
+/// Link boundary edges once the mesh has been created.
+void Mesh::linkBoundary()
+{
+ nvDebug("--- Linking boundaries:\n");
+
+ int num = 0;
+
+ // Create boundary edges.
+ uint edgeCount = this->edgeCount();
+ for(uint e = 0; e < edgeCount; e++)
+ {
+ Edge * edge = edgeAt(e);
+ if (edge != NULL && edge->pair == NULL) {
+ Edge * pair = new Edge(edge->id + 1);
+
+ uint i = edge->from()->id;
+ uint j = edge->next->from()->id;
+
+ Key key(j,i);
+ nvCheck(!m_edgeMap.get(key));
+
+ pair->vertex = m_vertexArray[j];
+ m_edgeMap.add(key, pair);
+
+ edge->pair = pair;
+ pair->pair = edge;
+
+ num++;
+ }
+ }
+
+ // Link boundary edges.
+ for (uint e = 0; e < edgeCount; e++) {
+ Edge * edge = edgeAt(e);
+ if (edge != NULL && edge->pair->face == NULL) {
+ linkBoundaryEdge(edge->pair);
+ }
+ }
+
+ nvDebug("--- %d boundary edges.\n", num);
+}
+
+/// Link this boundary edge.
+void Mesh::linkBoundaryEdge(Edge * edge)
+{
+ nvCheck(edge->face == NULL);
+
+ // Make sure next pointer has not been set. @@ We want to be able to relink boundary edges after mesh changes.
+ //nvCheck(edge->next() == NULL);
+
+ 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.
+ //nvCheck( edge->vertex()->edge() == NULL || edge->vertex()->edge()->face() != NULL );
+ edge->vertex->edge = edge;
+ }
+}
+
+
+/// Convert to tri mesh.
+TriMesh * Mesh::toTriMesh() const
+{
+ uint triangleCount = 0;
+
+ // Count triangle faces.
+ const uint faceCount = this->faceCount();
+ for(uint f = 0; f < faceCount; f++)
+ {
+ const Face * face = faceAt(f);
+ triangleCount += face->edgeCount() - 2;
+ }
+
+ TriMesh * triMesh = new TriMesh(triangleCount, vertexCount());
+
+ // Add vertices.
+ Array<TriMesh::Vertex> & vertices = triMesh->vertices();
+
+ const uint vertexCount = this->vertexCount();
+ for(uint v = 0; v < vertexCount; v++)
+ {
+ const Vertex * vertex = vertexAt(v);
+
+ TriMesh::Vertex triVertex;
+ triVertex.id = vertices.count();
+ triVertex.pos = vertex->pos;
+ triVertex.nor = vertex->nor;
+ triVertex.tex = vertex->tex;
+
+ vertices.append(triVertex);
+ }
+
+ // Add triangles.
+ Array<TriMesh::Face> & triangles = triMesh->faces();
+
+ for(uint f = 0; f < faceCount; f++)
+ {
+ const Face * face = faceAt(f);
+
+ // @@ Triangulate arbitrary polygons correctly.
+ const uint v0 = face->edge->vertex->id;
+ uint v1 = face->edge->next->vertex->id;
+
+ for(Face::ConstEdgeIterator it(face->edges()); !it.isDone(); it.advance())
+ {
+ uint v2 = it.current()->vertex->id;
+
+ // Skip the first two vertices.
+ if (v2 == v0 || v2 == v1) continue;
+
+ TriMesh::Face triangle;
+ triangle.id = triangles.count();
+ triangle.v[0] = v0;
+ triangle.v[1] = v1;
+ triangle.v[2] = v2;
+
+ v1 = v2;
+
+ triangles.append(triangle);
+ }
+ }
+
+ return triMesh;
+}
+
+QuadTriMesh * Mesh::toQuadTriMesh() const
+{
+ MeshBuilder builder;
+
+ const uint vertexCount = this->vertexCount();
+ builder.hintVertexCount(vertexCount);
+
+ for(uint v = 0; v < vertexCount; v++)
+ {
+ const Vertex * vertex = vertexAt(v);
+
+ builder.addPosition(vertex->pos);
+ builder.addNormal(vertex->nor);
+ builder.addTexCoord(vertex->tex);
+ }
+
+ const uint faceCount = this->faceCount();
+ builder.hintTriangleCount(faceCount);
+
+ for(uint f = 0; f < faceCount; f++)
+ {
+ const Face * face = faceAt(f);
+
+ builder.beginPolygon();
+
+ for(Face::ConstEdgeIterator it(face->edges()); !it.isDone(); it.advance())
+ {
+ uint v = it.current()->vertex->id;
+ builder.addVertex(v, v, v);
+ }
+
+ builder.endPolygon();
+ }
+
+ builder.done();
+
+ return builder.buildQuadTriMesh();
+}
+
+
+// Triangulate in place.
+void Mesh::triangulate() {
+
+ bool all_triangles = true;
+
+ const uint faceCount = m_faceArray.count();
+ for (uint 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.
+ Array<Edge *> edgeArray;
+ Array<Face *> faceArray;
+
+ swap(edgeArray, m_edgeArray);
+ swap(faceArray, m_faceArray);
+ m_edgeMap.clear();
+
+ for (uint f = 0; f < faceCount; f++) {
+ Face * face = faceArray[f];
+
+ // Trivial fan-like triangulation.
+ const uint v0 = face->edge->vertex->id;
+ uint v2, v1 = -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;
+ }
+ }
+
+ nvDebugCheck(m_faceArray.count() > faceCount); // triangle count > face count
+
+ linkBoundary();
+
+ deleteAll(edgeArray);
+ deleteAll(faceArray);
+}
+
+
+/*
+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 Mesh::splitBoundaryEdges() {
+
+ Array<Vertex *> boundaryVertices;
+
+ foreach(i, m_vertexArray) {
+ Vertex * v = m_vertexArray[i];
+ if (v->isBoundary()) {
+ boundaryVertices.append(v);
+ }
+ }
+
+ nvDebug("Fixing T-junctions:\n");
+
+ int splitCount = 0;
+
+ foreach(v, boundaryVertices) {
+ Vertex * vertex = boundaryVertices[v];
+
+ Vector3 x0 = vertex->pos;
+
+ // Find edges that this vertex overlaps with.
+ foreach(e, m_edgeArray) {
+ //for (uint e = 0; e < m_edgeArray.count(); 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);
+
+ // @@ Snap x0 to x1 or x2, if too close? No, do vertex snapping elsewhere.
+ /*if (equal(t, 0.0f, 0.01f)) {
+ //vertex->setPos(x1);
+ }
+ else if (equal(t, 1.0f, 0.01f)) {
+ //vertex->setPos(x2);
+ }
+ else*/
+ if (t > 0.0f + NV_EPSILON && t < 1.0f - NV_EPSILON) {
+ nvDebugCheck(equal(lerp(x1, x2, t), x0));
+
+ Vertex * splitVertex = splitBoundaryEdge(edge, t, x0);
+ vertex->linkColocal(splitVertex); // @@ Should we do this here?
+ splitCount++;
+ }
+ }
+ }
+ }
+ }
+
+ nvDebug(" - %d edges split.\n", splitCount);
+
+ nvDebugCheck(isValid());
+
+ return splitCount != 0;
+}
+
+
+// For this to be effective, we have to fix the boundary junctions first.
+Edge * Mesh::sewBoundary(Edge * startEdge) {
+ nvDebugCheck(startEdge->face == NULL);
+
+ // @@ We may want to be more conservative linking colocals in order to preserve the input topology. One way of doing that is by linking colocals only
+ // if the vertices next to them are linked as well. That is, by sewing boundaries after detecting them. If any pair of consecutive edges have their first
+ // and last vertex in the same position, then it can be linked.
+
+ Edge * lastBoundarySeen = startEdge;
+
+ nvDebug("Sewing Boundary:\n");
+
+ int count = 0;
+ int sewnCount = 0;
+
+ Edge * edge = startEdge;
+ do {
+ nvDebugCheck(edge->face == NULL);
+
+ Edge * edge_a = edge;
+ Edge * edge_b = edge->prev;
+
+ Edge * pair_a = edge_a->pair;
+ Edge * pair_b = edge_b->pair;
+
+ Vertex * v0a = edge_a->to();
+ Vertex * v0b = edge_b->from();
+ Vertex * v1a = edge_a->from();
+ Vertex * v1b = edge_b->to();
+
+ nvDebugCheck(v1a->isColocal(v1b));
+
+ /*
+ v0b + _+ v0a
+ \ /
+ b \ / a
+ \|/
+ v1b + v1a
+ */
+
+ // @@ This should not happen while sewing, but it may be produced somewhere else.
+ nvDebugCheck(edge_a != edge_b);
+
+ if (v0a->pos == v0b->pos) {
+
+ // Link vertices.
+ v0a->linkColocal(v0b);
+
+ // Remove edges to be collapsed.
+ disconnect(edge_a);
+ disconnect(edge_b);
+ disconnect(pair_a);
+ disconnect(pair_b);
+
+ // Link new boundary edges.
+ Edge * prevBoundary = edge_b->prev;
+ Edge * nextBoundary = edge_a->next;
+ if (nextBoundary != NULL) {
+ nvDebugCheck(nextBoundary->face == NULL);
+ nvDebugCheck(prevBoundary->face == NULL);
+ nextBoundary->setPrev(prevBoundary);
+
+ // Make sure boundary vertex points to boundary edge.
+ v0a->setEdge(nextBoundary); // This updates all colocals.
+ }
+ lastBoundarySeen = prevBoundary;
+
+ // Creat new edge.
+ Edge * newEdge_a = addEdge(v0a->id, v1a->id); // pair_a->from()->id, pair_a->to()->id
+ Edge * newEdge_b = addEdge(v1b->id, v0b->id);
+
+ newEdge_a->pair = newEdge_b;
+ newEdge_b->pair = newEdge_a;
+
+ newEdge_a->face = pair_a->face;
+ newEdge_b->face = pair_b->face;
+
+ newEdge_a->setNext(pair_a->next);
+ newEdge_a->setPrev(pair_a->prev);
+
+ newEdge_b->setNext(pair_b->next);
+ newEdge_b->setPrev(pair_b->prev);
+
+ delete edge_a;
+ delete edge_b;
+ delete pair_a;
+ delete pair_b;
+
+ edge = nextBoundary; // If nextBoundary is NULL we have closed the loop.
+ sewnCount++;
+ }
+ else {
+ edge = edge->next;
+ }
+
+ count++;
+ } while(edge != NULL && edge != lastBoundarySeen);
+
+ nvDebug(" - Sewn %d out of %d.\n", sewnCount, count);
+
+ if (lastBoundarySeen != NULL) {
+ nvDebugCheck(lastBoundarySeen->face == NULL);
+ }
+
+ return lastBoundarySeen;
+}
+
+
+// @@ We must always disconnect edge pairs simultaneously.
+void Mesh::disconnect(Edge * edge) {
+ nvDebugCheck(edge != NULL);
+
+ // Remove from edge list.
+ if ((edge->id & 1) == 0) {
+ nvDebugCheck(m_edgeArray[edge->id / 2] == edge);
+ m_edgeArray[edge->id / 2] = NULL;
+ }
+
+ // Remove edge from map. @@ Store map key inside edge?
+ nvDebugCheck(edge->from() != NULL && edge->to() != NULL);
+ bool removed = m_edgeMap.remove(Key(edge->from()->id, edge->to()->id));
+ nvDebugCheck(removed == true);
+
+ // 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?
+ }
+ }
+ //edge->setVertex(NULL);
+ }
+
+ // 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?
+ }
+ }
+ //edge->setFace(NULL);
+ }
+
+ // @@ Hack, we don't disconnect from pair, because pair needs us to remove itself from the map.
+ // Disconect from pair.
+ /*if (edge->pair != NULL) {
+ if (edge->pair->pair == edge) {
+ edge->pair->setPair(NULL);
+ }
+ //edge->setPair(NULL);
+ }*/
+
+ // 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 Mesh::remove(Edge * edge) {
+ nvDebugCheck(edge != NULL);
+
+ disconnect(edge);
+
+ delete edge;
+}
+
+void Mesh::remove(Vertex * vertex) {
+ nvDebugCheck(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 Mesh::remove(Face * face) {
+ nvDebugCheck(face != NULL);
+
+ // Remove from face list.
+ m_faceArray[face->id] = NULL;
+
+ // Disconnect from edges.
+ if (face->edge != NULL) {
+ nvDebugCheck(face->edge->face == face);
+
+ face->edge->face = NULL;
+
+ face->edge = NULL;
+ }
+
+ delete face;
+}
+
+
+void Mesh::compactEdges() {
+ const uint edgeCount = m_edgeArray.count();
+
+ uint c = 0;
+ for (uint i = 0; i < edgeCount; i++) {
+ if (m_edgeArray[i] != NULL) {
+ if (i != c) {
+ m_edgeArray[c] = m_edgeArray[i];
+ m_edgeArray[c]->id = 2 * c;
+ if (m_edgeArray[c]->pair != NULL) {
+ m_edgeArray[c]->pair->id = 2 * c + 1;
+ }
+ }
+ c++;
+ }
+ }
+
+ m_edgeArray.resize(c);
+}
+
+
+void Mesh::compactVertices() {
+ const uint vertexCount = m_vertexArray.count();
+
+ uint c = 0;
+ for (uint i = 0; i < vertexCount; i++) {
+ if (m_vertexArray[i] != NULL) {
+ if (i != c) {
+ m_vertexArray[c] = m_vertexArray[i];
+ m_vertexArray[c]->id = c;
+ }
+ c++;
+ }
+ }
+
+ m_vertexArray.resize(c);
+
+ // @@ Generate xref array for external attributes.
+}
+
+
+void Mesh::compactFaces() {
+ const uint faceCount = m_faceArray.count();
+
+ uint c = 0;
+ for (uint i = 0; i < faceCount; i++) {
+ if (m_faceArray[i] != NULL) {
+ if (i != c) {
+ m_faceArray[c] = m_faceArray[i];
+ m_faceArray[c]->id = c;
+ }
+ c++;
+ }
+ }
+
+ m_faceArray.resize(c);
+}
+
+
+Vertex * Mesh::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.
+ nvDebugCheck(pair != NULL);
+
+ // Make sure edge is a boundary edge.
+ nvDebugCheck(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);
+ vertex->col = lerp(edge->from()->col, edge->to()->col, 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);
+
+ nvDebugCheck(e0->next == e1);
+ nvDebugCheck(e1->prev == e0);
+
+ nvDebugCheck(p1->next == p0);
+ nvDebugCheck(p0->prev == p1);
+
+ nvDebugCheck(p0->pair == e0);
+ nvDebugCheck(e0->pair == p0);
+
+ nvDebugCheck(p1->pair == e1);
+ nvDebugCheck(e1->pair == p1);
+
+ // Link faces.
+ e0->face = edge->face;
+ e1->face = edge->face;
+
+ // Link vertices.
+ edge->from()->setEdge(e0);
+ vertex->setEdge(e1);
+
+ delete edge;
+ delete pair;
+
+ return vertex;
+}
+
+#if 0
+// Without introducing new vertices.
+void Mesh::splitBoundaryEdge(Edge * edge, Vertex * vertex) {
+
+ /*
+ We want to go from this configuration:
+
+ | | pn
+ + +
+ | ^
+ | |
+ edge |<->| pair
+ | |
+ v |
+ + +
+ | | pp
+
+ To this one:
+ \ /
+ \ /
+ + +
+ | ^
+ e0 |<->| p0
+ v |
+ vertex + +
+ | ^
+ e1 |<->| p1
+ v |
+ + +
+ / \
+ / \
+ */
+
+
+ Edge * pair = edge->pair;
+ Edge * pn = pair->next();
+ Edge * pp = pair->prev();
+
+ // Make sure boundaries are linked.
+ nvDebugCheck(pair != NULL);
+
+ // Make sure edge is a boundary edge.
+ nvDebugCheck(pair->face() == NULL);
+
+ nvDebugCheck(edge->isValid());
+ nvDebugCheck(pair->isValid());
+
+ disconnect(edge);
+ disconnect(pair);
+
+ // Add edges.
+ Edge * e0 = addEdge(edge->from()->id(), vertex->id());
+ Edge * e1 = addEdge(vertex->id(), edge->to()->id());
+
+ // Link faces.
+ e0->setFace(edge->face());
+ e1->setFace(edge->face());
+
+ // Link pairs.
+ Edge * p0 = findEdge(vertex->id(), pair->to()->id());
+ if (p0 == NULL) {
+ p0 = addEdge(vertex->id(), pair->to()->id());
+ pn->setPrev(p0);
+ }
+ else {
+ nvDebugCheck(p0->face() != NULL);
+ if (e0->prev() != NULL) {
+ pn->setPrev(e0->prev());
+ }
+ else {
+ nvDebugCheck(pn == e0);
+ }
+ }
+
+ Edge * p1 = findEdge(pair->from()->id(), vertex->id());
+ if (p1 == NULL) {
+ p1 = addEdge(pair->from()->id(), vertex->id());
+ pp->setNext(p1);
+ }
+ else {
+ nvDebugCheck(p1->face() != NULL);
+ if (e1->next() != NULL) {
+ pp->setPrev(e1->next());
+ }
+ else {
+ nvDebugCheck(pp == e1);
+ }
+ }
+
+ // Link edges.
+ e0->setNext(e1); // e1->setPrev(e0)
+
+ if (p0->face() == p1->face()) { // can be null
+ p1->setNext(p0); // p0->setPrev(p1)
+ }
+ else {
+ //if (p1->face() == NULL) p1->setNext(
+ }
+
+
+ e0->setPrev(edge->prev());
+ e1->setNext(edge->next());
+
+ nvDebugCheck(e0->pair == p0);
+ nvDebugCheck(e1->pair == p1);
+ nvDebugCheck(p0->pair == e0);
+ nvDebugCheck(p1->pair == e1);
+
+ nvDebugCheck(e0->isValid());
+ nvDebugCheck(e1->isValid());
+ nvDebugCheck(pp->isValid());
+ nvDebugCheck(pn->isValid());
+
+ nvDebugCheck(e0->pair->isValid());
+ nvDebugCheck(e1->pair->isValid());
+ nvDebugCheck(pp->pair->isValid());
+ nvDebugCheck(pn->pair->isValid());
+
+ nvDebugCheck(edge->face->isValid());
+
+ if (pn->pair->face != NULL) {
+ nvDebugCheck(pn->pair->face->isValid());
+ }
+
+ if (pp->pair->face() != NULL) {
+ nvDebugCheck(pn->pair->face->isValid());
+ }
+
+ if (p0->face != NULL) {
+ nvDebugCheck(p0->face->isValid());
+ }
+
+ if (p1->face() != NULL) {
+ nvDebugCheck(p1->face()->isValid());
+ }
+
+ nvDebugCheck(isValid()); // Only for extreme debugging.
+
+ // Link vertices.
+ edge->from()->setEdge(e0);
+ vertex->setEdge(p0);
+
+ delete edge;
+ delete pair;
+}
+#endif
+
+bool Mesh::isValid() const
+{
+ // Make sure all edges are valid.
+ const uint edgeCount = m_edgeArray.count();
+ for (uint 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;
+}
diff --git a/thirdparty/thekla_atlas/nvmesh/halfedge/Mesh.h b/thirdparty/thekla_atlas/nvmesh/halfedge/Mesh.h
new file mode 100644
index 0000000000..c202c2ef9a
--- /dev/null
+++ b/thirdparty/thekla_atlas/nvmesh/halfedge/Mesh.h
@@ -0,0 +1,274 @@
+// This code is in the public domain -- castanyo@yahoo.es
+
+#pragma once
+#ifndef NV_MESH_HALFEDGE_MESH_H
+#define NV_MESH_HALFEDGE_MESH_H
+
+#include "nvmesh/nvmesh.h"
+#include "nvcore/Array.h"
+#include "nvcore/HashMap.h"
+
+/*
+If I were to redo this again, there are a number of things that I would do differently.
+- Edge map is only useful when importing a mesh to guarantee the result is two-manifold. However, when manipulating the mesh
+ it's a pain to maintain the map up to date.
+- Edge array only points to the even vertices. There's no good reason for that. The map becomes required to traverse all edges
+ or you have to make sure edges are properly paired.
+- Linked boundaries. It's cleaner to assume a NULL pair means a boundary edge. Makes easier to seal boundaries. The only reason
+ why we link boundaries is to simplify traversal, but that could be done with two helper functions (nextBoundary, prevBoundary).
+- Minimize the amount of state that needs to be set in a certain way:
+ - boundary vertices point to boundary edge.
+- Remove parenthesis! Make some members public.
+- Remove member functions with side effects:
+ - e->setNext(n) modifies e->next and n->prev, instead use "link(e, n)", or "e->next = n, n->prev = e"
+*/
+
+
+namespace nv
+{
+ class Vector3;
+ class TriMesh;
+ class QuadTriMesh;
+ //template <typename T> struct Hash<Mesh::Key>;
+
+ namespace HalfEdge
+ {
+ class Edge;
+ class Face;
+ class Vertex;
+
+ /// Simple half edge mesh designed for dynamic mesh manipulation.
+ class Mesh
+ {
+ public:
+
+ Mesh();
+ Mesh(const Mesh * mesh);
+ ~Mesh();
+
+ void clear();
+
+ Vertex * addVertex(const Vector3 & pos);
+ //Vertex * addVertex(uint id, const Vector3 & pos);
+ //void addVertices(const Mesh * mesh);
+
+ void linkColocals();
+ void linkColocalsWithCanonicalMap(const Array<uint> & canonicalMap);
+ void resetColocalLinks();
+
+ Face * addFace();
+ Face * addFace(uint v0, uint v1, uint v2);
+ Face * addFace(uint v0, uint v1, uint v2, uint v3);
+ Face * addFace(const Array<uint> & indexArray);
+ Face * addFace(const Array<uint> & indexArray, uint first, uint num);
+ //void addFaces(const Mesh * mesh);
+
+ // These functions disconnect the given element from the mesh and delete it.
+ void disconnect(Edge * edge);
+ void disconnectPair(Edge * edge);
+ void disconnect(Vertex * vertex);
+ void disconnect(Face * face);
+
+ void remove(Edge * edge);
+ void remove(Vertex * vertex);
+ void remove(Face * face);
+
+ // Remove holes from arrays and reassign indices.
+ void compactEdges();
+ void compactVertices();
+ void compactFaces();
+
+ void triangulate();
+
+ void linkBoundary();
+
+ bool splitBoundaryEdges(); // Returns true if any split was made.
+
+ // Sew the boundary that starts at the given edge, returns one edge that still belongs to boundary, or NULL if boundary closed.
+ HalfEdge::Edge * sewBoundary(Edge * startEdge);
+
+
+ // Vertices
+ uint vertexCount() const { return m_vertexArray.count(); }
+ const Vertex * vertexAt(int i) const { return m_vertexArray[i]; }
+ Vertex * vertexAt(int i) { return m_vertexArray[i]; }
+
+ uint colocalVertexCount() const { return m_colocalVertexCount; }
+
+ // Faces
+ uint faceCount() const { return m_faceArray.count(); }
+ const Face * faceAt(int i) const { return m_faceArray[i]; }
+ Face * faceAt(int i) { return m_faceArray[i]; }
+
+ // Edges
+ uint edgeCount() const { return m_edgeArray.count(); }
+ 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;
+ uint 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;
+ uint 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;
+ uint 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;
+ uint 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;
+ uint 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;
+ uint m_current;
+ };
+ ConstEdgeIterator edges() const { return ConstEdgeIterator(this); }
+
+ // @@ Add half-edge iterator.
+
+
+
+ // Convert to tri mesh.
+ TriMesh * toTriMesh() const;
+ QuadTriMesh * toQuadTriMesh() const;
+
+ bool isValid() const;
+
+ public:
+
+ // Error status:
+ mutable uint errorCount;
+ mutable uint errorIndex0;
+ mutable uint errorIndex1;
+
+ private:
+
+ bool canAddFace(const Array<uint> & indexArray, uint first, uint num) const;
+ bool canAddEdge(uint i, uint j) const;
+ Edge * addEdge(uint i, uint j);
+
+ Edge * findEdge(uint i, uint j) const;
+
+ void linkBoundaryEdge(Edge * edge);
+ Vertex * splitBoundaryEdge(Edge * edge, float t, const Vector3 & pos);
+ void splitBoundaryEdge(Edge * edge, Vertex * vertex);
+
+ private:
+
+ Array<Vertex *> m_vertexArray;
+ Array<Edge *> m_edgeArray;
+ Array<Face *> m_faceArray;
+
+ struct Key {
+ Key() {}
+ Key(const Key & k) : p0(k.p0), p1(k.p1) {}
+ Key(uint v0, uint 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; }
+
+ uint p0;
+ uint p1;
+ };
+ friend struct Hash<Mesh::Key>;
+
+ HashMap<Key, Edge *> m_edgeMap;
+
+ uint m_colocalVertexCount;
+
+ };
+ /*
+ // This is a much better hash than the default and greatly improves performance!
+ template <> struct hash<Mesh::Key>
+ {
+ uint operator()(const Mesh::Key & k) const { return k.p0 + k.p1; }
+ };
+ */
+
+ } // HalfEdge namespace
+
+} // nv namespace
+
+#endif // NV_MESH_HALFEDGE_MESH_H
diff --git a/thirdparty/thekla_atlas/nvmesh/halfedge/Vertex.cpp b/thirdparty/thekla_atlas/nvmesh/halfedge/Vertex.cpp
new file mode 100644
index 0000000000..66dad69f8a
--- /dev/null
+++ b/thirdparty/thekla_atlas/nvmesh/halfedge/Vertex.cpp
@@ -0,0 +1,94 @@
+// This code is in the public domain -- castano@gmail.com
+
+#include "nvmesh.h" // pch
+
+#include "Vertex.h"
+
+#include "nvmath/Vector.inl"
+
+using namespace nv;
+using namespace HalfEdge;
+
+
+// Set first edge of all colocals.
+void Vertex::setEdge(Edge * e)
+{
+ for (VertexIterator it(colocals()); !it.isDone(); it.advance()) {
+ it.current()->edge = e;
+ }
+}
+
+// Update position of all colocals.
+void Vertex::setPos(const Vector3 & p)
+{
+ for (VertexIterator it(colocals()); !it.isDone(); it.advance()) {
+ it.current()->pos = p;
+ }
+}
+
+
+uint HalfEdge::Vertex::colocalCount() const
+{
+ uint count = 0;
+ for (ConstVertexIterator it(colocals()); !it.isDone(); it.advance()) { ++count; }
+ return count;
+}
+
+uint HalfEdge::Vertex::valence() const
+{
+ uint count = 0;
+ for (ConstEdgeIterator it(edges()); !it.isDone(); it.advance()) { ++count; }
+ return count;
+}
+
+const HalfEdge::Vertex * HalfEdge::Vertex::firstColocal() const
+{
+ uint 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;
+}
+
+HalfEdge::Vertex * HalfEdge::Vertex::firstColocal()
+{
+ Vertex * vertex = this;
+ uint firstId = id;
+
+ for (VertexIterator it(colocals()); !it.isDone(); it.advance())
+ {
+ if (it.current()->id < firstId) {
+ firstId = vertex->id;
+ vertex = it.current();
+ }
+ }
+
+ return vertex;
+}
+
+bool HalfEdge::Vertex::isFirstColocal() const
+{
+ return firstColocal() == this;
+}
+
+bool HalfEdge::Vertex::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;
+}
+
diff --git a/thirdparty/thekla_atlas/nvmesh/halfedge/Vertex.h b/thirdparty/thekla_atlas/nvmesh/halfedge/Vertex.h
new file mode 100644
index 0000000000..1c5c8d7141
--- /dev/null
+++ b/thirdparty/thekla_atlas/nvmesh/halfedge/Vertex.h
@@ -0,0 +1,221 @@
+// This code is in the public domain -- castanyo@yahoo.es
+
+#pragma once
+#ifndef NV_MESH_HALFEDGE_VERTEX_H
+#define NV_MESH_HALFEDGE_VERTEX_H
+
+#include "nvmesh/halfedge/Edge.h"
+
+namespace nv
+{
+ namespace HalfEdge { class Vertex; class Face; class Edge; }
+
+ // Half edge vertex.
+ class HalfEdge::Vertex
+ {
+ NV_FORBID_COPY(Vertex);
+ public:
+
+ uint id;
+
+ Edge * edge;
+ Vertex * next;
+ Vertex * prev;
+
+ Vector3 pos;
+ Vector3 nor;
+ Vector2 tex;
+ Vector4 col;
+
+
+ Vertex(uint id) : id(id), edge(NULL), pos(0.0f), nor(0.0f), tex(0.0f), col(0.0f) {
+ next = this;
+ prev = this;
+ }
+
+
+ void setEdge(Edge * e);
+ void setPos(const Vector3 & p);
+
+ uint colocalCount() const;
+ uint valence() const;
+ bool isFirstColocal() const;
+ const Vertex * firstColocal() const;
+ Vertex * firstColocal();
+
+ bool isColocal(const Vertex * v) const;
+
+
+ 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);
+ }
+
+
+ // for(EdgeIterator it(iterator()); !it.isDone(); it.advance()) { ... }
+ //
+ // EdgeIterator it(iterator());
+ // while(!it.isDone()) {
+ // ...
+ // id.advance();
+ // }
+
+ // 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 the edges around this vertex in counterclockwise order.
+ class ReverseEdgeIterator //: public Iterator<Edge *>
+ {
+ public:
+ ReverseEdgeIterator(Edge * e) : m_end(NULL), m_current(e) { }
+
+ virtual void advance()
+ {
+ if (m_end == NULL) m_end = m_current;
+ 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;
+ };
+
+ // Iterator that visits the edges around this vertex in counterclockwise order.
+ class ReverseConstEdgeIterator //: public Iterator<Edge *>
+ {
+ public:
+ ReverseConstEdgeIterator(const Edge * e) : m_end(NULL), m_current(e) { }
+
+ virtual void advance()
+ {
+ if (m_end == NULL) m_end = m_current;
+ 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;
+ };
+
+
+
+ // 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); }
+
+ };
+
+} // nv namespace
+
+#endif // NV_MESH_HALFEDGE_VERTEX_H
diff --git a/thirdparty/thekla_atlas/nvmesh/nvmesh.cpp b/thirdparty/thekla_atlas/nvmesh/nvmesh.cpp
new file mode 100644
index 0000000000..d007eda332
--- /dev/null
+++ b/thirdparty/thekla_atlas/nvmesh/nvmesh.cpp
@@ -0,0 +1,2 @@
+#include "nvmesh.h" // pch
+
diff --git a/thirdparty/thekla_atlas/nvmesh/nvmesh.h b/thirdparty/thekla_atlas/nvmesh/nvmesh.h
new file mode 100644
index 0000000000..eb6819675d
--- /dev/null
+++ b/thirdparty/thekla_atlas/nvmesh/nvmesh.h
@@ -0,0 +1,34 @@
+// This code is in the public domain -- castanyo@yahoo.es
+
+#pragma once
+#ifndef NV_MESH_H
+#define NV_MESH_H
+
+#include "nvcore/nvcore.h"
+
+// Function linkage
+#if NVMESH_SHARED
+#ifdef NVMESH_EXPORTS
+#define NVMESH_API DLL_EXPORT
+#define NVMESH_CLASS DLL_EXPORT_CLASS
+#else
+#define NVMESH_API DLL_IMPORT
+#define NVMESH_CLASS DLL_IMPORT
+#endif
+#else
+#define NVMESH_API
+#define NVMESH_CLASS
+#endif
+
+#if 1 //USE_PRECOMPILED_HEADERS // If using precompiled headers:
+//#include <string.h> // strlen, strcmp, etc.
+//#include "nvcore/StrLib.h"
+//#include "nvcore/StdStream.h"
+//#include "nvcore/Memory.h"
+//#include "nvcore/Debug.h"
+//#include "nvmath/Vector.h"
+//#include "nvcore/Array.h"
+//#include "nvcore/HashMap.h"
+#endif
+
+#endif // NV_MESH_H
diff --git a/thirdparty/thekla_atlas/nvmesh/param/Atlas.cpp b/thirdparty/thekla_atlas/nvmesh/param/Atlas.cpp
new file mode 100644
index 0000000000..4e41e76695
--- /dev/null
+++ b/thirdparty/thekla_atlas/nvmesh/param/Atlas.cpp
@@ -0,0 +1,1515 @@
+// Copyright NVIDIA Corporation 2006 -- Ignacio Castano <icastano@nvidia.com>
+
+#include "nvmesh.h" // pch
+
+#include "Atlas.h"
+#include "Util.h"
+#include "AtlasBuilder.h"
+#include "AtlasPacker.h"
+#include "SingleFaceMap.h"
+#include "OrthogonalProjectionMap.h"
+#include "LeastSquaresConformalMap.h"
+#include "ParameterizationQuality.h"
+
+//#include "nvmesh/export/MeshExportOBJ.h"
+
+#include "nvmesh/halfedge/Mesh.h"
+#include "nvmesh/halfedge/Face.h"
+#include "nvmesh/halfedge/Vertex.h"
+
+#include "nvmesh/MeshBuilder.h"
+#include "nvmesh/MeshTopology.h"
+#include "nvmesh/param/Util.h"
+#include "nvmesh/geometry/Measurements.h"
+
+#include "nvmath/Vector.inl"
+#include "nvmath/Fitting.h"
+#include "nvmath/Box.inl"
+#include "nvmath/ProximityGrid.h"
+#include "nvmath/Morton.h"
+
+#include "nvcore/StrLib.h"
+#include "nvcore/Array.inl"
+#include "nvcore/HashMap.inl"
+
+using namespace nv;
+
+
+/// Ctor.
+Atlas::Atlas()
+{
+}
+
+// Dtor.
+Atlas::~Atlas()
+{
+ deleteAll(m_meshChartsArray);
+}
+
+uint Atlas::chartCount() const
+{
+ uint count = 0;
+ foreach(c, m_meshChartsArray) {
+ count += m_meshChartsArray[c]->chartCount();
+ }
+ return count;
+}
+
+const Chart * Atlas::chartAt(uint i) const
+{
+ foreach(c, m_meshChartsArray) {
+ uint count = m_meshChartsArray[c]->chartCount();
+
+ if (i < count) {
+ return m_meshChartsArray[c]->chartAt(i);
+ }
+
+ i -= count;
+ }
+
+ return NULL;
+}
+
+Chart * Atlas::chartAt(uint i)
+{
+ foreach(c, m_meshChartsArray) {
+ uint count = m_meshChartsArray[c]->chartCount();
+
+ if (i < count) {
+ return m_meshChartsArray[c]->chartAt(i);
+ }
+
+ i -= count;
+ }
+
+ return NULL;
+}
+
+// Extract the charts and add to this atlas.
+void Atlas::addMeshCharts(MeshCharts * meshCharts)
+{
+ m_meshChartsArray.append(meshCharts);
+}
+
+void Atlas::extractCharts(const HalfEdge::Mesh * mesh)
+{
+ MeshCharts * meshCharts = new MeshCharts(mesh);
+ meshCharts->extractCharts();
+ addMeshCharts(meshCharts);
+}
+
+void Atlas::computeCharts(const HalfEdge::Mesh * mesh, const SegmentationSettings & settings, const Array<uint> & unchartedMaterialArray)
+{
+ MeshCharts * meshCharts = new MeshCharts(mesh);
+ meshCharts->computeCharts(settings, unchartedMaterialArray);
+ addMeshCharts(meshCharts);
+}
+
+
+
+
+#if 0
+
+/// Compute a seamless texture atlas.
+bool Atlas::computeSeamlessTextureAtlas(bool groupFaces/*= true*/, bool scaleTiles/*= false*/, uint w/*= 1024*/, uint h/* = 1024*/)
+{
+ // Implement seamless texture atlas similar to what ZBrush does. See also:
+ // "Meshed Atlases for Real-Time Procedural Solid Texturing"
+ // http://graphics.cs.uiuc.edu/~jch/papers/rtpst.pdf
+
+ // Other methods that we should experiment with:
+ //
+ // Seamless Texture Atlases:
+ // http://www.cs.jhu.edu/~bpurnomo/STA/index.html
+ //
+ // Rectangular Multi-Chart Geometry Images:
+ // http://graphics.cs.uiuc.edu/~jch/papers/rmcgi.pdf
+ //
+ // Discrete differential geometry also provide a way of constructing
+ // seamless quadrangulations as shown in:
+ // http://www.geometry.caltech.edu/pubs/TACD06.pdf
+ //
+
+#pragma message(NV_FILE_LINE "TODO: Implement seamless texture atlas.")
+
+ if (groupFaces)
+ {
+ // @@ TODO.
+ }
+ else
+ {
+ // @@ Create one atlas per face.
+ }
+
+ if (scaleTiles)
+ {
+ // @@ TODO
+ }
+
+ /*
+ if (!isQuadMesh(m_mesh)) {
+ // Only handle quads for now.
+ return false;
+ }
+
+ // Each face is a chart.
+ const uint faceCount = m_mesh->faceCount();
+ m_chartArray.resize(faceCount);
+
+ for(uint f = 0; f < faceCount; f++) {
+ m_chartArray[f].faceArray.clear();
+ m_chartArray[f].faceArray.append(f);
+ }
+
+ // Map each face to a separate square.
+
+ // Determine face layout according to width and height.
+ float aspect = float(m_width) / float(m_height);
+
+ uint i = 2;
+ uint total = (m_width / (i+1)) * (m_height / (i+1));
+ while(total > faceCount) {
+ i *= 2;
+ total = (m_width / (i+1)) * (m_height / (i+1));
+ }
+
+ uint tileSize = i / 2;
+
+ int x = 0;
+ int y = 0;
+
+ m_result = new HalfEdge::Mesh();
+
+ // Once you have that it's just matter of traversing the faces.
+ for(uint f = 0; f < faceCount; f++) {
+ // Compute texture coordinates.
+ Vector2 tex[4];
+ tex[0] = Vector2(float(x), float(y));
+ tex[1] = Vector2(float(x+tileSize), float(y));
+ tex[2] = Vector2(float(x+tileSize), float(y+tileSize));
+ tex[3] = Vector2(float(x), float(y+tileSize));
+
+ Array<uint> indexArray(4);
+
+ const HalfEdge::Face * face = m_mesh->faceAt(f);
+
+ int i = 0;
+ for(HalfEdge::Face::ConstEdgeIterator it(face->edges()); !it.isDone(); it.advance(), i++) {
+ const HalfEdge::Edge * edge = it.current();
+ const HalfEdge::Vertex * vertex = edge->from();
+
+ HalfEdge::Vertex * newVertex = m_result->addVertex(vertex->id(), vertex->pos());
+
+ newVertex->setTex(Vector3(tex[i], 0));
+ newVertex->setNor(vertex->nor());
+
+ indexArray.append(m_result->vertexCount() + 1);
+ }
+
+ m_result->addFace(indexArray);
+
+ // Move to the next tile.
+ x += tileSize + 1;
+ if (x + tileSize > m_width) {
+ x = 0;
+ y += tileSize + 1;
+ }
+ }
+ */
+
+ return false;
+}
+
+#endif
+
+
+void Atlas::parameterizeCharts()
+{
+ foreach(i, m_meshChartsArray) {
+ m_meshChartsArray[i]->parameterizeCharts();
+ }
+}
+
+
+float Atlas::packCharts(int quality, float texelsPerUnit, bool blockAlign, bool conservative)
+{
+ AtlasPacker packer(this);
+ packer.packCharts(quality, texelsPerUnit, blockAlign, conservative);
+ return packer.computeAtlasUtilization();
+}
+
+
+
+
+/// Ctor.
+MeshCharts::MeshCharts(const HalfEdge::Mesh * mesh) : m_mesh(mesh)
+{
+}
+
+// Dtor.
+MeshCharts::~MeshCharts()
+{
+ deleteAll(m_chartArray);
+}
+
+
+void MeshCharts::extractCharts()
+{
+ const uint faceCount = m_mesh->faceCount();
+
+ int first = 0;
+ Array<uint> queue(faceCount);
+
+ BitArray bitFlags(faceCount);
+ bitFlags.clearAll();
+
+ for (uint f = 0; f < faceCount; f++)
+ {
+ if (bitFlags.bitAt(f) == false)
+ {
+ // Start new patch. Reset queue.
+ first = 0;
+ queue.clear();
+ queue.append(f);
+ bitFlags.setBitAt(f);
+
+ while (first != queue.count())
+ {
+ 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();
+ nvDebugCheck(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;
+ nvDebugCheck(neighborFace != NULL);
+
+ if (bitFlags.bitAt(neighborFace->id) == false)
+ {
+ queue.append(neighborFace->id);
+ bitFlags.setBitAt(neighborFace->id);
+ }
+ }
+ }
+
+ first++;
+ }
+
+ Chart * chart = new Chart();
+ chart->build(m_mesh, queue);
+
+ m_chartArray.append(chart);
+ }
+ }
+}
+
+
+/*
+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.
+
+*/
+
+
+SegmentationSettings::SegmentationSettings()
+{
+ // Charts have no area or boundary limits right now.
+ maxChartArea = NV_FLOAT_MAX;
+ maxBoundaryLength = NV_FLOAT_MAX;
+
+ proxyFitMetricWeight = 1.0f;
+ roundnessMetricWeight = 0.1f;
+ straightnessMetricWeight = 0.25f;
+ normalSeamMetricWeight = 1.0f;
+ textureSeamMetricWeight = 0.1f;
+}
+
+
+
+void MeshCharts::computeCharts(const SegmentationSettings & settings, const Array<uint> & unchartedMaterialArray)
+{
+ Chart * vertexMap = NULL;
+
+ if (unchartedMaterialArray.count() != 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.append(vertexMap);
+ }
+
+ if (builder.facesLeft != 0) {
+
+ // Tweak these values:
+ const float maxThreshold = 2;
+ const uint growFaceCount = 32;
+ const uint maxIterations = 4;
+
+ builder.settings = settings;
+
+ //builder.settings.proxyFitMetricWeight *= 0.75; // relax proxy fit weight during initial seed placement.
+ //builder.settings.roundnessMetricWeight = 0;
+ //builder.settings.straightnessMetricWeight = 0;
+
+ // This seems a reasonable estimate.
+ uint maxSeedCount = max(6U, builder.facesLeft);
+
+ // Create initial charts greedely.
+ nvDebug("### Placing seeds\n");
+ builder.placeSeeds(maxThreshold, maxSeedCount);
+ nvDebug("### Placed %d seeds (max = %d)\n", builder.chartCount(), maxSeedCount);
+
+ builder.updateProxies();
+
+ builder.mergeCharts();
+
+ #if 1
+ nvDebug("### Relocating seeds\n");
+ builder.relocateSeeds();
+
+ nvDebug("### Reset charts\n");
+ builder.resetCharts();
+
+ if (vertexMap != NULL) {
+ builder.markUnchartedFaces(vertexMap->faceArray());
+ }
+
+ builder.settings = settings;
+
+ nvDebug("### Growing charts\n");
+
+ // Restart process growing charts in parallel.
+ uint iteration = 0;
+ while (true)
+ {
+ if (!builder.growCharts(maxThreshold, growFaceCount))
+ {
+ nvDebug("### Can't grow anymore\n");
+
+ // If charts cannot grow more: fill holes, merge charts, relocate seeds and start new iteration.
+
+ nvDebug("### Filling holes\n");
+ builder.fillHoles(maxThreshold);
+ nvDebug("### Using %d charts now\n", builder.chartCount());
+
+ builder.updateProxies();
+
+ nvDebug("### Merging charts\n");
+ builder.mergeCharts();
+ nvDebug("### Using %d charts now\n", builder.chartCount());
+
+ nvDebug("### Reseeding\n");
+ if (!builder.relocateSeeds())
+ {
+ nvDebug("### Cannot relocate seeds anymore\n");
+
+ // Done!
+ break;
+ }
+
+ if (iteration == maxIterations)
+ {
+ nvDebug("### Reached iteration limit\n");
+ break;
+ }
+ iteration++;
+
+ nvDebug("### Reset charts\n");
+ builder.resetCharts();
+
+ if (vertexMap != NULL) {
+ builder.markUnchartedFaces(vertexMap->faceArray());
+ }
+
+ nvDebug("### Growing charts\n");
+ }
+ };
+ #endif
+
+ // Make sure no holes are left!
+ nvDebugCheck(builder.facesLeft == 0);
+
+ const uint chartCount = builder.chartArray.count();
+ for (uint i = 0; i < chartCount; i++)
+ {
+ Chart * chart = new Chart();
+ m_chartArray.append(chart);
+
+ chart->build(m_mesh, builder.chartFaces(i));
+ }
+ }
+
+
+ const uint chartCount = m_chartArray.count();
+
+ // Build face indices.
+ m_faceChart.resize(m_mesh->faceCount());
+ m_faceIndex.resize(m_mesh->faceCount());
+
+ for (uint i = 0; i < chartCount; i++)
+ {
+ const Chart * chart = m_chartArray[i];
+
+ const uint faceCount = chart->faceCount();
+ for (uint f = 0; f < faceCount; f++)
+ {
+ uint 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 (uint 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 MeshCharts::parameterizeCharts()
+{
+ ParameterizationQuality globalParameterizationQuality;
+
+ // Parameterize the charts.
+ uint diskCount = 0;
+ const uint chartCount = m_chartArray.count();
+ for (uint 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());
+
+ // If the orthogonal projection produces better results, just use that.
+ // @@ It may be dangerous to do this, because isValid() does not detect self-overlaps.
+ // @@ Another problem is that with very thin patches with nearly zero parametric area, the results of our metric are not accurate.
+ /*if (orthogonalQuality.isValid() && orthogonalQuality.rmsStretchMetric() < lscmQuality.rmsStretchMetric()) {
+ computeOrthogonalProjectionMap(chart->unifiedMesh());
+ chartParameterizationQuality = orthogonalQuality;
+ }
+ else*/ {
+ chartParameterizationQuality = lscmQuality;
+ }
+
+ // If conformal map failed,
+
+ // @@ Experiment with other parameterization methods.
+ //computeCircularBoundaryMap(chart->unifiedMesh());
+ //computeConformalMap(chart->unifiedMesh());
+ //computeNaturalConformalMap(chart->unifiedMesh());
+ //computeGuidanceGradientMap(chart->unifiedMesh());
+ }
+
+ //ParameterizationQuality chartParameterizationQuality(chart->unifiedMesh());
+
+ isValid = chartParameterizationQuality.isValid();
+
+ if (!isValid)
+ {
+ nvDebug("*** Invalid parameterization.\n");
+#if 0
+ // Dump mesh to inspect problem:
+ static int pieceCount = 0;
+
+ StringBuilder fileName;
+ fileName.format("invalid_chart_%d.obj", pieceCount++);
+ exportMesh(chart->unifiedMesh(), fileName.str());
+#endif
+ }
+
+ // @@ Check that parameterization quality is above a certain threshold.
+
+ // @@ Detect boundary self-intersections.
+
+ globalParameterizationQuality += chartParameterizationQuality;
+ }
+
+ if (!isValid)
+ {
+ //nvDebugBreak();
+ // @@ Run the builder again, but only on this chart.
+ //AtlasBuilder builder(chart->chartMesh());
+ }
+
+ // Transfer parameterization from unified mesh to chart mesh.
+ chart->transferParameterization();
+
+ }
+
+ nvDebug(" Parameterized %d/%d charts.\n", diskCount, chartCount);
+ nvDebug(" RMS stretch metric: %f\n", globalParameterizationQuality.rmsStretchMetric());
+ nvDebug(" MAX stretch metric: %f\n", globalParameterizationQuality.maxStretchMetric());
+ nvDebug(" RMS conformal metric: %f\n", globalParameterizationQuality.rmsConformalMetric());
+ nvDebug(" RMS authalic metric: %f\n", globalParameterizationQuality.maxAuthalicMetric());
+}
+
+
+
+Chart::Chart() : m_chartMesh(NULL), m_unifiedMesh(NULL), m_isDisk(false), m_isVertexMapped(false)
+{
+}
+
+void Chart::build(const HalfEdge::Mesh * originalMesh, const Array<uint> & faceArray)
+{
+ // Copy face indices.
+ m_faceArray = faceArray;
+
+ const uint meshVertexCount = originalMesh->vertexCount();
+
+ m_chartMesh = new HalfEdge::Mesh();
+ m_unifiedMesh = new HalfEdge::Mesh();
+
+ Array<uint> chartMeshIndices;
+ chartMeshIndices.resize(meshVertexCount, ~0);
+
+ Array<uint> unifiedMeshIndices;
+ unifiedMeshIndices.resize(meshVertexCount, ~0);
+
+ // Add vertices.
+ const uint faceCount = faceArray.count();
+ for (uint f = 0; f < faceCount; f++)
+ {
+ const HalfEdge::Face * face = originalMesh->faceAt(faceArray[f]);
+ nvDebugCheck(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();
+
+ nvDebugCheck(vertex->pos == unifiedVertex->pos);
+ m_unifiedMesh->addVertex(vertex->pos);
+ }
+
+ if (chartMeshIndices[vertex->id] == ~0)
+ {
+ chartMeshIndices[vertex->id] = m_chartMesh->vertexCount();
+ m_chartToOriginalMap.append(vertex->id);
+ m_chartToUnifiedMap.append(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.
+ //nvCheck(m_chartMesh->colocalVertexCount() == m_unifiedMesh->vertexCount());
+
+ // Is that OK? What happens in meshes were that happens? Does anything break? Apparently not...
+
+
+
+ Array<uint> faceIndices(7);
+
+ // Add faces.
+ for (uint f = 0; f < faceCount; f++)
+ {
+ const HalfEdge::Face * face = originalMesh->faceAt(faceArray[f]);
+ nvDebugCheck(face != NULL);
+
+ faceIndices.clear();
+
+ for(HalfEdge::Face::ConstEdgeIterator it(face->edges()); !it.isDone(); it.advance())
+ {
+ const HalfEdge::Vertex * vertex = it.current()->vertex;
+ nvDebugCheck(vertex != NULL);
+
+ faceIndices.append(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;
+ nvDebugCheck(vertex != NULL);
+
+ vertex = vertex->firstColocal();
+
+ faceIndices.append(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 = unifyVertices(m_unifiedMesh.ptr());
+ }
+
+ //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 = triangulate(m_unifiedMesh.ptr());
+
+ //exportMesh(m_unifiedMesh.ptr(), "debug_triangulated.obj");
+
+
+ // Analyze chart topology.
+ MeshTopology topology(m_unifiedMesh.ptr());
+ m_isDisk = topology.isDisk();
+
+ // This is sometimes failing, when triangulate fails to add a triangle, it generates a hole in the mesh.
+ //nvDebugCheck(m_isDisk);
+
+ /*if (!m_isDisk) {
+ static int pieceCount = 0;
+ StringBuilder fileName;
+ fileName.format("debug_hole_%d.obj", pieceCount++);
+ exportMesh(m_unifiedMesh.ptr(), fileName.str());
+ }*/
+
+
+#if 0
+ if (!m_isDisk) {
+ nvDebugBreak();
+
+ static int pieceCount = 0;
+
+ StringBuilder fileName;
+ fileName.format("debug_nodisk_%d.obj", pieceCount++);
+ exportMesh(m_chartMesh.ptr(), fileName.str());
+ }
+#endif
+
+}
+
+
+void Chart::buildVertexMap(const HalfEdge::Mesh * originalMesh, const Array<uint> & unchartedMaterialArray)
+{
+ nvCheck(m_chartMesh == NULL && m_unifiedMesh == NULL);
+
+ m_isVertexMapped = true;
+
+ // Build face indices.
+ m_faceArray.clear();
+
+ const uint meshFaceCount = originalMesh->faceCount();
+ for (uint f = 0; f < meshFaceCount; f++) {
+ const HalfEdge::Face * face = originalMesh->faceAt(f);
+
+ if (unchartedMaterialArray.contains(face->material)) {
+ m_faceArray.append(f);
+ }
+ }
+
+ const uint faceCount = m_faceArray.count();
+
+ if (faceCount == 0) {
+ return;
+ }
+
+
+ // @@ The chartMesh construction is basically the same as with regular charts, don't duplicate!
+
+ const uint meshVertexCount = originalMesh->vertexCount();
+
+ m_chartMesh = new HalfEdge::Mesh();
+
+ Array<uint> chartMeshIndices;
+ chartMeshIndices.resize(meshVertexCount, ~0);
+
+ // Vertex map mesh only has disconnected vertices.
+ for (uint f = 0; f < faceCount; f++)
+ {
+ const HalfEdge::Face * face = originalMesh->faceAt(m_faceArray[f]);
+ nvDebugCheck(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.append(vertex->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();
+
+ Array<uint> faceIndices(7);
+
+ // Add faces.
+ for (uint f = 0; f < faceCount; f++)
+ {
+ const HalfEdge::Face * face = originalMesh->faceAt(m_faceArray[f]);
+ nvDebugCheck(face != NULL);
+
+ faceIndices.clear();
+
+ for(HalfEdge::Face::ConstEdgeIterator it(face->edges()); !it.isDone(); it.advance())
+ {
+ const HalfEdge::Vertex * vertex = it.current()->vertex;
+ nvDebugCheck(vertex != NULL);
+ nvDebugCheck(chartMeshIndices[vertex->id] != ~0);
+
+ faceIndices.append(chartMeshIndices[vertex->id]);
+ }
+
+ HalfEdge::Face * new_face = m_chartMesh->addFace(faceIndices);
+ nvDebugCheck(new_face != NULL);
+ }
+
+ m_chartMesh->linkBoundary();
+
+
+ const uint chartVertexCount = m_chartMesh->vertexCount();
+
+ Box bounds;
+ bounds.clearBounds();
+
+ for (uint i = 0; i < chartVertexCount; i++) {
+ HalfEdge::Vertex * vertex = m_chartMesh->vertexAt(i);
+ bounds.addPointToBounds(vertex->pos);
+ }
+
+ ProximityGrid grid;
+ grid.init(bounds, chartVertexCount);
+
+ for (uint i = 0; i < chartVertexCount; i++) {
+ HalfEdge::Vertex * vertex = m_chartMesh->vertexAt(i);
+ grid.add(vertex->pos, i);
+ }
+
+
+#if 0
+ // Arrange vertices in a rectangle.
+ vertexMapWidth = ftoi_ceil(sqrtf(float(chartVertexCount)));
+ vertexMapHeight = (chartVertexCount + vertexMapWidth - 1) / vertexMapWidth;
+ nvDebugCheck(vertexMapWidth >= vertexMapHeight);
+
+ int x = 0, y = 0;
+ for (uint i = 0; i < chartVertexCount; i++) {
+ HalfEdge::Vertex * vertex = m_chartMesh->vertexAt(i);
+
+ vertex->tex.x = float(x);
+ vertex->tex.y = float(y);
+
+ x++;
+ if (x == vertexMapWidth) {
+ x = 0;
+ y++;
+ nvCheck(y < vertexMapHeight);
+ }
+ }
+
+#elif 0
+ // Arrange vertices in a rectangle, traversing grid in 3D morton order and laying them down in 2D morton order.
+ vertexMapWidth = ftoi_ceil(sqrtf(float(chartVertexCount)));
+ vertexMapHeight = (chartVertexCount + vertexMapWidth - 1) / vertexMapWidth;
+ nvDebugCheck(vertexMapWidth >= vertexMapHeight);
+
+ int n = 0;
+ uint32 texelCode = 0;
+
+ uint cellsVisited = 0;
+
+ const uint32 cellCodeCount = grid.mortonCount();
+ for (uint32 cellCode = 0; cellCode < cellCodeCount; cellCode++) {
+ int cell = grid.mortonIndex(cellCode);
+ if (cell < 0) continue;
+
+ cellsVisited++;
+
+ const Array<uint> & indexArray = grid.cellArray[cell].indexArray;
+
+ foreach(i, indexArray) {
+ uint idx = indexArray[i];
+ HalfEdge::Vertex * vertex = m_chartMesh->vertexAt(idx);
+
+ //vertex->tex.x = float(n % rectangleWidth) + 0.5f;
+ //vertex->tex.y = float(n / rectangleWidth) + 0.5f;
+
+ // Lay down the points in z order too.
+ uint x, y;
+ do {
+ x = decodeMorton2X(texelCode);
+ y = decodeMorton2Y(texelCode);
+ texelCode++;
+ } while (x >= U32(vertexMapWidth) || y >= U32(vertexMapHeight));
+
+ vertex->tex.x = float(x);
+ vertex->tex.y = float(y);
+
+ n++;
+ }
+ }
+
+ nvDebugCheck(cellsVisited == grid.cellArray.count());
+ nvDebugCheck(n == chartVertexCount);
+
+#else
+
+ uint texelCount = 0;
+
+ const float positionThreshold = 0.01f;
+ const float normalThreshold = 0.01f;
+
+ uint verticesVisited = 0;
+ uint cellsVisited = 0;
+
+ Array<int> vertexIndexArray;
+ vertexIndexArray.resize(chartVertexCount, -1); // Init all indices to -1.
+
+ // Traverse vertices in morton order. @@ It may be more interesting to sort them based on orientation.
+ const uint cellCodeCount = grid.mortonCount();
+ for (uint cellCode = 0; cellCode < cellCodeCount; cellCode++) {
+ int cell = grid.mortonIndex(cellCode);
+ if (cell < 0) continue;
+
+ cellsVisited++;
+
+ const Array<uint> & indexArray = grid.cellArray[cell].indexArray;
+
+ foreach(i, indexArray) {
+ uint idx = indexArray[i];
+ HalfEdge::Vertex * vertex = m_chartMesh->vertexAt(idx);
+
+ nvDebugCheck(vertexIndexArray[idx] == -1);
+
+ Array<uint> neighbors;
+ grid.gather(vertex->pos, positionThreshold, /*ref*/neighbors);
+
+ // Compare against all nearby vertices, cluster greedily.
+ foreach(j, neighbors) {
+ uint 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++;
+ }
+ }
+
+ nvDebugCheck(cellsVisited == grid.cellArray.count());
+ nvDebugCheck(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.
+ nvDebugCheck(vertexMapWidth >= vertexMapHeight);
+
+ nvDebug("Reduced vertex count from %d to %d.\n", chartVertexCount, texelCount);
+
+#if 0
+ // This lays down the clustered vertices linearly.
+ for (uint i = 0; i < chartVertexCount; i++) {
+ HalfEdge::Vertex * vertex = m_chartMesh->vertexAt(i);
+
+ int idx = vertexIndexArray[i];
+
+ vertex->tex.x = float(idx % vertexMapWidth);
+ vertex->tex.y = float(idx / vertexMapWidth);
+ }
+#else
+ // Lay down the clustered vertices in morton order.
+
+ Array<uint> texelCodes;
+ texelCodes.resize(texelCount);
+
+ // For each texel, assign one morton code.
+ uint texelCode = 0;
+ for (uint i = 0; i < texelCount; i++) {
+ uint x, y;
+ do {
+ x = decodeMorton2X(texelCode);
+ y = decodeMorton2Y(texelCode);
+ texelCode++;
+ } while (x >= U32(vertexMapWidth) || y >= U32(vertexMapHeight));
+
+ texelCodes[i] = texelCode - 1;
+ }
+
+ for (uint i = 0; i < chartVertexCount; i++) {
+ HalfEdge::Vertex * vertex = m_chartMesh->vertexAt(i);
+
+ int idx = vertexIndexArray[i];
+ if (idx != -1) {
+ uint texelCode = texelCodes[idx];
+ uint x = decodeMorton2X(texelCode);
+ uint y = decodeMorton2Y(texelCode);
+
+ vertex->tex.x = float(x);
+ vertex->tex.y = float(y);
+ }
+ }
+
+#endif
+
+#endif
+
+}
+
+
+
+static void getBoundaryEdges(HalfEdge::Mesh * mesh, Array<HalfEdge::Edge *> & boundaryEdges)
+{
+ nvDebugCheck(mesh != NULL);
+
+ const uint 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 (uint e = 0; e < edgeCount; e++)
+ {
+ HalfEdge::Edge * startEdge = mesh->edgeAt(e);
+
+ if (startEdge != NULL && startEdge->isBoundary() && bitFlags.bitAt(e) == false)
+ {
+ nvDebugCheck(startEdge->face != NULL);
+ nvDebugCheck(startEdge->pair->face == NULL);
+
+ startEdge = startEdge->pair;
+
+ const HalfEdge::Edge * edge = startEdge;
+ do {
+ nvDebugCheck(edge->face == NULL);
+ nvDebugCheck(bitFlags.bitAt(edge->id/2) == false);
+
+ bitFlags.setBitAt(edge->id / 2);
+ edge = edge->next;
+ } while(startEdge != edge);
+
+ boundaryEdges.append(startEdge);
+ }
+ }
+}
+
+
+bool Chart::closeLoop(uint start, const Array<HalfEdge::Edge *> & loop)
+{
+ const uint vertexCount = loop.count() - start;
+
+ nvDebugCheck(vertexCount >= 3);
+ if (vertexCount < 3) return false;
+
+ nvDebugCheck(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.
+
+ Array<Vector3> points;
+ points.resize(vertexCount);
+ for (uint i = 0; i < vertexCount; i++) {
+ points[i] = loop[start+i]->vertex->pos;
+ }
+
+ bool isPlanar = Fit::isPlanar(vertexCount, points.buffer());
+
+ if (isPlanar) {
+ // Add face and connect edges.
+ HalfEdge::Face * face = m_unifiedMesh->addFace();
+ for (uint 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];
+
+ nvDebugCheck(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 (uint 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 (uint 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);
+ nvDebugCheck(face != NULL);
+ }
+ }
+
+ return true;
+}
+
+
+bool Chart::closeHoles()
+{
+ nvDebugCheck(!m_isVertexMapped);
+
+ Array<HalfEdge::Edge *> boundaryEdges;
+ getBoundaryEdges(m_unifiedMesh.ptr(), boundaryEdges);
+
+ uint boundaryCount = boundaryEdges.count();
+ if (boundaryCount <= 1)
+ {
+ // Nothing to close.
+ return true;
+ }
+
+ // Compute lengths and areas.
+ Array<float> boundaryLengths;
+ //Array<Vector3> boundaryCentroids;
+
+ for (uint i = 0; i < boundaryCount; i++)
+ {
+ const HalfEdge::Edge * startEdge = boundaryEdges[i];
+ nvCheck(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.append(boundaryLength);
+ //boundaryCentroids.append(boundaryCentroid / boundaryEdgeCount);
+ }
+
+
+ // Find disk boundary.
+ uint diskBoundary = 0;
+ float maxLength = boundaryLengths[0];
+
+ for (uint i = 1; i < boundaryCount; i++)
+ {
+ if (boundaryLengths[i] > maxLength)
+ {
+ maxLength = boundaryLengths[i];
+ diskBoundary = i;
+ }
+ }
+
+
+ // Sew holes.
+ /*for (uint i = 0; i < boundaryCount; i++)
+ {
+ if (diskBoundary == i)
+ {
+ // Skip disk boundary.
+ continue;
+ }
+
+ HalfEdge::Edge * startEdge = boundaryEdges[i];
+ nvCheck(startEdge->face() == NULL);
+
+ boundaryEdges[i] = m_unifiedMesh->sewBoundary(startEdge);
+ }
+
+ exportMesh(m_unifiedMesh.ptr(), "debug_sewn.obj");*/
+
+ //bool hasNewHoles = false;
+
+ // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ // @@ Close loop is wrong, after closing a loop, we do not only have to add the face, but make sure that every edge in he loop is pointing to the right place.
+
+ // Close holes.
+ for (uint i = 0; i < boundaryCount; i++)
+ {
+ if (diskBoundary == i)
+ {
+ // Skip disk boundary.
+ continue;
+ }
+
+ HalfEdge::Edge * startEdge = boundaryEdges[i];
+ nvDebugCheck(startEdge != NULL);
+ nvDebugCheck(startEdge->face == NULL);
+
+#if 1
+ Array<HalfEdge::Vertex *> vertexLoop;
+ Array<HalfEdge::Edge *> edgeLoop;
+
+ HalfEdge::Edge * edge = startEdge;
+ do {
+ HalfEdge::Vertex * vertex = edge->next->vertex; // edge->to()
+
+ uint i;
+ for (i = 0; i < vertexLoop.count(); i++) {
+ if (vertex->isColocal(vertexLoop[i])) {
+ break;
+ }
+ }
+
+ bool isCrossing = (i != vertexLoop.count());
+
+ if (isCrossing) {
+
+ HalfEdge::Edge * prev = edgeLoop[i]; // Previous edge before the loop.
+ HalfEdge::Edge * next = edge->next; // Next edge after the loop.
+
+ nvDebugCheck(prev->to()->isColocal(next->from()));
+
+ // Close loop.
+ edgeLoop.append(edge);
+ closeLoop(i+1, edgeLoop);
+
+ // Link boundary loop.
+ prev->setNext(next);
+ vertex->setEdge(next);
+
+ // Start over again.
+ vertexLoop.clear();
+ edgeLoop.clear();
+
+ edge = startEdge;
+ vertex = edge->to();
+ }
+
+ vertexLoop.append(vertex);
+ edgeLoop.append(edge);
+
+ edge = edge->next;
+ } while(edge != startEdge);
+
+ closeLoop(0, edgeLoop);
+#endif
+
+ /*
+
+ // Add face and connect boundary edges.
+ HalfEdge::Face * face = m_unifiedMesh->addFace();
+ face->setEdge(startEdge);
+
+ HalfEdge::Edge * edge = startEdge;
+ do {
+ edge->setFace(face);
+
+ edge = edge->next();
+ } while(edge != startEdge);
+
+ */
+
+
+ /*
+ uint edgeCount = 0;
+ HalfEdge::Edge * edge = startEdge;
+ do {
+ edgeCount++;
+ edge = edge->next();
+ } while(edge != startEdge);
+
+
+
+ // Count edges in this boundary.
+ uint edgeCount = 0;
+ HalfEdge::Edge * edge = startEdge;
+ do {
+ edgeCount++;
+ edge = edge->next();
+ } while(edge != startEdge);
+
+ // Trivial hole, fill with one triangle. This actually works for all convex boundaries with non colinear vertices.
+ if (edgeCount == 3) {
+ // Add face and connect boundary edges.
+ HalfEdge::Face * face = m_unifiedMesh->addFace();
+ face->setEdge(startEdge);
+
+ edge = startEdge;
+ do {
+ edge->setFace(face);
+
+ edge = edge->next();
+ } while(edge != startEdge);
+
+ // @@ Implement the above using addFace, it should now work with existing edges, as long as their face pointers is zero.
+
+ }
+ else {
+ // Ideally we should:
+ // - compute best fit plane of boundary vertices.
+ // - project boundary polygon onto plane.
+ // - triangulate boundary polygon.
+ // - add faces of the resulting triangulation.
+
+ // I don't have a good triangulator available. A more simple solution that works in more (but not all) cases:
+ // - compute boundary centroid.
+ // - add vertex centroid.
+ // - connect centroid vertex with boundary vertices.
+ // - connect radial edges with boundary edges.
+
+ // This should work for non-convex boundaries with colinear vertices as long as the kernel of the polygon is not empty.
+
+ // Compute boundary centroid:
+ Vector3 centroid_pos(0);
+ Vector2 centroid_tex(0);
+
+ HalfEdge::Edge * edge = startEdge;
+ do {
+ centroid_pos += edge->vertex()->pos;
+ centroid_tex += edge->vertex()->tex;
+ edge = edge->next();
+ } while(edge != startEdge);
+
+ centroid_pos *= (1.0f / edgeCount);
+ centroid_tex *= (1.0f / edgeCount);
+
+ HalfEdge::Vertex * centroid = m_unifiedMesh->addVertex(centroid_pos);
+ centroid->tex = centroid_tex;
+
+ // Add one pair of edges for each boundary vertex.
+ edge = startEdge;
+ do {
+ HalfEdge::Edge * next = edge->next();
+
+ nvCheck(edge->face() == NULL);
+ HalfEdge::Face * face = m_unifiedMesh->addFace(centroid->id(), edge->from()->id(), edge->to()->id());
+
+ if (face != NULL) {
+ nvCheck(edge->face() == face);
+ }
+ else {
+ hasNewHoles = true;
+ }
+
+ edge = next;
+ } while(edge != startEdge);
+ }
+ */
+ }
+
+ /*nvDebugCheck(!hasNewHoles);
+
+ if (hasNewHoles) {
+ // Link boundary again, in case closeHoles created new holes!
+ m_unifiedMesh->linkBoundary();
+ }*/
+
+ // Because some algorithms do not expect sparse edge buffers.
+ //m_unifiedMesh->compactEdges();
+
+ // In case we messed up:
+ //m_unifiedMesh->linkBoundary();
+
+ getBoundaryEdges(m_unifiedMesh.ptr(), boundaryEdges);
+
+ boundaryCount = boundaryEdges.count();
+ nvDebugCheck(boundaryCount == 1);
+
+ //exportMesh(m_unifiedMesh.ptr(), "debug_hole_filled.obj");
+
+ return boundaryCount == 1;
+}
+
+
+// Transfer parameterization from unified mesh to chart mesh.
+void Chart::transferParameterization() {
+ nvDebugCheck(!m_isVertexMapped);
+
+ uint vertexCount = m_chartMesh->vertexCount();
+ for (uint 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 Chart::computeSurfaceArea() const {
+ return nv::computeSurfaceArea(m_chartMesh.ptr()) * scale;
+}
+
+float Chart::computeParametricArea() const {
+ // This only makes sense in parameterized meshes.
+ nvDebugCheck(m_isDisk);
+ nvDebugCheck(!m_isVertexMapped);
+
+ return nv::computeParametricArea(m_chartMesh.ptr());
+}
+
+Vector2 Chart::computeParametricBounds() const {
+ // This only makes sense in parameterized meshes.
+ nvDebugCheck(m_isDisk);
+ nvDebugCheck(!m_isVertexMapped);
+
+ Box bounds;
+ bounds.clearBounds();
+
+ uint vertexCount = m_chartMesh->vertexCount();
+ for (uint v = 0; v < vertexCount; v++) {
+ HalfEdge::Vertex * vertex = m_chartMesh->vertexAt(v);
+ bounds.addPointToBounds(Vector3(vertex->tex, 0));
+ }
+
+ return bounds.extents().xy();
+}
diff --git a/thirdparty/thekla_atlas/nvmesh/param/Atlas.h b/thirdparty/thekla_atlas/nvmesh/param/Atlas.h
new file mode 100644
index 0000000000..8b478207e3
--- /dev/null
+++ b/thirdparty/thekla_atlas/nvmesh/param/Atlas.h
@@ -0,0 +1,183 @@
+// Copyright NVIDIA Corporation 2006 -- Ignacio Castano <icastano@nvidia.com>
+
+#pragma once
+#ifndef NV_MESH_ATLAS_H
+#define NV_MESH_ATLAS_H
+
+#include "nvcore/Array.h"
+#include "nvcore/Ptr.h"
+#include "nvmath/Vector.h"
+#include "nvmesh/nvmesh.h"
+#include "nvmesh/halfedge/Mesh.h"
+
+
+namespace nv
+{
+ namespace HalfEdge { class Mesh; }
+
+ class Chart;
+ class MeshCharts;
+ class VertexMap;
+
+ struct SegmentationSettings
+ {
+ SegmentationSettings();
+
+ float maxChartArea;
+ float maxBoundaryLength;
+
+ float proxyFitMetricWeight;
+ float roundnessMetricWeight;
+ float straightnessMetricWeight;
+ float normalSeamMetricWeight;
+ float textureSeamMetricWeight;
+ };
+
+
+ /// An atlas is a set of charts.
+ class Atlas
+ {
+ public:
+
+ Atlas();
+ ~Atlas();
+
+ uint meshCount() const { return m_meshChartsArray.count(); }
+ const MeshCharts * meshAt(uint i) const { return m_meshChartsArray[i]; }
+ MeshCharts * meshAt(uint i) { return m_meshChartsArray[i]; }
+
+ uint chartCount() const;
+ const Chart * chartAt(uint i) const;
+ Chart * chartAt(uint i);
+
+ // Add mesh charts and takes ownership.
+ void addMeshCharts(MeshCharts * meshCharts);
+
+ void extractCharts(const HalfEdge::Mesh * mesh);
+ void computeCharts(const HalfEdge::Mesh * mesh, const SegmentationSettings & settings, const Array<uint> & unchartedMaterialArray);
+
+
+ // Compute a trivial seamless texture similar to ZBrush.
+ //bool computeSeamlessTextureAtlas(bool groupFaces = true, bool scaleTiles = false, uint w = 1024, uint h = 1024);
+
+ void parameterizeCharts();
+
+ // Pack charts in the smallest possible rectangle.
+ float packCharts(int quality, float texelArea, bool blockAlign, bool conservative);
+
+ private:
+
+ Array<MeshCharts *> m_meshChartsArray;
+
+ };
+
+
+ // Set of charts corresponding to a single mesh.
+ class MeshCharts
+ {
+ public:
+ MeshCharts(const HalfEdge::Mesh * mesh);
+ ~MeshCharts();
+
+ uint chartCount() const { return m_chartArray.count(); }
+ uint vertexCount () const { return m_totalVertexCount; }
+
+ const Chart * chartAt(uint i) const { return m_chartArray[i]; }
+ Chart * chartAt(uint i) { return m_chartArray[i]; }
+
+ void computeVertexMap(const Array<uint> & unchartedMaterialArray);
+
+ // Extract the charts of the input mesh.
+ void extractCharts();
+
+ // Compute charts using a simple segmentation algorithm.
+ void computeCharts(const SegmentationSettings & settings, const Array<uint> & unchartedMaterialArray);
+
+ void parameterizeCharts();
+
+ uint faceChartAt(uint i) const { return m_faceChart[i]; }
+ uint faceIndexWithinChartAt(uint i) const { return m_faceIndex[i]; }
+
+ uint vertexCountBeforeChartAt(uint i) const { return m_chartVertexCountPrefixSum[i]; }
+
+ private:
+
+ const HalfEdge::Mesh * m_mesh;
+
+ Array<Chart *> m_chartArray;
+
+ Array<uint> m_chartVertexCountPrefixSum;
+ uint m_totalVertexCount;
+
+ Array<uint> m_faceChart; // the chart of every face of the input mesh.
+ Array<uint> m_faceIndex; // the index within the chart for every face of the input mesh.
+ };
+
+
+ /// A chart is a connected set of faces with a certain topology (usually a disk).
+ class Chart
+ {
+ public:
+
+ Chart();
+
+ void build(const HalfEdge::Mesh * originalMesh, const Array<uint> & faceArray);
+ void buildVertexMap(const HalfEdge::Mesh * originalMesh, const Array<uint> & unchartedMaterialArray);
+
+ bool closeHoles();
+
+ bool isDisk() const { return m_isDisk; }
+ bool isVertexMapped() const { return m_isVertexMapped; }
+
+ uint vertexCount() const { return m_chartMesh->vertexCount(); }
+ uint colocalVertexCount() const { return m_unifiedMesh->vertexCount(); }
+
+ uint faceCount() const { return m_faceArray.count(); }
+ uint faceAt(uint i) const { return m_faceArray[i]; }
+
+ const HalfEdge::Mesh * chartMesh() const { return m_chartMesh.ptr(); }
+ HalfEdge::Mesh * chartMesh() { return m_chartMesh.ptr(); }
+ const HalfEdge::Mesh * unifiedMesh() const { return m_unifiedMesh.ptr(); }
+ HalfEdge::Mesh * unifiedMesh() { return m_unifiedMesh.ptr(); }
+
+ //uint vertexIndex(uint i) const { return m_vertexIndexArray[i]; }
+
+ uint mapChartVertexToOriginalVertex(uint i) const { return m_chartToOriginalMap[i]; }
+ uint mapChartVertexToUnifiedVertex(uint i) const { return m_chartToUnifiedMap[i]; }
+
+ const Array<uint> & faceArray() const { return m_faceArray; }
+
+ void transferParameterization();
+
+ float computeSurfaceArea() const;
+ float computeParametricArea() const;
+ Vector2 computeParametricBounds() const;
+
+
+ float scale = 1.0f;
+ uint vertexMapWidth;
+ uint vertexMapHeight;
+
+ private:
+
+ bool closeLoop(uint start, const Array<HalfEdge::Edge *> & loop);
+
+ // Chart mesh.
+ AutoPtr<HalfEdge::Mesh> m_chartMesh;
+ AutoPtr<HalfEdge::Mesh> m_unifiedMesh;
+
+ bool m_isDisk;
+ bool m_isVertexMapped;
+
+ // List of faces of the original mesh that belong to this chart.
+ Array<uint> m_faceArray;
+
+ // Map vertices of the chart mesh to vertices of the original mesh.
+ Array<uint> m_chartToOriginalMap;
+
+ Array<uint> m_chartToUnifiedMap;
+ };
+
+} // nv namespace
+
+#endif // NV_MESH_ATLAS_H
diff --git a/thirdparty/thekla_atlas/nvmesh/param/AtlasBuilder.cpp b/thirdparty/thekla_atlas/nvmesh/param/AtlasBuilder.cpp
new file mode 100644
index 0000000000..bd2140c2f3
--- /dev/null
+++ b/thirdparty/thekla_atlas/nvmesh/param/AtlasBuilder.cpp
@@ -0,0 +1,1320 @@
+// This code is in the public domain -- castano@gmail.com
+
+#include "nvmesh.h" // pch
+
+#include "AtlasBuilder.h"
+#include "Util.h"
+
+#include "nvmesh/halfedge/Mesh.h"
+#include "nvmesh/halfedge/Face.h"
+#include "nvmesh/halfedge/Vertex.h"
+
+#include "nvmath/Matrix.inl"
+#include "nvmath/Vector.inl"
+
+//#include "nvcore/IntroSort.h"
+#include "nvcore/Array.inl"
+
+#include <algorithm> // std::sort
+
+#include <float.h> // FLT_MAX
+#include <limits.h> // UINT_MAX
+
+using namespace nv;
+
+namespace
+{
+
+ // 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(uint size = UINT_MAX) : maxSize(size) {}
+
+ void push(float priority, uint face) {
+ uint i = 0;
+ const uint count = pairs.count();
+ for (; i < count; i++) {
+ if (pairs[i].priority > priority) break;
+ }
+
+ Pair p = { priority, face };
+ pairs.insertAt(i, p);
+
+ if (pairs.count() > maxSize) {
+ pairs.removeAt(0);
+ }
+ }
+
+ // push face out of order, to be sorted later.
+ void push(uint face) {
+ Pair p = { 0.0f, face };
+ pairs.append(p);
+ }
+
+ uint pop() {
+ uint f = pairs.back().face;
+ pairs.pop_back();
+ return f;
+ }
+
+ void sort() {
+ //nv::sort(pairs); // @@ My intro sort appears to be much slower than it should!
+ std::sort(pairs.buffer(), pairs.buffer() + pairs.count());
+ }
+
+ void clear() {
+ pairs.clear();
+ }
+
+ uint count() const { return pairs.count(); }
+
+ float firstPriority() const { return pairs.back().priority; }
+
+
+ const uint maxSize;
+
+ struct Pair {
+ bool operator <(const Pair & p) const { return priority > p.priority; } // !! Sort in inverse priority order!
+ float priority;
+ uint face;
+ };
+
+
+ Array<Pair> pairs;
+ };
+
+ static bool isNormalSeam(const HalfEdge::Edge * edge) {
+ return (edge->vertex->nor != edge->pair->next->vertex->nor || edge->next->vertex->nor != edge->pair->vertex->nor);
+ }
+
+ static bool isTextureSeam(const HalfEdge::Edge * edge) {
+ return (edge->vertex->tex != edge->pair->next->vertex->tex || edge->next->vertex->tex != edge->pair->vertex->tex);
+ }
+
+} // namespace
+
+
+struct nv::ChartBuildData
+{
+ ChartBuildData(int id) : id(id) {
+ planeNormal = Vector3(0);
+ centroid = Vector3(0);
+ coneAxis = Vector3(0);
+ coneAngle = 0;
+ area = 0;
+ boundaryLength = 0;
+ normalSum = Vector3(0);
+ centroidSum = Vector3(0);
+ }
+
+ int id;
+
+ // Proxy info:
+ Vector3 planeNormal;
+ Vector3 centroid;
+ Vector3 coneAxis;
+ float coneAngle;
+
+ float area;
+ float boundaryLength;
+ Vector3 normalSum;
+ Vector3 centroidSum;
+
+ Array<uint> seeds; // @@ These could be a pointers to the HalfEdge faces directly.
+ Array<uint> faces;
+ PriorityQueue candidates;
+};
+
+
+
+AtlasBuilder::AtlasBuilder(const HalfEdge::Mesh * m) : mesh(m), facesLeft(m->faceCount())
+{
+ const uint faceCount = m->faceCount();
+ faceChartArray.resize(faceCount, -1);
+ faceCandidateArray.resize(faceCount, -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.
+ uint edgeCount = m->edgeCount();
+ edgeLengths.resize(edgeCount);
+
+ for (uint i = 0; i < edgeCount; i++) {
+ uint id = m->edgeAt(i)->id;
+ nvDebugCheck(id / 2 == i);
+
+ edgeLengths[i] = m->edgeAt(i)->length();
+ }
+
+ faceAreas.resize(faceCount);
+ for (uint i = 0; i < faceCount; i++) {
+ faceAreas[i] = m->faceAt(i)->area();
+ }
+}
+
+AtlasBuilder::~AtlasBuilder()
+{
+ const uint chartCount = chartArray.count();
+ for (uint i = 0; i < chartCount; i++)
+ {
+ delete chartArray[i];
+ }
+}
+
+
+void AtlasBuilder::markUnchartedFaces(const Array<uint> & unchartedFaces)
+{
+ const uint unchartedFaceCount = unchartedFaces.count();
+ for (uint i = 0; i < unchartedFaceCount; i++){
+ uint f = unchartedFaces[i];
+ faceChartArray[f] = -2;
+ //faceCandidateArray[f] = -2; // @@ ?
+
+ removeCandidate(f);
+ }
+
+ nvDebugCheck(facesLeft >= unchartedFaceCount);
+ facesLeft -= unchartedFaceCount;
+}
+
+
+void AtlasBuilder::computeShortestPaths()
+{
+ const uint faceCount = mesh->faceCount();
+ shortestPaths.resize(faceCount*faceCount, FLT_MAX);
+
+ // Fill edges:
+ for (uint 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;
+
+ uint 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 (uint k = 0; k < faceCount; k++)
+ {
+ for (uint i = 0; i < faceCount; i++)
+ {
+ for (uint j = 0; j < faceCount; j++)
+ {
+ shortestPaths[i*faceCount + j] = min(shortestPaths[i*faceCount + j], shortestPaths[i*faceCount + k]+shortestPaths[k*faceCount + j]);
+ }
+ }
+ }
+}
+
+
+void AtlasBuilder::placeSeeds(float threshold, uint 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 (uint i = 0; i < maxSeedCount; i++)
+ {
+ if (facesLeft == 0) {
+ // No faces left, stop creating seeds.
+ break;
+ }
+
+ createRandomChart(threshold);
+ }
+}
+
+
+void AtlasBuilder::createRandomChart(float threshold)
+{
+ ChartBuildData * chart = new ChartBuildData(chartArray.count());
+ chartArray.append(chart);
+
+ // Pick random face that is not used by any chart yet.
+ uint randomFaceIdx = rand.getRange(facesLeft - 1);
+ uint i = 0;
+ for (uint f = 0; f != randomFaceIdx; f++, i++)
+ {
+ while (faceChartArray[i] != -1) i++;
+ }
+ while (faceChartArray[i] != -1) i++;
+
+ chart->seeds.append(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 AtlasBuilder::addFaceToChart(ChartBuildData * chart, uint f, bool recomputeProxy)
+{
+ // Add face to chart.
+ chart->faces.append(f);
+
+ nvDebugCheck(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);
+}
+
+// @@ Get N best candidates in one pass.
+const AtlasBuilder::Candidate & AtlasBuilder::getBestCandidate() const
+{
+ uint best = 0;
+ float bestCandidateMetric = FLT_MAX;
+
+ const uint candidateCount = candidateArray.count();
+ nvCheck(candidateCount > 0);
+
+ for (uint i = 0; i < candidateCount; i++)
+ {
+ const Candidate & candidate = candidateArray[i];
+
+ if (candidate.metric < bestCandidateMetric) {
+ bestCandidateMetric = candidate.metric;
+ best = i;
+ }
+ }
+
+ return candidateArray[best];
+}
+
+
+// Returns true if any of the charts can grow more.
+bool AtlasBuilder::growCharts(float threshold, uint faceCount)
+{
+#if 1 // Using one global list.
+
+ faceCount = min(faceCount, facesLeft);
+
+ for (uint 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.
+
+#else // Using one list per chart.
+ bool canGrowMore = false;
+
+ const uint chartCount = chartArray.count();
+ for (uint i = 0; i < chartCount; i++)
+ {
+ if (growChart(chartArray[i], threshold, faceCount))
+ {
+ canGrowMore = true;
+ }
+ }
+
+ return canGrowMore;
+#endif
+}
+
+bool AtlasBuilder::growChart(ChartBuildData * chart, float threshold, uint faceCount)
+{
+ // Try to add faceCount faces within threshold to chart.
+ for (uint i = 0; i < faceCount; )
+ {
+ if (chart->candidates.count() == 0 || chart->candidates.firstPriority() > threshold)
+ {
+ return false;
+ }
+
+ uint 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 AtlasBuilder::resetCharts()
+{
+ const uint faceCount = mesh->faceCount();
+ for (uint i = 0; i < faceCount; i++)
+ {
+ faceChartArray[i] = -1;
+ faceCandidateArray[i] = -1;
+ }
+
+ facesLeft = faceCount;
+
+ candidateArray.clear();
+
+ const uint chartCount = chartArray.count();
+ for (uint i = 0; i < chartCount; i++)
+ {
+ ChartBuildData * chart = chartArray[i];
+
+ const uint 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 AtlasBuilder::updateCandidates(ChartBuildData * chart, uint 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())
+ {
+ uint f = edge->face->id;
+
+ if (faceChartArray[f] == -1)
+ {
+ chart->candidates.push(f);
+ }
+ }
+ }
+}
+
+
+void AtlasBuilder::updateProxies()
+{
+ const uint chartCount = chartArray.count();
+ for (uint i = 0; i < chartCount; i++)
+ {
+ updateProxy(chartArray[i]);
+ }
+}
+
+
+namespace {
+
+ float absoluteSum(Vector4::Arg v)
+ {
+ return fabs(v.x) + fabs(v.y) + fabs(v.z) + fabs(v.w);
+ }
+
+ //#pragma message(NV_FILE_LINE "FIXME: Using the c=cos(teta) substitution, the equation system becomes linear and we can avoid the newton solver.")
+
+ struct ConeFitting
+ {
+ ConeFitting(const HalfEdge::Mesh * m, float g, float tf, float tx) : mesh(m), gamma(g), tolf(tf), tolx(tx), F(0), D(0), H(0) {
+ }
+
+ void addTerm(Vector3 N, float A)
+ {
+ const float c = cosf(X.w);
+ const float s = sinf(X.w);
+ const float tmp = dot(X.xyz(), N) - c;
+
+ F += tmp * tmp;
+
+ D.x += 2 * X.x * tmp;
+ D.y += 2 * X.y * tmp;
+ D.z += 2 * X.z * tmp;
+ D.w += 2 * s * tmp;
+
+ H(0,0) = 2 * X.x * N.x + 2 * tmp;
+ H(0,1) = 2 * X.x * N.y;
+ H(0,2) = 2 * X.x * N.z;
+ H(0,3) = 2 * X.x * s;
+
+ H(1,0) = 2 * X.y * N.x;
+ H(1,1) = 2 * X.y * N.y + 2 * tmp;
+ H(1,2) = 2 * X.y * N.z;
+ H(1,3) = 2 * X.y * s;
+
+ H(2,0) = 2 * X.z * N.x;
+ H(2,1) = 2 * X.z * N.y;
+ H(2,2) = 2 * X.z * N.z + 2 * tmp;
+ H(2,3) = 2 * X.z * s;
+
+ H(3,0) = 2 * s * N.x;
+ H(3,1) = 2 * s * N.y;
+ H(3,2) = 2 * s * N.z;
+ H(3,3) = 2 * s * s + 2 * c * tmp;
+ }
+
+ Vector4 solve(ChartBuildData * chart, Vector4 start)
+ {
+ const uint faceCount = chart->faces.count();
+
+ X = start;
+
+ Vector4 dX;
+
+ do {
+ for (uint i = 0; i < faceCount; i++)
+ {
+ const HalfEdge::Face * face = mesh->faceAt(chart->faces[i]);
+
+ addTerm(face->normal(), face->area());
+ }
+
+ Vector4 dX;
+ //solveKramer(H, D, &dX);
+ solveLU(H, D, &dX);
+
+ // @@ Do a full newton step and reduce by half if F doesn't decrease.
+ X -= gamma * dX;
+
+ // Constrain normal to be normalized.
+ X = Vector4(normalize(X.xyz()), X.w);
+
+ } while(absoluteSum(D) > tolf || absoluteSum(dX) > tolx);
+
+ return X;
+ }
+
+ HalfEdge::Mesh const * const mesh;
+ const float gamma;
+ const float tolf;
+ const float tolx;
+
+ Vector4 X;
+
+ float F;
+ Vector4 D;
+ Matrix H;
+ };
+
+ // Unnormalized face normal assuming it's a triangle.
+ static Vector3 triangleNormal(const HalfEdge::Face * face)
+ {
+ Vector3 p0 = face->edge->vertex->pos;
+ Vector3 p1 = face->edge->next->vertex->pos;
+ Vector3 p2 = face->edge->next->next->vertex->pos;
+
+ Vector3 e0 = p2 - p0;
+ Vector3 e1 = p1 - p0;
+
+ return normalizeSafe(cross(e0, e1), Vector3(0), 0.0f);
+ }
+
+ static Vector3 triangleNormalAreaScaled(const HalfEdge::Face * face)
+ {
+ Vector3 p0 = face->edge->vertex->pos;
+ Vector3 p1 = face->edge->next->vertex->pos;
+ Vector3 p2 = face->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.
+ static Vector3 triangleCenter(const HalfEdge::Face * face)
+ {
+ Vector3 p0 = face->edge->vertex->pos;
+ Vector3 p1 = face->edge->next->vertex->pos;
+ Vector3 p2 = face->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;
+ }
+
+} // namespace
+
+void AtlasBuilder::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.count());
+
+ //#pragma message(NV_FILE_LINE "TODO: Experiment with conic fitting.")
+
+ // F = (Nc*Nt - cos Oc)^2 = (x*Nt_x + y*Nt_y + z*Nt_z - cos w)^2
+ // dF/dx = 2 * x * (x*Nt_x + y*Nt_y + z*Nt_z - cos w)
+ // dF/dy = 2 * y * (x*Nt_x + y*Nt_y + z*Nt_z - cos w)
+ // dF/dz = 2 * z * (x*Nt_x + y*Nt_y + z*Nt_z - cos w)
+ // dF/dw = 2 * sin w * (x*Nt_x + y*Nt_y + z*Nt_z - cos w)
+
+ // JacobianMatrix({
+ // 2 * x * (x*Nt_x + y*Nt_y + z*Nt_z - Cos(w)),
+ // 2 * y * (x*Nt_x + y*Nt_y + z*Nt_z - Cos(w)),
+ // 2 * z * (x*Nt_x + y*Nt_y + z*Nt_z - Cos(w)),
+ // 2 * Sin(w) * (x*Nt_x + y*Nt_y + z*Nt_z - Cos(w))}, {x,y,z,w})
+
+ // H[0,0] = 2 * x * Nt_x + 2 * (x*Nt_x + y*Nt_y + z*Nt_z - cos(w));
+ // H[0,1] = 2 * x * Nt_y;
+ // H[0,2] = 2 * x * Nt_z;
+ // H[0,3] = 2 * x * sin(w);
+
+ // H[1,0] = 2 * y * Nt_x;
+ // H[1,1] = 2 * y * Nt_y + 2 * (x*Nt_x + y*Nt_y + z*Nt_z - cos(w));
+ // H[1,2] = 2 * y * Nt_z;
+ // H[1,3] = 2 * y * sin(w);
+
+ // H[2,0] = 2 * z * Nt_x;
+ // H[2,1] = 2 * z * Nt_y;
+ // H[2,2] = 2 * z * Nt_z + 2 * (x*Nt_x + y*Nt_y + z*Nt_z - cos(w));
+ // H[2,3] = 2 * z * sin(w);
+
+ // H[3,0] = 2 * sin(w) * Nt_x;
+ // H[3,1] = 2 * sin(w) * Nt_y;
+ // H[3,2] = 2 * sin(w) * Nt_z;
+ // H[3,3] = 2 * sin(w) * sin(w) + 2 * cos(w) * (x*Nt_x + y*Nt_y + z*Nt_z - cos(w));
+
+ // @@ Cone fitting might be quite slow.
+
+ /*ConeFitting coneFitting(mesh, 0.1f, 0.001f, 0.001f);
+
+ Vector4 start = Vector4(chart->coneAxis, chart->coneAngle);
+ Vector4 solution = coneFitting.solve(chart, start);
+
+ chart->coneAxis = solution.xyz();
+ chart->coneAngle = solution.w;*/
+}
+
+
+
+bool AtlasBuilder::relocateSeeds()
+{
+ bool anySeedChanged = false;
+
+ const uint chartCount = chartArray.count();
+ for (uint i = 0; i < chartCount; i++)
+ {
+ if (relocateSeed(chartArray[i]))
+ {
+ anySeedChanged = true;
+ }
+ }
+
+ return anySeedChanged;
+}
+
+
+bool AtlasBuilder::relocateSeed(ChartBuildData * chart)
+{
+ Vector3 centroid = computeChartCentroid(chart);
+
+ const uint N = 10; // @@ Hardcoded to 10?
+ PriorityQueue bestTriangles(N);
+
+ // Find the first N triangles that fit the proxy best.
+ const uint faceCount = chart->faces.count();
+ for (uint 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.
+ uint mostCentral;
+ float maxDistance = -1;
+
+ const uint bestCount = bestTriangles.count();
+ for (uint i = 0; i < bestCount; i++)
+ {
+ const HalfEdge::Face * face = mesh->faceAt(bestTriangles.pairs[i].face);
+ Vector3 faceCentroid = triangleCenter(face);
+
+ float distance = length(centroid - faceCentroid);
+
+ /*#pragma message(NV_FILE_LINE "TODO: Implement evaluateDistanceToBoundary.")
+ float distance = evaluateDistanceToBoundary(chart, bestTriangles.pairs[i].face);*/
+
+ if (distance > maxDistance)
+ {
+ maxDistance = distance;
+ mostCentral = bestTriangles.pairs[i].face;
+ }
+ }
+ nvDebugCheck(maxDistance >= 0);
+
+ // In order to prevent k-means cyles we record all the previously chosen seeds.
+ uint index;
+ if (chart->seeds.find(mostCentral, &index))
+ {
+ // Move new seed to the end of the seed array.
+ uint last = chart->seeds.count() - 1;
+ swap(chart->seeds[index], chart->seeds[last]);
+ return false;
+ }
+ else
+ {
+ // Append new seed.
+ chart->seeds.append(mostCentral);
+ return true;
+ }
+}
+
+void AtlasBuilder::removeCandidate(uint f)
+{
+ int c = faceCandidateArray[f];
+ if (c != -1) {
+ faceCandidateArray[f] = -1;
+
+ if (c == candidateArray.count() - 1) {
+ candidateArray.popBack();
+ }
+ else {
+ candidateArray.replaceWithLast(c);
+ faceCandidateArray[candidateArray[c].face] = c;
+ }
+ }
+}
+
+void AtlasBuilder::updateCandidate(ChartBuildData * chart, uint f, float metric)
+{
+ if (faceCandidateArray[f] == -1) {
+ const uint index = candidateArray.count();
+ faceCandidateArray[f] = index;
+ candidateArray.resize(index + 1);
+ candidateArray[index].face = f;
+ candidateArray[index].chart = chart;
+ candidateArray[index].metric = metric;
+ }
+ else {
+ int c = faceCandidateArray[f];
+ nvDebugCheck(c != -1);
+
+ Candidate & candidate = candidateArray[c];
+ nvDebugCheck(candidate.face == f);
+
+ if (metric < candidate.metric || chart == candidate.chart) {
+ candidate.metric = metric;
+ candidate.chart = chart;
+ }
+ }
+
+}
+
+
+void AtlasBuilder::updatePriorities(ChartBuildData * chart)
+{
+ // Re-evaluate candidate priorities.
+ uint candidateCount = chart->candidates.count();
+ for (uint 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 AtlasBuilder::evaluatePriority(ChartBuildData * chart, uint 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(
+ settings.proxyFitMetricWeight * F +
+ settings.roundnessMetricWeight * C +
+ settings.straightnessMetricWeight * P +
+ settings.normalSeamMetricWeight * N +
+ settings.textureSeamMetricWeight * T);
+
+ /*cost = settings.proxyFitMetricWeight * powf(F, settings.proxyFitMetricExponent);
+ cost = max(cost, settings.roundnessMetricWeight * powf(C, settings.roundnessMetricExponent));
+ cost = max(cost, settings.straightnessMetricWeight * pow(P, settings.straightnessMetricExponent));
+ cost = max(cost, settings.normalSeamMetricWeight * N);
+ cost = max(cost, settings.textureSeamMetricWeight * T);*/
+
+ // Enforce limits strictly:
+ if (newChartArea > settings.maxChartArea) cost = FLT_MAX;
+ if (newBoundaryLength > settings.maxBoundaryLength) cost = FLT_MAX;
+
+ // Make sure normal seams are fully respected:
+ if (settings.normalSeamMetricWeight >= 1000 && N != 0) cost = FLT_MAX;
+
+ nvCheck(isFinite(cost));
+ return cost;
+}
+
+
+// Returns a value in [0-1].
+float AtlasBuilder::evaluateProxyFitMetric(ChartBuildData * chart, uint f)
+{
+ const HalfEdge::Face * face = mesh->faceAt(f);
+ Vector3 faceNormal = triangleNormal(face);
+ //return square(dot(chart->coneAxis, faceNormal) - cosf(chart->coneAngle));
+
+ // Use plane fitting metric for now:
+ //return square(1 - dot(faceNormal, chart->planeNormal)); // @@ normal deviations should be weighted by face area
+ return 1 - dot(faceNormal, chart->planeNormal); // @@ normal deviations should be weighted by face area
+
+ // Find distance to chart.
+ /*Vector3 faceCentroid = face->centroid();
+
+ float dist = 0;
+ int count = 0;
+
+ for (HalfEdge::Face::ConstEdgeIterator it(face->edges()); !it.isDone(); it.advance())
+ {
+ const HalfEdge::Edge * edge = it.current();
+
+ if (!edge->isBoundary()) {
+ const HalfEdge::Face * neighborFace = edge->pair()->face();
+ if (faceChartArray[neighborFace->id()] == chart->id) {
+ dist += length(neighborFace->centroid() - faceCentroid);
+ count++;
+ }
+ }
+ }
+
+ dist /= (count * count);
+
+ return (1 - dot(faceNormal, chart->planeNormal)) * dist;*/
+
+ //return (1 - dot(faceNormal, chart->planeNormal));
+}
+
+float AtlasBuilder::evaluateDistanceToBoundary(ChartBuildData * chart, uint face)
+{
+//#pragma message(NV_FILE_LINE "TODO: Evaluate distance to boundary metric.")
+
+ // @@ This is needed for the seed relocation code.
+ // @@ This could provide a better roundness metric.
+
+ return 0.0f;
+}
+
+float AtlasBuilder::evaluateDistanceToSeed(ChartBuildData * chart, uint f)
+{
+ //const uint seed = chart->seeds.back();
+ //const uint faceCount = mesh->faceCount();
+ //return shortestPaths[seed * faceCount + f];
+
+ const HalfEdge::Face * seed = mesh->faceAt(chart->seeds.back());
+ const HalfEdge::Face * face = mesh->faceAt(f);
+ return length(triangleCenter(seed) - triangleCenter(face));
+}
+
+
+float AtlasBuilder::evaluateRoundnessMetric(ChartBuildData * chart, uint face, float newBoundaryLength, float newChartArea)
+{
+ // @@ D-charts use distance to seed.
+ // C(c,t) = pi * D(S_c,t)^2 / A_c
+ //return PI * square(evaluateDistanceToSeed(chart, face)) / chart->area;
+ //return PI * square(evaluateDistanceToSeed(chart, face)) / chart->area;
+ //return 2 * PI * evaluateDistanceToSeed(chart, face) / chart->boundaryLength;
+
+ // Garland's Hierarchical Face Clustering paper uses ratio between boundary and area, which is easier to compute and might work as well:
+ // roundness = D^2/4*pi*A -> circle = 1, non circle greater than 1
+
+ //return square(newBoundaryLength) / (newChartArea * 4 * PI);
+ 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;
+ }
+
+ //return square(newBoundaryLength) / (4 * PI * newChartArea);
+ //return clamp(1 - (4 * PI * newChartArea) / square(newBoundaryLength), 0.0f, 1.0f);
+
+ // Use the ratio between the new roundness vs. the previous roundness.
+ // - If we use the absolute metric, when the initial face is very long, then it's hard to make any progress.
+ //return (square(newBoundaryLength) * chart->area) / (square(chart->boundaryLength) * newChartArea);
+ //return (4 * PI * newChartArea) / square(newBoundaryLength) - (4 * PI * chart->area) / square(chart->boundaryLength);
+
+ //if (square(newBoundaryLength) * chart->area) / (square(chart->boundaryLength) * newChartArea);
+
+}
+
+float AtlasBuilder::evaluateStraightnessMetric(ChartBuildData * chart, uint 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 = edge->length();
+ float l = edgeLengths[edge->id/2];
+
+ if (edge->isBoundary())
+ {
+ l_out += l;
+ }
+ else
+ {
+ uint neighborFaceId = edge->pair->face->id;
+ if (faceChartArray[neighborFaceId] != chart->id) {
+ l_out += l;
+ }
+ else {
+ l_in += l;
+ }
+ }
+ }
+ nvDebugCheck(l_in != 0.0f); // Candidate face must be adjacent to chart. @@ This is not true if the input mesh has zero-length edges.
+
+ //return l_out / l_in;
+ float ratio = (l_out - l_in) / (l_out + l_in);
+ //if (ratio < 0) ratio *= 10; // Encourage closing gaps.
+ return min(ratio, 0.0f); // Only use the straightness metric to close gaps.
+ //return ratio;
+}
+
+
+float AtlasBuilder::evaluateNormalSeamMetric(ChartBuildData * chart, uint 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 uint 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 (isNormalSeam(edge))
+ {
+ 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);
+ //float a0 = clamp(acosf(d0) / (PI/2), 0.0f, 1.0f);
+ //float a1 = clamp(acosf(d1) / (PI/2), 0.0f, 1.0f);
+ //l *= (a0 + a1) * 0.5f;
+
+ l *= 1 - (d0 + d1) * 0.5f;
+
+ seamFactor += l;
+ }
+ }
+
+ if (seamFactor == 0) return 0.0f;
+ return seamFactor / totalLength;
+}
+
+
+float AtlasBuilder::evaluateTextureSeamMetric(ChartBuildData * chart, uint f)
+{
+ float seamLength = 0.0f;
+ //float newSeamLength = 0.0f;
+ //float oldSeamLength = 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();
+
+ /*float l = edge->length();
+ totalLength += l;
+
+ if (edge->isBoundary() || !edge->isSeam()) {
+ continue;
+ }
+
+ // Make sure it's a texture seam.
+ if (isTextureSeam(edge))
+ {
+ uint neighborFaceId = edge->pair()->face()->id();
+ if (faceChartArray[neighborFaceId] != chart->id) {
+ newSeamLength += l;
+ }
+ else {
+ oldSeamLength += l;
+ }
+ }*/
+
+ if (edge->isBoundary()) {
+ continue;
+ }
+
+ const uint 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 (isTextureSeam(edge))
+ {
+ seamLength += l;
+ }
+ }
+
+ if (seamLength == 0.0f) {
+ return 0.0f; // Avoid division by zero.
+ }
+
+ return seamLength / totalLength;
+}
+
+
+float AtlasBuilder::evaluateSeamMetric(ChartBuildData * chart, uint f)
+{
+ float newSeamLength = 0.0f;
+ float oldSeamLength = 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();
+
+ //float l = edge->length();
+ float l = edgeLengths[edge->id/2];
+
+ if (edge->isBoundary())
+ {
+ newSeamLength += l;
+ }
+ else
+ {
+ if (edge->isSeam())
+ {
+ uint neighborFaceId = edge->pair->face->id;
+ if (faceChartArray[neighborFaceId] != chart->id) {
+ newSeamLength += l;
+ }
+ else {
+ oldSeamLength += l;
+ }
+ }
+ }
+
+ totalLength += l;
+ }
+
+ return (newSeamLength - oldSeamLength) / totalLength;
+}
+
+
+float AtlasBuilder::evaluateChartArea(ChartBuildData * chart, uint f)
+{
+ const HalfEdge::Face * face = mesh->faceAt(f);
+ //return chart->area + face->area();
+ return chart->area + faceAreas[face->id];
+}
+
+
+float AtlasBuilder::evaluateBoundaryLength(ChartBuildData * chart, uint 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
+ {
+ uint neighborFaceId = edge->pair->face->id;
+ if (faceChartArray[neighborFaceId] != chart->id) {
+ boundaryLength += edgeLength;
+ }
+ else {
+ boundaryLength -= edgeLength;
+ }
+ }
+ }
+ //nvDebugCheck(boundaryLength >= 0);
+
+ return max(0.0f, boundaryLength); // @@ Hack!
+}
+
+Vector3 AtlasBuilder::evaluateChartNormalSum(ChartBuildData * chart, uint f)
+{
+ const HalfEdge::Face * face = mesh->faceAt(f);
+ return chart->normalSum + triangleNormalAreaScaled(face);
+}
+
+Vector3 AtlasBuilder::evaluateChartCentroidSum(ChartBuildData * chart, uint f)
+{
+ const HalfEdge::Face * face = mesh->faceAt(f);
+ return chart->centroidSum + face->centroid();
+}
+
+
+Vector3 AtlasBuilder::computeChartCentroid(const ChartBuildData * chart)
+{
+ Vector3 centroid(0);
+
+ const uint faceCount = chart->faces.count();
+ for (uint i = 0; i < faceCount; i++)
+ {
+ const HalfEdge::Face * face = mesh->faceAt(chart->faces[i]);
+ centroid += triangleCenter(face);
+ }
+
+ return centroid / float(faceCount);
+}
+
+
+void AtlasBuilder::fillHoles(float threshold)
+{
+ while (facesLeft > 0)
+ {
+ createRandomChart(threshold);
+ }
+}
+
+
+void AtlasBuilder::mergeChart(ChartBuildData * owner, ChartBuildData * chart, float sharedBoundaryLength)
+{
+ const uint faceCount = chart->faces.count();
+ for (uint i = 0; i < faceCount; i++)
+ {
+ uint f = chart->faces[i];
+
+ nvDebugCheck(faceChartArray[f] == chart->id);
+ faceChartArray[f] = owner->id;
+
+ owner->faces.append(f);
+ }
+
+ // Update adjacencies?
+
+ owner->area += chart->area;
+ owner->boundaryLength += chart->boundaryLength - sharedBoundaryLength;
+
+ owner->normalSum += chart->normalSum;
+ owner->centroidSum += chart->centroidSum;
+
+ updateProxy(owner);
+}
+
+void AtlasBuilder::mergeCharts()
+{
+ Array<float> sharedBoundaryLengths;
+
+ const uint chartCount = chartArray.count();
+ for (int c = chartCount-1; c >= 0; c--)
+ {
+ sharedBoundaryLengths.clear();
+ sharedBoundaryLengths.resize(chartCount, 0.0f);
+
+ ChartBuildData * chart = chartArray[c];
+
+ float externalBoundary = 0.0f;
+
+ const uint faceCount = chart->faces.count();
+ for (uint i = 0; i < faceCount; i++)
+ {
+ uint 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 {
+ uint neighborFace = edge->pair->face->id;
+ uint neighborChart = faceChartArray[neighborFace];
+
+ if (neighborChart != c) {
+ if ((edge->isSeam() && (isNormalSeam(edge) || isTextureSeam(edge))) || 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 * 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 * 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 < I32(chartArray.count()); /*do not increment if removed*/)
+ {
+ if (chartArray[c] == NULL) {
+ chartArray.removeAt(c);
+
+ // Update faceChartArray.
+ const uint faceCount = faceChartArray.count();
+ for (uint i = 0; i < faceCount; i++) {
+ nvDebugCheck (faceChartArray[i] != -1);
+ nvDebugCheck (faceChartArray[i] != c);
+ nvDebugCheck (faceChartArray[i] <= I32(chartArray.count()));
+
+ if (faceChartArray[i] > c) {
+ faceChartArray[i]--;
+ }
+ }
+ }
+ else {
+ chartArray[c]->id = c;
+ c++;
+ }
+ }
+}
+
+
+
+const Array<uint> & AtlasBuilder::chartFaces(uint i) const
+{
+ return chartArray[i]->faces;
+}
diff --git a/thirdparty/thekla_atlas/nvmesh/param/AtlasBuilder.h b/thirdparty/thekla_atlas/nvmesh/param/AtlasBuilder.h
new file mode 100644
index 0000000000..f25c724f7e
--- /dev/null
+++ b/thirdparty/thekla_atlas/nvmesh/param/AtlasBuilder.h
@@ -0,0 +1,111 @@
+// This code is in the public domain -- castano@gmail.com
+
+#pragma once
+#ifndef NV_MESH_ATLASBUILDER_H
+#define NV_MESH_ATLASBUILDER_H
+
+#include "Atlas.h"
+
+#include "nvmath/Vector.h"
+#include "nvmath/Random.h"
+#include "nvmesh/nvmesh.h"
+
+#include "nvcore/Array.h"
+#include "nvcore/BitArray.h"
+
+
+
+namespace nv
+{
+ namespace HalfEdge { class Mesh; }
+
+ struct ChartBuildData;
+
+ struct AtlasBuilder
+ {
+ AtlasBuilder(const HalfEdge::Mesh * m);
+ ~AtlasBuilder();
+
+ void markUnchartedFaces(const Array<uint> & unchartedFaces);
+
+ void computeShortestPaths();
+
+ void placeSeeds(float threshold, uint maxSeedCount);
+ void createRandomChart(float threshold);
+
+ void addFaceToChart(ChartBuildData * chart, uint f, bool recomputeProxy=false);
+
+ bool growCharts(float threshold, uint faceCount);
+ bool growChart(ChartBuildData * chart, float threshold, uint faceCount);
+
+ void resetCharts();
+
+ void updateCandidates(ChartBuildData * chart, uint face);
+
+ void updateProxies();
+ void updateProxy(ChartBuildData * chart);
+
+ bool relocateSeeds();
+ bool relocateSeed(ChartBuildData * chart);
+
+ void updatePriorities(ChartBuildData * chart);
+
+ float evaluatePriority(ChartBuildData * chart, uint face);
+ float evaluateProxyFitMetric(ChartBuildData * chart, uint face);
+ float evaluateDistanceToBoundary(ChartBuildData * chart, uint face);
+ float evaluateDistanceToSeed(ChartBuildData * chart, uint face);
+ float evaluateRoundnessMetric(ChartBuildData * chart, uint face, float newBoundaryLength, float newChartArea);
+ float evaluateStraightnessMetric(ChartBuildData * chart, uint face);
+
+ float evaluateNormalSeamMetric(ChartBuildData * chart, uint f);
+ float evaluateTextureSeamMetric(ChartBuildData * chart, uint f);
+ float evaluateSeamMetric(ChartBuildData * chart, uint f);
+
+ float evaluateChartArea(ChartBuildData * chart, uint f);
+ float evaluateBoundaryLength(ChartBuildData * chart, uint f);
+ Vector3 evaluateChartNormalSum(ChartBuildData * chart, uint f);
+ Vector3 evaluateChartCentroidSum(ChartBuildData * chart, uint f);
+
+ Vector3 computeChartCentroid(const ChartBuildData * chart);
+
+
+ void fillHoles(float threshold);
+ void mergeCharts();
+
+ // @@ Cleanup.
+ struct Candidate {
+ uint face;
+ ChartBuildData * chart;
+ float metric;
+ };
+
+ const Candidate & getBestCandidate() const;
+ void removeCandidate(uint f);
+ void updateCandidate(ChartBuildData * chart, uint f, float metric);
+
+ void mergeChart(ChartBuildData * owner, ChartBuildData * chart, float sharedBoundaryLength);
+
+
+ uint chartCount() const { return chartArray.count(); }
+ const Array<uint> & chartFaces(uint i) const;
+
+ const HalfEdge::Mesh * mesh;
+ uint facesLeft;
+ Array<int> faceChartArray;
+ Array<ChartBuildData *> chartArray;
+ Array<float> shortestPaths;
+
+ Array<float> edgeLengths;
+ Array<float> faceAreas;
+
+ Array<Candidate> candidateArray; //
+ Array<uint> faceCandidateArray; // Map face index to candidate index.
+
+ MTRand rand;
+
+ SegmentationSettings settings;
+ };
+
+} // nv namespace
+
+#endif // NV_MESH_ATLASBUILDER_H
diff --git a/thirdparty/thekla_atlas/nvmesh/param/AtlasPacker.cpp b/thirdparty/thekla_atlas/nvmesh/param/AtlasPacker.cpp
new file mode 100644
index 0000000000..f2156899ae
--- /dev/null
+++ b/thirdparty/thekla_atlas/nvmesh/param/AtlasPacker.cpp
@@ -0,0 +1,1379 @@
+// This code is in the public domain -- castano@gmail.com
+
+#include "nvmesh.h" // pch
+
+#include "AtlasPacker.h"
+#include "nvmesh/halfedge/Vertex.h"
+#include "nvmesh/halfedge/Face.h"
+#include "nvmesh/param/Atlas.h"
+#include "nvmesh/param/Util.h"
+#include "nvmesh/raster/Raster.h"
+
+#include "nvmath/Vector.inl"
+#include "nvmath/ConvexHull.h"
+#include "nvmath/Color.h"
+#include "nvmath/ftoi.h"
+
+#include "nvcore/StrLib.h" // debug
+#include "nvcore/StdStream.h" // fileOpen
+
+#include <float.h> // FLT_MAX
+#include <limits.h> // UINT_MAX
+
+using namespace nv;
+
+#define DEBUG_OUTPUT 0
+
+#if DEBUG_OUTPUT
+
+#include "nvimage/ImageIO.h"
+
+namespace
+{
+ const uint TGA_TYPE_GREY = 3;
+ const uint TGA_TYPE_RGB = 2;
+ const uint TGA_ORIGIN_UPPER = 0x20;
+
+#pragma pack(push, 1)
+ struct TgaHeader {
+ uint8 id_length;
+ uint8 colormap_type;
+ uint8 image_type;
+ uint16 colormap_index;
+ uint16 colormap_length;
+ uint8 colormap_size;
+ uint16 x_origin;
+ uint16 y_origin;
+ uint16 width;
+ uint16 height;
+ uint8 pixel_size;
+ uint8 flags;
+
+ enum { Size = 18 }; //const static int SIZE = 18;
+ };
+#pragma pack(pop)
+
+ static void outputDebugBitmap(const char * fileName, const BitMap & bitmap, int w, int h)
+ {
+ FILE * fp = fileOpen(fileName, "wb");
+ if (fp == NULL) return;
+
+ nvStaticCheck(sizeof(TgaHeader) == TgaHeader::Size);
+ TgaHeader tga;
+ tga.id_length = 0;
+ tga.colormap_type = 0;
+ tga.image_type = TGA_TYPE_GREY;
+
+ tga.colormap_index = 0;
+ tga.colormap_length = 0;
+ tga.colormap_size = 0;
+
+ tga.x_origin = 0;
+ tga.y_origin = 0;
+ tga.width = w;
+ tga.height = h;
+ tga.pixel_size = 8;
+ tga.flags = TGA_ORIGIN_UPPER;
+
+ fwrite(&tga, sizeof(TgaHeader), 1, fp);
+
+ for (int j = 0; j < h; j++) {
+ for (int i = 0; i < w; i++) {
+ uint8 color = bitmap.bitAt(i, j) ? 0xFF : 0x0;
+ fwrite(&color, 1, 1, fp);
+ }
+ }
+
+ fclose(fp);
+ }
+
+ static void outputDebugImage(const char * fileName, const Image & bitmap, int w, int h)
+ {
+ FILE * fp = fileOpen(fileName, "wb");
+ if (fp == NULL) return;
+
+ nvStaticCheck(sizeof(TgaHeader) == TgaHeader::Size);
+ TgaHeader tga;
+ tga.id_length = 0;
+ tga.colormap_type = 0;
+ tga.image_type = TGA_TYPE_RGB;
+
+ tga.colormap_index = 0;
+ tga.colormap_length = 0;
+ tga.colormap_size = 0;
+
+ tga.x_origin = 0;
+ tga.y_origin = 0;
+ tga.width = w;
+ tga.height = h;
+ tga.pixel_size = 24;
+ tga.flags = TGA_ORIGIN_UPPER;
+
+ fwrite(&tga, sizeof(TgaHeader), 1, fp);
+
+ for (int j = 0; j < h; j++) {
+ for (int i = 0; i < w; i++) {
+ Color32 color = bitmap.pixel(i, j);
+ fwrite(&color.r, 1, 1, fp);
+ fwrite(&color.g, 1, 1, fp);
+ fwrite(&color.b, 1, 1, fp);
+ }
+ }
+
+ fclose(fp);
+ }
+}
+
+#endif // DEBUG_OUTPUT
+
+inline int align(int x, int a) {
+ //return a * ((x + a - 1) / a);
+ //return (x + a - 1) & -a;
+ return (x + a - 1) & ~(a - 1);
+}
+
+inline bool isAligned(int x, int a) {
+ return (x & (a - 1)) == 0;
+}
+
+
+
+AtlasPacker::AtlasPacker(Atlas * atlas) : m_atlas(atlas), m_bitmap(256, 256)
+{
+ m_width = 0;
+ m_height = 0;
+
+ m_debug_bitmap.allocate(256, 256);
+ m_debug_bitmap.fill(Color32(0,0,0,0));
+}
+
+AtlasPacker::~AtlasPacker()
+{
+}
+
+// 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.
+ Array<Vector2> points(16);
+
+ HalfEdge::Mesh * mesh = chart->chartMesh();
+ const uint vertexCount = mesh->vertexCount();
+
+ for (uint i = 0; i < vertexCount; i++) {
+ HalfEdge::Vertex * vertex = mesh->vertexAt(i);
+ if (vertex->isBoundary()) {
+ points.append(vertex->tex);
+ }
+ }
+
+ // This is not valid anymore. The chart mesh may have multiple boundaries!
+ /*const HalfEdge::Vertex * vertex = findBoundaryVertex(chart->chartMesh());
+
+ // Traverse boundary.
+ const HalfEdge::Edge * const firstEdge = vertex->edge();
+ const HalfEdge::Edge * edge = firstEdge;
+ do {
+ vertex = edge->vertex();
+
+ nvDebugCheck (vertex->isBoundary());
+ points.append(vertex->tex);
+
+ edge = edge->next();
+ } while (edge != firstEdge);*/
+
+#if 1
+ Array<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 uint hullCount = hull.count();
+ for (uint 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);
+ nvDebugCheck(isFinite(axis));
+
+ // Compute bounding box.
+ Vector2 box_min(FLT_MAX, FLT_MAX);
+ Vector2 box_max(-FLT_MAX, -FLT_MAX);
+
+ for (uint 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;
+ }
+ }
+
+ // Make sure the box contains all the input points since the convex hull is not 100% accurate.
+ /*const uint pointCount = points.count();
+ for (uint v = 0; v < pointCount; v++) {
+
+ Vector2 point = points[v];
+
+ float x = dot(best_axis, point);
+ if (x < best_min.x) best_min.x = x;
+
+ float y = dot(Vector2(-best_axis.y, best_axis.x), point);
+ if (y < best_min.y) best_min.y = y;
+ }*/
+
+ // Consider all points, not only boundary points, in case the input chart is malformed.
+ for (uint 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;
+
+#else
+ // Approximate implementation: try 16 different directions and keep the best.
+
+ const uint N = 16;
+ Vector2 axis[N];
+
+ float minAngle = 0;
+ float maxAngle = PI / 2;
+
+ int best;
+ Vector2 mins[N];
+ Vector2 maxs[N];
+
+ const int iterationCount = 1;
+ for (int j = 0; j < iterationCount; j++)
+ {
+ // Init predefined directions.
+ for (int i = 0; i < N; i++)
+ {
+ float angle = lerp(minAngle, maxAngle, float(i)/N);
+ axis[i].set(cosf(angle), sinf(angle));
+ }
+
+ // Compute box for each direction.
+ for (int i = 0; i < N; i++)
+ {
+ mins[i].set(FLT_MAX, FLT_MAX);
+ maxs[i].set(-FLT_MAX, -FLT_MAX);
+ }
+
+ for (uint p = 0; p < points.count(); p++)
+ {
+ Vector2 point = points[p];
+
+ for (int i = 0; i < N; i++)
+ {
+ float x = dot(axis[i], point);
+ if (x < mins[i].x) mins[i].x = x;
+ if (x > maxs[i].x) maxs[i].x = x;
+
+ float y = dot(Vector2(-axis[i].y, axis[i].x), point);
+ if (y < mins[i].y) mins[i].y = y;
+ if (y > maxs[i].y) maxs[i].y = y;
+ }
+ }
+
+ // Find box with minimum area.
+ best = -1;
+ int second_best = -1;
+ float best_area = FLT_MAX;
+ float second_best_area = FLT_MAX;
+
+ for (int i = 0; i < N; i++)
+ {
+ float area = (maxs[i].x - mins[i].x) * (maxs[i].y - mins[i].y);
+
+ if (area < best_area)
+ {
+ second_best_area = best_area;
+ second_best = best;
+
+ best_area = area;
+ best = i;
+ }
+ else if (area < second_best_area)
+ {
+ second_best_area = area;
+ second_best = i;
+ }
+ }
+ nvDebugCheck(best != -1);
+ nvDebugCheck(second_best != -1);
+ nvDebugCheck(best != second_best);
+
+ if (j != iterationCount-1)
+ {
+ // Handle wrap-around during the first iteration.
+ if (j == 0) {
+ if (best == 0 && second_best == N-1) best = N;
+ if (best == N-1 && second_best == 0) second_best = N;
+ }
+
+ if (best < second_best) swap(best, second_best);
+
+ // Update angles.
+ float deltaAngle = (maxAngle - minAngle) / N;
+ maxAngle = minAngle + (best - 0.5f) * deltaAngle;
+ minAngle = minAngle + (second_best + 0.5f) * deltaAngle;
+ }
+ }
+
+ // Compute major and minor axis, and origin.
+ *majorAxis = axis[best];
+ *minorAxis = Vector2(-axis[best].y, axis[best].x);
+ *origin = mins[best];
+
+ // @@ If the parameterization is invalid, we could have an interior vertex outside the boundary.
+ // @@ In that case the returned bounding box would be incorrect. Compute updated bounds here.
+ /*for (uint p = 0; p < points.count(); p++)
+ {
+ Vector2 point = points[p];
+
+ for (int i = 0; i < N; i++)
+ {
+ float x = dot(*majorAxis, point);
+ float y = dot(*minorAxis, point);
+ }
+ }*/
+#endif
+}
+
+
+void AtlasPacker::packCharts(int quality, float texelsPerUnit, bool blockAligned, bool conservative)
+{
+ const uint chartCount = m_atlas->chartCount();
+ if (chartCount == 0) return;
+
+ Array<float> chartOrderArray;
+ chartOrderArray.resize(chartCount);
+
+ Array<Vector2> chartExtents;
+ chartExtents.resize(chartCount);
+
+ float meshArea = 0;
+ for (uint 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()) {
+ // Let's assume vertex maps are arranged in a rectangle.
+ //HalfEdge::Mesh * mesh = chart->chartMesh();
+
+ // 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 = fabs(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;
+ }
+ nvCheck(isFinite(scale));
+
+ // Compute bounding box of chart.
+ Vector2 majorAxis, minorAxis, origin, end;
+ computeBoundingBox(chart, &majorAxis, &minorAxis, &origin, &end);
+
+ nvCheck(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 uint vertexCount = mesh->vertexCount();
+ for (uint 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) {
+ nvDebug("tmp: %f %f\n", tmp.x, tmp.y);
+ nvDebug("scale: %f\n", scale);
+ nvDebug("origin: %f %f\n", origin.x, origin.y);
+ nvDebug("majorAxis: %f %f\n", majorAxis.x, majorAxis.y);
+ nvDebug("minorAxis: %f %f\n", minorAxis.x, minorAxis.y);
+ nvDebugBreak();
+ }
+ //nvCheck(tmp.x >= 0 && tmp.y >= 0);
+
+ vertex->tex = tmp;
+
+ nvCheck(isFinite(vertex->tex.x) && isFinite(vertex->tex.y));
+
+ extents = max(extents, tmp);
+ }
+ nvDebugCheck(extents.x >= 0 && extents.y >= 0);
+
+ // Limit chart size.
+ if (extents.x > 1024 || extents.y > 1024) {
+ float limit = max(extents.x, extents.y);
+
+ scale = 1024 / (limit + 1);
+
+ for (uint i = 0; i < vertexCount; i++)
+ {
+ HalfEdge::Vertex * vertex = mesh->vertexAt(i);
+ vertex->tex *= scale;
+ }
+
+ extents *= scale;
+
+ nvDebugCheck(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 (blockAligned) {
+ // Align all chart extents to 4x4 blocks, but taking padding into account.
+ if (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 (blockAligned) {
+ // Align all chart extents to 4x4 blocks, but taking padding into account.
+ if (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 (uint 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;
+
+ nvCheck(isFinite(vertex->tex.x) && 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.
+
+ //nvDebug("Sorting charts.\n");
+
+ // Sort charts by area.
+ m_radix.sort(chartOrderArray);
+ const uint32 * ranks = m_radix.ranks();
+
+ // Estimate size of the map based on the mesh surface area and given texel scale.
+ float texelCount = meshArea * square(texelsPerUnit) / 0.75f; // Assume 75% utilization.
+ if (texelCount < 1) texelCount = 1;
+ uint approximateExtent = nextPowerOfTwo(uint(sqrtf(texelCount)));
+
+ //nvDebug("Init bitmap.\n");
+
+ // @@ Pack all charts smaller than a texel into a compact rectangle.
+ // @@ Start considering only 1x1 charts. Extend to 1xn charts later.
+
+ /*for (uint i = 0; i < chartCount; i++)
+ {
+ uint c = ranks[chartCount - i - 1]; // largest chart first
+
+ Chart * chart = m_atlas->chartAt(c);
+
+ if (!chart->isDisk()) continue;
+
+ if (iceil(chartExtents[c].x) == 1 && iceil(chartExtents[c].x) == 1) {
+ // @@ Add to
+ }
+ }*/
+
+
+
+ // Init bit map.
+ m_bitmap.clearAll();
+ if (approximateExtent > m_bitmap.width()) {
+ m_bitmap.resize(approximateExtent, approximateExtent, false);
+ m_debug_bitmap.resize(approximateExtent, approximateExtent);
+ m_debug_bitmap.fill(Color32(0,0,0,0));
+ }
+
+
+ int w = 0;
+ int h = 0;
+
+#if 1
+ // Add sorted charts to bitmap.
+ for (uint i = 0; i < chartCount; i++)
+ {
+ uint 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()) {
+ // 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 (conservative) {
+ // Init all bits to 0.
+ chart_bitmap.resize(ftoi_ceil(chartExtents[c].x) + 2, ftoi_ceil(chartExtents[c].y) + 2, /*initValue=*/false); // + 2 to add padding on both sides.
+
+ // Rasterize chart and dilate.
+ drawChartBitmapDilate(chart, &chart_bitmap, /*padding=*/1);
+ }
+ 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(quality, &chart_bitmap, chartExtents[c], w, h, &best_x, &best_y, &best_cw, &best_ch, &best_r);
+
+ /*if (w < best_x + best_cw || h < best_y + best_ch)
+ {
+ nvDebug("Resize extents to (%d, %d).\n", best_x + best_cw, best_y + best_ch);
+ }*/
+
+ // Update parametric extents.
+ w = max(w, best_x + best_cw);
+ h = max(h, best_y + best_ch);
+
+ w = align(w, 4);
+ h = align(h, 4);
+
+ // Resize bitmap if necessary.
+ if (uint(w) > m_bitmap.width() || uint(h) > m_bitmap.height())
+ {
+ //nvDebug("Resize bitmap (%d, %d).\n", nextPowerOfTwo(w), nextPowerOfTwo(h));
+ m_bitmap.resize(nextPowerOfTwo(U32(w)), nextPowerOfTwo(U32(h)), false);
+ m_debug_bitmap.resize(nextPowerOfTwo(U32(w)), nextPowerOfTwo(U32(h)));
+ }
+
+ //nvDebug("Add chart at (%d, %d).\n", best_x, best_y);
+
+ addChart(&chart_bitmap, w, h, best_x, best_y, best_r, /*debugOutput=*/NULL);
+
+ // IC: Output chart again to debug bitmap.
+ if (chart->isVertexMapped()) {
+ addChart(&chart_bitmap, w, h, best_x, best_y, best_r, &m_debug_bitmap);
+ }
+ else {
+ addChart(chart, w, h, best_x, best_y, best_r, &m_debug_bitmap);
+ }
+
+ //float best_angle = 2 * PI * best_r;
+
+ // Translate and rotate chart texture coordinates.
+ HalfEdge::Mesh * mesh = chart->chartMesh();
+ const uint vertexCount = mesh->vertexCount();
+ for (uint v = 0; v < vertexCount; v++)
+ {
+ HalfEdge::Vertex * vertex = mesh->vertexAt(v);
+
+ Vector2 t = vertex->tex;
+ if (best_r) 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;
+
+ nvCheck(vertex->tex.x >= 0 && vertex->tex.y >= 0);
+ nvCheck(isFinite(vertex->tex.x) && isFinite(vertex->tex.y));
+ }
+
+#if DEBUG_OUTPUT && 0
+ StringBuilder fileName;
+ fileName.format("debug_packer_%d.tga", i);
+ //outputDebugBitmap(fileName.str(), m_bitmap, w, h);
+ outputDebugImage(fileName.str(), m_debug_bitmap, w, h);
+#endif
+ }
+
+#else // 0
+
+ // Add sorted charts to bitmap.
+ for (uint i = 0; i < chartCount; i++)
+ {
+ uint c = ranks[chartCount - i - 1]; // largest chart first
+
+ Chart * chart = m_atlas->chartAt(c);
+
+ if (!chart->isDisk()) continue;
+
+ Vector2 scale(1, 1);
+
+#if 0 // old method.
+ //m_padding_x = 2*padding;
+ //m_padding_y = 2*padding;
+#else
+ //m_padding_x = 0; //padding;
+ //m_padding_y = 0; //padding;
+#endif
+
+ int bw = ftoi_ceil(chartExtents[c].x + 1);
+ int bh = ftoi_ceil(chartExtents[c].y + 1);
+
+ if (chartExtents[c].x < 1.0f) {
+ scale.x = 0.01f; // @@ Ideally we would like to scale it to 0, but then our rasterizer would not touch any pixels.
+ bw = 1;
+ }
+ if (chartExtents[c].y < 1.0f) {
+ scale.y = 0.01f;
+ bh = 1;
+ }
+
+ //BitMap chart_bitmap(iceil(chartExtents[c].x) + 1 + m_padding_x * 2, iceil(chartExtents[c].y) + 1 + m_padding_y * 2);
+ //BitMap chart_bitmap(ftoi_ceil(chartExtents[c].x/2)*2, ftoi_ceil(chartExtents[c].y/2)*2);
+ BitMap chart_bitmap(bw, bh);
+ chart_bitmap.clearAll();
+
+ Vector2 offset;
+ offset.x = 0; // (chart_bitmap.width() - chartExtents[c].x) * 0.5f;
+ offset.y = 0; // (chart_bitmap.height() - chartExtents[c].y) * 0.5f;
+
+ drawChartBitmap(chart, &chart_bitmap, scale, offset);
+
+ int best_x, best_y;
+ int best_cw, best_ch;
+ int best_r;
+ findChartLocation(quality, &chart_bitmap, chartExtents[c], w, h, &best_x, &best_y, &best_cw, &best_ch, &best_r);
+
+ /*if (w < best_x + best_cw || h < best_y + best_ch)
+ {
+ nvDebug("Resize extents to (%d, %d).\n", best_x + best_cw, best_y + best_ch);
+ }*/
+
+ // Update parametric extents.
+ w = max(w, best_x + best_cw);
+ h = max(h, best_y + best_ch);
+
+ // Resize bitmap if necessary.
+ if (uint(w) > m_bitmap.width() || uint(h) > m_bitmap.height())
+ {
+ //nvDebug("Resize bitmap (%d, %d).\n", nextPowerOfTwo(w), nextPowerOfTwo(h));
+ m_bitmap.resize(nextPowerOfTwo(w), nextPowerOfTwo(h), false);
+ m_debug_bitmap.resize(nextPowerOfTwo(w), nextPowerOfTwo(h));
+ }
+
+ //nvDebug("Add chart at (%d, %d).\n", best_x, best_y);
+
+#if 0 // old method.
+#if _DEBUG
+ checkCanAddChart(chart, w, h, best_x, best_y, best_r);
+#endif
+
+ // Add chart.
+ addChart(chart, w, h, best_x, best_y, best_r);
+#else
+ // Add chart reusing its bitmap.
+ addChart(&chart_bitmap, w, h, best_x, best_y, best_r);
+#endif
+
+ //float best_angle = 2 * PI * best_r;
+
+ // Translate and rotate chart texture coordinates.
+ HalfEdge::Mesh * mesh = chart->chartMesh();
+ const uint vertexCount = mesh->vertexCount();
+ for (uint v = 0; v < vertexCount; v++)
+ {
+ HalfEdge::Vertex * vertex = mesh->vertexAt(v);
+
+ Vector2 t = vertex->tex * scale + offset;
+ if (best_r) 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;
+
+ nvCheck(vertex->tex.x >= 0 && vertex->tex.y >= 0);
+ }
+
+#if DEBUG_OUTPUT && 0
+ StringBuilder fileName;
+ fileName.format("debug_packer_%d.tga", i);
+ //outputDebugBitmap(fileName.str(), m_bitmap, w, h);
+ outputDebugImage(fileName.str(), m_debug_bitmap, w, h);
+#endif
+ }
+
+#endif // 0
+
+ //w -= padding - 1; // Leave one pixel border!
+ //h -= padding - 1;
+
+ m_width = max(0, w);
+ m_height = max(0, h);
+
+ nvCheck(isAligned(m_width, 4));
+ nvCheck(isAligned(m_height, 4));
+
+ m_debug_bitmap.resize(m_width, m_height);
+ m_debug_bitmap.setFormat(Image::Format_ARGB);
+
+#if DEBUG_OUTPUT
+ //outputDebugBitmap("debug_packer_final.tga", m_bitmap, w, h);
+ //outputDebugImage("debug_packer_final.tga", m_debug_bitmap, w, h);
+ ImageIO::save("debug_packer_final.tga", &m_debug_bitmap);
+#endif
+}
+
+
+// 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 AtlasPacker::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)
+{
+ 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);
+ }
+ else
+ {
+ findChartLocation_random(bitmap, extents, w, h, best_x, best_y, best_w, best_h, best_r, attempts);
+ }
+}
+
+#define BLOCK_SIZE 4
+
+void AtlasPacker::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)
+{
+ int best_metric = INT_MAX;
+
+ // Try two different orientations.
+ for (int r = 0; r < 2; r++)
+ {
+ int cw = bitmap->width();
+ int ch = bitmap->height();
+ if (r & 1) swap(cw, ch);
+
+ for (int y = 0; y <= h + 1; y += BLOCK_SIZE) // + 1 to extend atlas in case atlas full.
+ {
+ for (int x = 0; x <= w + 1; x += BLOCK_SIZE) // + 1 not really necessary here.
+ {
+ // Early out.
+ int area = max(w, x+cw) * max(h, y+ch);
+ //int perimeter = max(w, x+cw) + max(h, y+ch);
+ int extents = max(max(w, x+cw), max(h, y+ch));
+
+ int metric = extents*extents + area;
+
+ if (metric > best_metric) {
+ continue;
+ }
+ if (metric == best_metric && max(x, y) >= max(*best_x, *best_y)) {
+ // If metric is the same, pick the one closest to the origin.
+ continue;
+ }
+
+ if (canAddChart(bitmap, w, h, x, y, r))
+ {
+ 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:
+ nvDebugCheck (best_metric != INT_MAX);
+}
+
+
+void AtlasPacker::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)
+{
+ 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.
+
+ x = align(x, BLOCK_SIZE);
+ y = align(y, BLOCK_SIZE);
+
+ int cw = bitmap->width();
+ int ch = bitmap->height();
+ if (r & 1) swap(cw, ch);
+
+ // Early out.
+ int area = max(w, x+cw) * max(h, y+ch);
+ //int perimeter = max(w, x+cw) + max(h, y+ch);
+ int extents = max(max(w, x+cw), max(h, y+ch));
+
+ int metric = extents*extents + area;
+
+ if (metric > best_metric) {
+ continue;
+ }
+ if (metric == best_metric && min(x, y) > min(*best_x, *best_y)) {
+ // If metric is the same, pick the one closest to the origin.
+ continue;
+ }
+
+ if (canAddChart(bitmap, w, h, x, y, r))
+ {
+ 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 AtlasPacker::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 uint faceCount = chart->faceCount();
+ for (uint f = 0; f < faceCount; f++)
+ {
+ const HalfEdge::Face * face = chart->chartMesh()->faceAt(f);
+
+ Vector2 vertices[4];
+
+ uint 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);
+ }
+ }
+
+ swap(tmp, *bitmap);
+ }
+}
+
+
+void AtlasPacker::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)
+ };
+ /*static const Vector2 pad[4] = {
+ Vector2(-1, -1),
+ Vector2(1, -1),
+ Vector2(-1, 1),
+ Vector2(1, 1)
+ };*/
+
+ // 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 uint faceCount = chart->chartMesh()->faceCount();
+ for (uint f = 0; f < faceCount; f++)
+ {
+ const HalfEdge::Face * face = chart->chartMesh()->faceAt(f);
+
+ Vector2 vertices[4];
+
+ uint 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];
+ nvCheck(ftoi_ceil(vertices[edgeCount].x) >= 0);
+ nvCheck(ftoi_ceil(vertices[edgeCount].y) >= 0);
+ nvCheck(ftoi_ceil(vertices[edgeCount].x) <= w);
+ nvCheck(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);
+ }
+ }
+ }
+
+ // @@ This only allows us to expand the size in texel intervals.
+ /*if (m_padding_x != 0 && m_padding_y != 0)*/ {
+
+ // Expand chart by padding pixels. (dilation)
+ BitMap tmp(w, h);
+ //for (int i = 0; i < 1; 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);
+ }
+ }
+
+ swap(tmp, *bitmap);
+ //}
+ }
+}
+
+bool AtlasPacker::canAddChart(const BitMap * bitmap, int atlas_w, int atlas_h, int offset_x, int offset_y, int r)
+{
+ nvDebugCheck(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;
+}
+
+#if 0
+void AtlasPacker::checkCanAddChart(const Chart * chart, int w, int h, int x, int y, int r)
+{
+ nvDebugCheck(r == 0 || r == 1);
+ Vector2 extents = Vector2(float(w), float(h));
+ Vector2 offset = Vector2(float(x), float(y));
+
+ // Rasterize chart faces, set bits.
+ const uint faceCount = chart->faceCount();
+ for (uint f = 0; f < faceCount; f++)
+ {
+ const HalfEdge::Face * face = chart->chartMesh()->faceAt(f);
+
+ Vector2 vertices[4];
+
+ uint edgeCount = 0;
+ for (HalfEdge::Face::ConstEdgeIterator it(face->edges()); !it.isDone(); it.advance())
+ {
+ if (edgeCount < 4)
+ {
+ Vector2 t = it.vertex()->tex;
+ if (r == 1) swap(t.x, t.y);
+ vertices[edgeCount] = t + offset;
+ }
+ edgeCount++;
+ }
+
+ if (edgeCount == 3)
+ {
+ Raster::drawTriangle(Raster::Mode_Antialiased, extents, /*enableScissors=*/true, vertices, AtlasPacker::checkBitsCallback, &m_bitmap);
+ }
+ else
+ {
+ Raster::drawQuad(Raster::Mode_Antialiased, extents, /*enableScissors=*/true, vertices, AtlasPacker::checkBitsCallback, &m_bitmap);
+ }
+ }
+}
+#endif // 0
+
+
+static Color32 chartColor = Color32(0);
+static void selectRandomColor(MTRand & rand) {
+ // Pick random color for this chart. @@ Select random hue, but fixed saturation/luminance?
+ chartColor.r = 128 + rand.getRange(127);
+ chartColor.g = 128 + rand.getRange(127);
+ chartColor.b = 128 + rand.getRange(127);
+ chartColor.a = 255;
+}
+static bool debugDrawCallback(void * param, int x, int y, Vector3::Arg, Vector3::Arg, Vector3::Arg, float area)
+{
+ Image * image = (Image *)param;
+
+ if (area > 0.0) {
+ Color32 c = image->pixel(x, y);
+ c.r = chartColor.r;
+ c.g = chartColor.g;
+ c.b = chartColor.b;
+ c.a += U8(ftoi_round(0.5f * area * 255));
+ image->pixel(x, y) = c;
+ }
+
+ return true;
+}
+
+void AtlasPacker::addChart(const Chart * chart, int w, int h, int x, int y, int r, Image * debugOutput)
+{
+ nvDebugCheck(r == 0 || r == 1);
+
+ nvDebugCheck(debugOutput != NULL);
+ selectRandomColor(m_rand);
+
+ Vector2 extents = Vector2(float(w), float(h));
+ Vector2 offset = Vector2(float(x), float(y)) + Vector2(0.5);
+
+ // Rasterize chart faces, set bits.
+ const uint faceCount = chart->faceCount();
+ for (uint f = 0; f < faceCount; f++)
+ {
+ const HalfEdge::Face * face = chart->chartMesh()->faceAt(f);
+
+ Vector2 vertices[4];
+
+ uint edgeCount = 0;
+ for (HalfEdge::Face::ConstEdgeIterator it(face->edges()); !it.isDone(); it.advance())
+ {
+ if (edgeCount < 4)
+ {
+ Vector2 t = it.vertex()->tex;
+ if (r == 1) swap(t.x, t.y);
+ vertices[edgeCount] = t + offset;
+ }
+ edgeCount++;
+ }
+
+ if (edgeCount == 3)
+ {
+ Raster::drawTriangle(Raster::Mode_Antialiased, extents, /*enableScissors=*/true, vertices, debugDrawCallback, debugOutput);
+ }
+ else
+ {
+ Raster::drawQuad(Raster::Mode_Antialiased, extents, /*enableScissors=*/true, vertices, debugDrawCallback, debugOutput);
+ }
+ }
+}
+
+
+void AtlasPacker::addChart(const BitMap * bitmap, int atlas_w, int atlas_h, int offset_x, int offset_y, int r, Image * debugOutput)
+{
+ nvDebugCheck(r == 0 || r == 1);
+
+ // Check whether the two bitmaps overlap.
+
+ const int w = bitmap->width();
+ const int h = bitmap->height();
+
+ if (debugOutput != NULL) {
+ selectRandomColor(m_rand);
+ }
+
+ 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 (debugOutput) debugOutput->pixel(xx, yy) = chartColor;
+ else {
+ nvDebugCheck(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) {
+ if (debugOutput) debugOutput->pixel(xx, yy) = chartColor;
+ else {
+ nvDebugCheck(m_bitmap.bitAt(xx, yy) == false);
+ m_bitmap.setBitAt(xx, yy);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+
+
+/*static*/ bool AtlasPacker::checkBitsCallback(void * param, int x, int y, Vector3::Arg, Vector3::Arg, Vector3::Arg, float)
+{
+ BitMap * bitmap = (BitMap * )param;
+
+ nvDebugCheck(bitmap->bitAt(x, y) == false);
+
+ return true;
+}
+
+/*static*/ bool AtlasPacker::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;
+}
+
+
+
+float AtlasPacker::computeAtlasUtilization() const {
+ const uint w = m_width;
+ const uint h = m_height;
+ nvDebugCheck(w <= m_bitmap.width());
+ nvDebugCheck(h <= m_bitmap.height());
+
+ uint count = 0;
+ for (uint y = 0; y < h; y++) {
+ for (uint x = 0; x < w; x++) {
+ count += m_bitmap.bitAt(x, y);
+ }
+ }
+
+ return float(count) / (w * h);
+}
diff --git a/thirdparty/thekla_atlas/nvmesh/param/AtlasPacker.h b/thirdparty/thekla_atlas/nvmesh/param/AtlasPacker.h
new file mode 100644
index 0000000000..2d305f38cd
--- /dev/null
+++ b/thirdparty/thekla_atlas/nvmesh/param/AtlasPacker.h
@@ -0,0 +1,63 @@
+// This code is in the public domain -- castano@gmail.com
+
+#pragma once
+#ifndef NV_MESH_ATLASPACKER_H
+#define NV_MESH_ATLASPACKER_H
+
+#include "nvcore/RadixSort.h"
+#include "nvmath/Vector.h"
+#include "nvmath/Random.h"
+#include "nvimage/BitMap.h"
+#include "nvimage/Image.h"
+
+#include "nvmesh/nvmesh.h"
+
+
+namespace nv
+{
+ class Atlas;
+ class Chart;
+
+ struct AtlasPacker
+ {
+ AtlasPacker(Atlas * atlas);
+ ~AtlasPacker();
+
+ void packCharts(int quality, float texelArea, bool blockAligned, bool conservative);
+ float computeAtlasUtilization() const;
+
+ private:
+
+ 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);
+ 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);
+ 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);
+
+ void drawChartBitmapDilate(const Chart * chart, BitMap * bitmap, int padding);
+ void drawChartBitmap(const Chart * chart, BitMap * bitmap, const Vector2 & scale, const Vector2 & offset);
+
+ bool canAddChart(const BitMap * bitmap, int w, int h, int x, int y, int r);
+ void addChart(const BitMap * bitmap, int w, int h, int x, int y, int r, Image * debugOutput);
+ //void checkCanAddChart(const Chart * chart, int w, int h, int x, int y, int r);
+ void addChart(const Chart * chart, int w, int h, int x, int y, int r, Image * debugOutput);
+
+
+ static bool checkBitsCallback(void * param, int x, int y, Vector3::Arg bar, Vector3::Arg dx, Vector3::Arg dy, float coverage);
+ static bool setBitsCallback(void * param, int x, int y, Vector3::Arg bar, Vector3::Arg dx, Vector3::Arg dy, float coverage);
+
+ private:
+
+ Atlas * m_atlas;
+ BitMap m_bitmap;
+ Image m_debug_bitmap;
+ RadixSort m_radix;
+
+ uint m_width;
+ uint m_height;
+
+ MTRand m_rand;
+
+ };
+
+} // nv namespace
+
+#endif // NV_MESH_ATLASPACKER_H
diff --git a/thirdparty/thekla_atlas/nvmesh/param/LeastSquaresConformalMap.cpp b/thirdparty/thekla_atlas/nvmesh/param/LeastSquaresConformalMap.cpp
new file mode 100644
index 0000000000..cd1e8bbb7b
--- /dev/null
+++ b/thirdparty/thekla_atlas/nvmesh/param/LeastSquaresConformalMap.cpp
@@ -0,0 +1,483 @@
+// Copyright NVIDIA Corporation 2008 -- Ignacio Castano <icastano@nvidia.com>
+
+#include "nvmesh.h" // pch
+
+#include "LeastSquaresConformalMap.h"
+#include "ParameterizationQuality.h"
+#include "Util.h"
+
+#include "nvmesh/halfedge/Mesh.h"
+#include "nvmesh/halfedge/Vertex.h"
+#include "nvmesh/halfedge/Face.h"
+
+#include "nvmath/Sparse.h"
+#include "nvmath/Solver.h"
+#include "nvmath/Vector.inl"
+
+#include "nvcore/Array.inl"
+
+
+using namespace nv;
+using namespace HalfEdge;
+
+namespace
+{
+
+ // Test all pairs of vertices in the boundary and check distance.
+ static void findDiameterVertices(HalfEdge::Mesh * mesh, HalfEdge::Vertex ** a, HalfEdge::Vertex ** b)
+ {
+ nvDebugCheck(mesh != NULL);
+ nvDebugCheck(a != NULL);
+ nvDebugCheck(b != NULL);
+
+ const uint vertexCount = mesh->vertexCount();
+
+ float maxLength = 0.0f;
+
+ for (uint v0 = 1; v0 < vertexCount; v0++)
+ {
+ HalfEdge::Vertex * vertex0 = mesh->vertexAt(v0);
+ nvDebugCheck(vertex0 != NULL);
+
+ if (!vertex0->isBoundary()) continue;
+
+ for (uint v1 = 0; v1 < v0; v1++)
+ {
+ HalfEdge::Vertex * vertex1 = mesh->vertexAt(v1);
+ nvDebugCheck(vertex1 != NULL);
+
+ if (!vertex1->isBoundary()) continue;
+
+ float len = length(vertex0->pos - vertex1->pos);
+
+ if (len > maxLength)
+ {
+ maxLength = len;
+
+ *a = vertex0;
+ *b = vertex1;
+ }
+ }
+ }
+
+ nvDebugCheck(*a != NULL && *b != NULL);
+ }
+
+ // Fast sweep in 3 directions
+ static bool findApproximateDiameterVertices(HalfEdge::Mesh * mesh, HalfEdge::Vertex ** a, HalfEdge::Vertex ** b)
+ {
+ nvDebugCheck(mesh != NULL);
+ nvDebugCheck(a != NULL);
+ nvDebugCheck(b != NULL);
+
+ const uint 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 (uint v = 1; v < vertexCount; v++)
+ {
+ HalfEdge::Vertex * vertex = mesh->vertexAt(v);
+ nvDebugCheck(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 (uint v = 1; v < vertexCount; v++)
+ {
+ HalfEdge::Vertex * vertex = mesh->vertexAt(v);
+ nvDebugCheck(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 Bruno Levy:
+
+ // Computes the coordinates of the vertices of a triangle
+ // in a local 2D orthonormal basis of the triangle's plane.
+ static void project_triangle(Vector3::Arg p0, Vector3::Arg p1, Vector3::Arg p2, Vector2 * z0, Vector2 * z1, Vector2 * z2)
+ {
+ Vector3 X = normalize(p1 - p0, 0.0f);
+ Vector3 Z = normalize(cross(X, (p2 - p0)), 0.0f);
+ Vector3 Y = normalize(cross(Z, X), 0.0f);
+
+ float x0 = 0.0f;
+ float y0 = 0.0f;
+ float x1 = length(p1 - p0);
+ float y1 = 0.0f;
+ float x2 = dot((p2 - p0), X);
+ float y2 = dot((p2 - p0), Y);
+
+ *z0 = Vector2(x0, y0);
+ *z1 = Vector2(x1, y1);
+ *z2 = Vector2(x2, y2);
+ }
+
+ // LSCM equation, geometric form :
+ // (Z1 - Z0)(U2 - U0) = (Z2 - Z0)(U1 - U0)
+ // Where Uk = uk + i.vk is the complex number
+ // corresponding to (u,v) coords
+ // Zk = xk + i.yk is the complex number
+ // corresponding to local (x,y) coords
+ // cool: no divide with this expression,
+ // makes it more numerically stable in
+ // the presence of degenerate triangles.
+
+ static void setup_conformal_map_relations(SparseMatrix & 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;
+
+ Vector2 z0, z1, z2;
+ project_triangle(p0, p1, p2, &z0, &z1, &z2);
+
+ Vector2 z01 = z1 - z0;
+ Vector2 z02 = z2 - z0;
+
+ float a = z01.x;
+ float b = z01.y;
+ float c = z02.x;
+ float d = z02.y;
+ nvCheck(b == 0.0f);
+
+ // 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;
+
+ // Note : b = 0
+
+ // Real part
+ A.setCoefficient(u0_id, 2 * row + 0, -a+c);
+ A.setCoefficient(v0_id, 2 * row + 0, b-d);
+ A.setCoefficient(u1_id, 2 * row + 0, -c);
+ A.setCoefficient(v1_id, 2 * row + 0, d);
+ A.setCoefficient(u2_id, 2 * row + 0, a);
+
+ // Imaginary part
+ A.setCoefficient(u0_id, 2 * row + 1, -b+d);
+ A.setCoefficient(v0_id, 2 * row + 1, -a+c);
+ A.setCoefficient(u1_id, 2 * row + 1, -d);
+ A.setCoefficient(v1_id, 2 * row + 1, -c);
+ A.setCoefficient(v2_id, 2 * row + 1, a);
+ }
+
+
+ // 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 triangle_cosines(Vector3::Arg v1, Vector3::Arg v2, Vector3::Arg v3, float *a1, float *a2, float *a3)
+ {
+ *a1 = vec_angle_cos(v3, v1, v2);
+ *a2 = vec_angle_cos(v1, v2, v3);
+ *a3 = vec_angle_cos(v2, v3, v1);
+ }
+
+ static void setup_abf_relations(SparseMatrix & 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;
+
+#if 1
+ // @@ 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);
+
+ /*// Hack for degenerate triangles.
+ if (equal(s0, 0) && equal(s1, 0) && equal(s2, 0)) {
+ if (equal(a0, 0)) a0 += 0.001f;
+ if (equal(a1, 0)) a1 += 0.001f;
+ if (equal(a2, 0)) a2 += 0.001f;
+
+ if (equal(a0, PI)) a0 = PI - a1 - a2;
+ if (equal(a1, PI)) a1 = PI - a0 - a2;
+ if (equal(a2, PI)) a2 = PI - a0 - a1;
+
+ s0 = sinf(a0);
+ s1 = sinf(a1);
+ s2 = sinf(a2);
+ }*/
+
+ if (s1 > s0 && s1 > s2)
+ {
+ swap(s1, s2);
+ swap(s0, s1);
+
+ swap(a1, a2);
+ swap(a0, a1);
+
+ swap(id1, id2);
+ swap(id0, id1);
+ }
+ else if (s0 > s1 && s0 > s2)
+ {
+ swap(s0, s2);
+ swap(s0, s1);
+
+ swap(a0, a2);
+ swap(a0, a1);
+
+ swap(id0, id2);
+ swap(id0, id1);
+ }
+
+ float c0 = cosf(a0);
+#else
+ float c0, c1, c2;
+ triangle_cosines(p0, p1, p2, &c0, &c1, &c2);
+
+ float s0 = 1 - c0*c0;
+ float s1 = 1 - c1*c1;
+ float s2 = 1 - c2*c2;
+
+ nvDebugCheck(s0 != 0 || s1 != 0 || s2 != 0);
+
+ if (s1 > s0 && s1 > s2)
+ {
+ swap(s1, s2);
+ swap(s0, s1);
+
+ swap(c1, c2);
+ swap(c0, c1);
+
+ swap(id1, id2);
+ swap(id0, id1);
+ }
+ else if (s0 > s1 && s0 > s2)
+ {
+ swap(s0, s2);
+ swap(s0, s1);
+
+ swap(c0, c2);
+ swap(c0, c1);
+
+ swap(id0, id2);
+ swap(id0, id1);
+ }
+#endif
+
+ 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);
+ }
+
+} // namespace
+
+
+bool nv::computeLeastSquaresConformalMap(HalfEdge::Mesh * mesh)
+{
+ nvDebugCheck(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 uint vertexCount = mesh->vertexCount();
+ const uint D = 2 * vertexCount;
+ const uint N = 2 * 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;
+ }
+
+ SparseMatrix 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 (uint v = 0; v < vertexCount; v++)
+ {
+ HalfEdge::Vertex * vertex = mesh->vertexAt(v);
+ nvDebugCheck(vertex != NULL);
+
+ // Initial solution.
+ x[2 * v + 0] = vertex->tex.x;
+ x[2 * v + 1] = vertex->tex.y;
+ }
+
+ // Fill A:
+ const uint faceCount = mesh->faceCount();
+ for (uint f = 0, t = 0; f < faceCount; f++)
+ {
+ const HalfEdge::Face * face = mesh->faceAt(f);
+ nvDebugCheck(face != NULL);
+ nvDebugCheck(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();
+ nvCheck(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 uint lockedParameters[] =
+ {
+ 2 * v0->id + 0,
+ 2 * v0->id + 1,
+ 2 * v1->id + 0,
+ 2 * v1->id + 1
+ };
+
+ // Solve
+ LeastSquaresSolver(A, b, x, lockedParameters, 4, 0.000001f);
+
+ // Map x back to texcoords:
+ for (uint v = 0; v < vertexCount; v++)
+ {
+ HalfEdge::Vertex * vertex = mesh->vertexAt(v);
+ nvDebugCheck(vertex != NULL);
+
+ vertex->tex = Vector2(x[2 * v + 0], x[2 * v + 1]);
+ }
+
+ return true;
+}
diff --git a/thirdparty/thekla_atlas/nvmesh/param/LeastSquaresConformalMap.h b/thirdparty/thekla_atlas/nvmesh/param/LeastSquaresConformalMap.h
new file mode 100644
index 0000000000..51fbf193c8
--- /dev/null
+++ b/thirdparty/thekla_atlas/nvmesh/param/LeastSquaresConformalMap.h
@@ -0,0 +1,15 @@
+// Copyright NVIDIA Corporation 2008 -- Ignacio Castano <icastano@nvidia.com>
+
+#pragma once
+#ifndef NV_MESH_LEASTSQUARESCONFORMALMAP_H
+#define NV_MESH_LEASTSQUARESCONFORMALMAP_H
+
+namespace nv
+{
+ namespace HalfEdge { class Mesh; }
+
+ bool computeLeastSquaresConformalMap(HalfEdge::Mesh * mesh);
+
+} // nv namespace
+
+#endif // NV_MESH_LEASTSQUARESCONFORMALMAP_H
diff --git a/thirdparty/thekla_atlas/nvmesh/param/OrthogonalProjectionMap.cpp b/thirdparty/thekla_atlas/nvmesh/param/OrthogonalProjectionMap.cpp
new file mode 100644
index 0000000000..d6e5e30561
--- /dev/null
+++ b/thirdparty/thekla_atlas/nvmesh/param/OrthogonalProjectionMap.cpp
@@ -0,0 +1,99 @@
+// This code is in the public domain -- castano@gmail.com
+
+#include "nvmesh.h" // pch
+
+#include "OrthogonalProjectionMap.h"
+
+#include "nvcore/Array.inl"
+
+#include "nvmath/Fitting.h"
+#include "nvmath/Vector.inl"
+#include "nvmath/Box.inl"
+#include "nvmath/Plane.inl"
+
+#include "nvmesh/halfedge/Mesh.h"
+#include "nvmesh/halfedge/Vertex.h"
+#include "nvmesh/halfedge/Face.h"
+#include "nvmesh/geometry/Bounds.h"
+
+
+using namespace nv;
+
+bool nv::computeOrthogonalProjectionMap(HalfEdge::Mesh * mesh)
+{
+ Vector3 axis[2];
+
+#if 1
+
+ uint vertexCount = mesh->vertexCount();
+ Array<Vector3> points(vertexCount);
+ points.resize(vertexCount);
+
+ for (uint i = 0; i < vertexCount; i++)
+ {
+ points[i] = mesh->vertexAt(i)->pos;
+ }
+
+#if 0
+ axis[0] = Fit::computePrincipalComponent_EigenSolver(vertexCount, points.buffer());
+ axis[0] = normalize(axis[0]);
+
+ Plane plane = Fit::bestPlane(vertexCount, points.buffer());
+
+ Vector3 n = plane.vector();
+
+ axis[1] = cross(axis[0], n);
+ axis[1] = normalize(axis[1]);
+#else
+ // Avoid redundant computations.
+ float matrix[6];
+ Fit::computeCovariance(vertexCount, points.buffer(), matrix);
+
+ if (matrix[0] == 0 && matrix[3] == 0 && matrix[5] == 0) {
+ return false;
+ }
+
+ float eigenValues[3];
+ Vector3 eigenVectors[3];
+ if (!nv::Fit::eigenSolveSymmetric3(matrix, eigenValues, eigenVectors)) {
+ return false;
+ }
+
+ axis[0] = normalize(eigenVectors[0]);
+ axis[1] = normalize(eigenVectors[1]);
+#endif
+
+
+#else
+
+ // IC: I thought this was generally more robust, but turns out it's not even guaranteed to return a valid projection. Imagine a narrow quad perpendicular to one plane, but rotated so that the shortest axis of
+ // the bounding box is in the direction of that plane.
+
+ // Use the shortest box axis
+ Box box = MeshBounds::box(mesh);
+ Vector3 dir = box.extents();
+
+ if (fabs(dir.x) <= fabs(dir.y) && fabs(dir.x) <= fabs(dir.z)) {
+ axis[0] = Vector3(0, 1, 0);
+ axis[1] = Vector3(0, 0, 1);
+ }
+ else if (fabs(dir.y) <= fabs(dir.z)) {
+ axis[0] = Vector3(1, 0, 0);
+ axis[1] = Vector3(0, 0, 1);
+ }
+ else {
+ axis[0] = Vector3(1, 0, 0);
+ axis[1] = Vector3(0, 1, 0);
+ }
+#endif
+
+ // 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;
+}
diff --git a/thirdparty/thekla_atlas/nvmesh/param/OrthogonalProjectionMap.h b/thirdparty/thekla_atlas/nvmesh/param/OrthogonalProjectionMap.h
new file mode 100644
index 0000000000..54920413d5
--- /dev/null
+++ b/thirdparty/thekla_atlas/nvmesh/param/OrthogonalProjectionMap.h
@@ -0,0 +1,15 @@
+// This code is in the public domain -- castano@gmail.com
+
+#pragma once
+#ifndef NV_MESH_ORTHOGONALPROJECTIONMAP_H
+#define NV_MESH_ORTHOGONALPROJECTIONMAP_H
+
+namespace nv
+{
+ namespace HalfEdge { class Mesh; }
+
+ bool computeOrthogonalProjectionMap(HalfEdge::Mesh * mesh);
+
+} // nv namespace
+
+#endif // NV_MESH_ORTHOGONALPROJECTIONMAP_H
diff --git a/thirdparty/thekla_atlas/nvmesh/param/ParameterizationQuality.cpp b/thirdparty/thekla_atlas/nvmesh/param/ParameterizationQuality.cpp
new file mode 100644
index 0000000000..683ee603cd
--- /dev/null
+++ b/thirdparty/thekla_atlas/nvmesh/param/ParameterizationQuality.cpp
@@ -0,0 +1,323 @@
+// Copyright NVIDIA Corporation 2008 -- Ignacio Castano <icastano@nvidia.com>
+
+#include "nvmesh.h" // pch
+
+#include "ParameterizationQuality.h"
+
+#include "nvmesh/halfedge/Mesh.h"
+#include "nvmesh/halfedge/Face.h"
+#include "nvmesh/halfedge/Vertex.h"
+#include "nvmesh/halfedge/Edge.h"
+
+#include "nvmath/Vector.inl"
+
+#include "nvcore/Debug.h"
+
+#include <float.h>
+
+
+using namespace nv;
+
+#if 0
+/*
+float triangleConformalEnergy(Vector3 q[3], Vector2 p[3])
+{
+const Vector3 v1 = q[0];
+const Vector3 v2 = q[1];
+const Vector3 v3 = q[2];
+
+const Vector2 w1 = p[0];
+const Vector2 w2 = p[1];
+const Vector2 w3 = p[2];
+
+float x1 = v2.x() - v1.x();
+float x2 = v3.x() - v1.x();
+float y1 = v2.y() - v1.y();
+float y2 = v3.y() - v1.y();
+float z1 = v2.z() - v1.z();
+float z2 = v3.z() - v1.z();
+
+float s1 = w2.x() - w1.x();
+float s2 = w3.x() - w1.x();
+float t1 = w2.y() - w1.y();
+float t2 = w3.y() - w1.y();
+
+float r = 1.0f / (s1 * t2 - s2 * t1);
+Vector3 sdir((t2 * x1 - t1 * x2) * r, (t2 * y1 - t1 * y2) * r, (t2 * z1 - t1 * z2) * r);
+Vector3 tdir((s1 * x2 - s2 * x1) * r, (s1 * y2 - s2 * y1) * r, (s1 * z2 - s2 * z1) * r);
+
+Vector3 N = cross(v3-v1, v2-v1);
+
+// Rotate 90 around N.
+}
+*/
+
+static float triangleConformalEnergy(Vector3 q[3], Vector2 p[3])
+{
+ // Using Denis formulas:
+ Vector3 c0 = q[1] - q[2];
+ Vector3 c1 = q[2] - q[0];
+ Vector3 c2 = q[0] - q[1];
+
+ Vector3 N = cross(-c0, c1);
+ float T = length(N); // 2T
+ N = normalize(N, 0);
+
+ float cot_alpha0 = dot(-c1, c2) / length(cross(-c1, c2));
+ float cot_alpha1 = dot(-c2, c0) / length(cross(-c2, c0));
+ float cot_alpha2 = dot(-c0, c1) / length(cross(-c0, c1));
+
+ Vector3 t0 = -cot_alpha1 * c1 + cot_alpha2 * c2;
+ Vector3 t1 = -cot_alpha2 * c2 + cot_alpha0 * c0;
+ Vector3 t2 = -cot_alpha0 * c0 + cot_alpha1 * c1;
+
+ nvCheck(equal(length(t0), length(c0)));
+ nvCheck(equal(length(t1), length(c1)));
+ nvCheck(equal(length(t2), length(c2)));
+ nvCheck(equal(dot(t0, c0), 0));
+ nvCheck(equal(dot(t1, c1), 0));
+ nvCheck(equal(dot(t2, c2), 0));
+
+ // Gradients
+ Vector3 grad_u = 1.0f / T * (p[0].x * t0 + p[1].x * t1 + p[2].x * t2);
+ Vector3 grad_v = 1.0f / T * (p[0].y * t0 + p[1].y * t1 + p[2].y * t2);
+
+ // Rotated gradients
+ Vector3 Jgrad_u = 1.0f / T * (p[0].x * c0 + p[1].x * c1 + p[2].x * c2);
+ Vector3 Jgrad_v = 1.0f / T * (p[0].y * c0 + p[1].y * c1 + p[2].y * c2);
+
+ // Using Lengyel's formulas:
+ {
+ const Vector3 v1 = q[0];
+ const Vector3 v2 = q[1];
+ const Vector3 v3 = q[2];
+
+ const Vector2 w1 = p[0];
+ const Vector2 w2 = p[1];
+ const Vector2 w3 = p[2];
+
+ float x1 = v2.x - v1.x;
+ float x2 = v3.x - v1.x;
+ float y1 = v2.y - v1.y;
+ float y2 = v3.y - v1.y;
+ float z1 = v2.z - v1.z;
+ float z2 = v3.z - v1.z;
+
+ float s1 = w2.x - w1.x;
+ float s2 = w3.x - w1.x;
+ float t1 = w2.y - w1.y;
+ float t2 = w3.y - w1.y;
+
+ float r = 1.0f / (s1 * t2 - s2 * t1);
+ Vector3 sdir((t2 * x1 - t1 * x2) * r, (t2 * y1 - t1 * y2) * r, (t2 * z1 - t1 * z2) * r);
+ Vector3 tdir((s1 * x2 - s2 * x1) * r, (s1 * y2 - s2 * y1) * r, (s1 * z2 - s2 * z1) * r);
+
+ Vector3 Jsdir = cross(N, sdir);
+ Vector3 Jtdir = cross(N, tdir);
+
+ float x = 3;
+ }
+
+ // check: sdir == grad_u
+ // check: tdir == grad_v
+
+ return length(grad_u - Jgrad_v);
+}
+#endif // 0
+
+
+ParameterizationQuality::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::ParameterizationQuality(const HalfEdge::Mesh * mesh)
+{
+ nvDebugCheck(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 uint faceCount = mesh->faceCount();
+ for (uint 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;
+ }
+
+ nvDebugCheck(isFinite(m_parametricArea) && m_parametricArea >= 0);
+ nvDebugCheck(isFinite(m_geometricArea) && m_geometricArea >= 0);
+ nvDebugCheck(isFinite(m_stretchMetric));
+ nvDebugCheck(isFinite(m_maxStretchMetric));
+ nvDebugCheck(isFinite(m_conformalMetric));
+ nvDebugCheck(isFinite(m_authalicMetric));
+}
+
+bool ParameterizationQuality::isValid() const
+{
+ return m_flippedTriangleCount == 0; // @@ Does not test for self-overlaps.
+}
+
+float ParameterizationQuality::rmsStretchMetric() const
+{
+ if (m_geometricArea == 0) return 0.0f;
+ float normFactor = sqrtf(m_parametricArea / m_geometricArea);
+ return sqrtf(m_stretchMetric / m_geometricArea) * normFactor;
+}
+
+float ParameterizationQuality::maxStretchMetric() const
+{
+ if (m_geometricArea == 0) return 0.0f;
+ float normFactor = sqrtf(m_parametricArea / m_geometricArea);
+ return m_maxStretchMetric * normFactor;
+}
+
+float ParameterizationQuality::rmsConformalMetric() const
+{
+ if (m_geometricArea == 0) return 0.0f;
+ return sqrtf(m_conformalMetric / m_geometricArea);
+}
+
+float ParameterizationQuality::maxAuthalicMetric() const
+{
+ if (m_geometricArea == 0) return 0.0f;
+ return sqrtf(m_authalicMetric / m_geometricArea);
+}
+
+void ParameterizationQuality::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 = max(m_maxStretchMetric, pq.m_maxStretchMetric);
+
+ m_conformalMetric += pq.m_conformalMetric;
+ m_authalicMetric += pq.m_authalicMetric;
+}
+
+
+void ParameterizationQuality::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 * max(0.0f, a + c - sqrtf(square(a - c) + 4 * square(b)))); // gamma uppercase, min eigenvalue.
+ float sigma2 = sqrtf(0.5f * max(0.0f, a + c + sqrtf(square(a - c) + 4 * square(b)))); // gamma lowercase, max eigenvalue.
+ nvCheck(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);
+ nvDebugCheck(equal(rmsStretch, rmsStretch2, 0.01f));
+
+ if (parametricArea < 0.0f)
+ {
+ // Count flipped triangles.
+ m_flippedTriangleCount++;
+
+ parametricArea = fabsf(parametricArea);
+ }
+
+ m_stretchMetric += square(rmsStretch) * geometricArea;
+ m_maxStretchMetric = 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);
+}
diff --git a/thirdparty/thekla_atlas/nvmesh/param/ParameterizationQuality.h b/thirdparty/thekla_atlas/nvmesh/param/ParameterizationQuality.h
new file mode 100644
index 0000000000..342e26b889
--- /dev/null
+++ b/thirdparty/thekla_atlas/nvmesh/param/ParameterizationQuality.h
@@ -0,0 +1,56 @@
+// Copyright NVIDIA Corporation 2008 -- Ignacio Castano <icastano@nvidia.com>
+
+#pragma once
+#ifndef NV_MESH_PARAMETERIZATIONQUALITY_H
+#define NV_MESH_PARAMETERIZATIONQUALITY_H
+
+#include <nvmesh/nvmesh.h>
+
+namespace nv
+{
+ class Vector2;
+ class Vector3;
+
+ namespace HalfEdge { class Mesh; }
+
+ // Estimate quality of existing parameterization.
+ NVMESH_CLASS class ParameterizationQuality
+ {
+ public:
+ ParameterizationQuality();
+ ParameterizationQuality(const HalfEdge::Mesh * mesh);
+
+ bool isValid() const;
+
+ float rmsStretchMetric() const;
+ float maxStretchMetric() const;
+
+ float rmsConformalMetric() const;
+ float maxAuthalicMetric() const;
+
+ void operator += (const ParameterizationQuality & pq);
+
+ private:
+
+ void processTriangle(Vector3 p[3], Vector2 t[3]);
+
+ private:
+
+ uint m_totalTriangleCount;
+ uint m_flippedTriangleCount;
+ uint m_zeroAreaTriangleCount;
+
+ float m_parametricArea;
+ float m_geometricArea;
+
+ float m_stretchMetric;
+ float m_maxStretchMetric;
+
+ float m_conformalMetric;
+ float m_authalicMetric;
+
+ };
+
+} // nv namespace
+
+#endif // NV_MESH_PARAMETERIZATIONQUALITY_H
diff --git a/thirdparty/thekla_atlas/nvmesh/param/SingleFaceMap.cpp b/thirdparty/thekla_atlas/nvmesh/param/SingleFaceMap.cpp
new file mode 100644
index 0000000000..4b205de8bf
--- /dev/null
+++ b/thirdparty/thekla_atlas/nvmesh/param/SingleFaceMap.cpp
@@ -0,0 +1,53 @@
+// Copyright NVIDIA Corporation 2008 -- Ignacio Castano <icastano@nvidia.com>
+
+#include "nvmesh.h" // pch
+
+#include "SingleFaceMap.h"
+
+#include "nvmesh/halfedge/Mesh.h"
+#include "nvmesh/halfedge/Vertex.h"
+#include "nvmesh/halfedge/Face.h"
+
+#include "nvmath/Vector.inl"
+
+using namespace nv;
+
+
+
+void nv::computeSingleFaceMap(HalfEdge::Mesh * mesh)
+{
+ nvDebugCheck(mesh != NULL);
+ nvDebugCheck(mesh->faceCount() == 1);
+
+ HalfEdge::Face * face = mesh->faceAt(0);
+ nvCheck(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);
+
+ uint i = 0;
+ for (HalfEdge::Face::EdgeIterator it(face->edges()); !it.isDone(); it.advance(), i++)
+ {
+ HalfEdge::Vertex * vertex = it.vertex();
+ nvCheck(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);
+ }
+ }
+}
+
diff --git a/thirdparty/thekla_atlas/nvmesh/param/SingleFaceMap.h b/thirdparty/thekla_atlas/nvmesh/param/SingleFaceMap.h
new file mode 100644
index 0000000000..b70719f5d8
--- /dev/null
+++ b/thirdparty/thekla_atlas/nvmesh/param/SingleFaceMap.h
@@ -0,0 +1,18 @@
+// Copyright NVIDIA Corporation 2008 -- Ignacio Castano <icastano@nvidia.com>
+
+#pragma once
+#ifndef NV_MESH_SINGLEFACEMAP_H
+#define NV_MESH_SINGLEFACEMAP_H
+
+namespace nv
+{
+ namespace HalfEdge
+ {
+ class Mesh;
+ }
+
+ void computeSingleFaceMap(HalfEdge::Mesh * mesh);
+
+} // nv namespace
+
+#endif // NV_MESH_SINGLEFACEMAP_H
diff --git a/thirdparty/thekla_atlas/nvmesh/param/Util.cpp b/thirdparty/thekla_atlas/nvmesh/param/Util.cpp
new file mode 100644
index 0000000000..fe7b58edf8
--- /dev/null
+++ b/thirdparty/thekla_atlas/nvmesh/param/Util.cpp
@@ -0,0 +1,326 @@
+// This code is in the public domain -- castano@gmail.com
+
+#include "nvmesh.h" // pch
+
+#include "Util.h"
+
+#include "nvmesh/halfedge/Mesh.h"
+#include "nvmesh/halfedge/Face.h"
+#include "nvmesh/halfedge/Vertex.h"
+
+#include "nvmath/Vector.inl"
+
+#include "nvcore/Array.inl"
+
+
+using namespace nv;
+
+// Determine if the given mesh is a quad mesh.
+bool nv::isQuadMesh(const HalfEdge::Mesh * mesh)
+{
+ nvDebugCheck(mesh != NULL);
+
+ const uint faceCount = mesh->faceCount();
+ for(uint i = 0; i < faceCount; i++) {
+ const HalfEdge::Face * face = mesh->faceAt(i);
+ if (face->edgeCount() != 4) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool nv::isTriangularMesh(const HalfEdge::Mesh * mesh)
+{
+ for (HalfEdge::Mesh::ConstFaceIterator it(mesh->faces()); !it.isDone(); it.advance())
+ {
+ const HalfEdge::Face * face = it.current();
+ if (face->edgeCount() != 3) return false;
+ }
+ return true;
+}
+
+
+uint nv::countMeshTriangles(const HalfEdge::Mesh * mesh)
+{
+ const uint faceCount = mesh->faceCount();
+
+ uint triangleCount = 0;
+
+ for (uint f = 0; f < faceCount; f++)
+ {
+ const HalfEdge::Face * face = mesh->faceAt(f);
+
+ uint edgeCount = face->edgeCount();
+ nvDebugCheck(edgeCount > 2);
+
+ triangleCount += edgeCount - 2;
+ }
+
+ return triangleCount;
+}
+
+const HalfEdge::Vertex * nv::findBoundaryVertex(const HalfEdge::Mesh * mesh)
+{
+ const uint vertexCount = mesh->vertexCount();
+
+ for (uint v = 0; v < vertexCount; v++)
+ {
+ const HalfEdge::Vertex * vertex = mesh->vertexAt(v);
+ if (vertex->isBoundary()) return vertex;
+ }
+
+ return NULL;
+}
+
+
+HalfEdge::Mesh * nv::unifyVertices(const HalfEdge::Mesh * inputMesh)
+{
+ HalfEdge::Mesh * mesh = new HalfEdge::Mesh;
+
+ // Only add the first colocal.
+ const uint vertexCount = inputMesh->vertexCount();
+ for (uint v = 0; v < vertexCount; v++) {
+ const HalfEdge::Vertex * vertex = inputMesh->vertexAt(v);
+
+ if (vertex->isFirstColocal()) {
+ mesh->addVertex(vertex->pos);
+ }
+ }
+
+ nv::Array<uint> indexArray;
+
+ // Add new faces pointing to first colocals.
+ uint faceCount = inputMesh->faceCount();
+ for (uint f = 0; f < faceCount; f++) {
+ const HalfEdge::Face * face = inputMesh->faceAt(f);
+
+ indexArray.clear();
+
+ for (HalfEdge::Face::ConstEdgeIterator it(face->edges()); !it.isDone(); it.advance()) {
+ const HalfEdge::Edge * edge = it.current();
+ const HalfEdge::Vertex * vertex = edge->vertex->firstColocal();
+
+ indexArray.append(vertex->id);
+ }
+
+ mesh->addFace(indexArray);
+ }
+
+ mesh->linkBoundary();
+
+ return mesh;
+}
+
+#include "nvmath/Basis.h"
+
+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.
+HalfEdge::Mesh * nv::triangulate(const HalfEdge::Mesh * inputMesh)
+{
+ HalfEdge::Mesh * mesh = new HalfEdge::Mesh;
+
+ // Add all vertices.
+ const uint vertexCount = inputMesh->vertexCount();
+ for (uint v = 0; v < vertexCount; v++) {
+ const HalfEdge::Vertex * vertex = inputMesh->vertexAt(v);
+ mesh->addVertex(vertex->pos);
+ }
+
+ Array<int> polygonVertices;
+ Array<float> polygonAngles;
+ Array<Vector2> polygonPoints;
+
+ const uint faceCount = inputMesh->faceCount();
+ for (uint f = 0; f < faceCount; f++)
+ {
+ const HalfEdge::Face * face = inputMesh->faceAt(f);
+ nvDebugCheck(face != NULL);
+
+ const uint edgeCount = face->edgeCount();
+ nvDebugCheck(edgeCount >= 3);
+
+ polygonVertices.clear();
+ polygonVertices.reserve(edgeCount);
+
+ if (edgeCount == 3) {
+ // Simple case for triangles.
+ for (HalfEdge::Face::ConstEdgeIterator it(face->edges()); !it.isDone(); it.advance())
+ {
+ const HalfEdge::Edge * edge = it.current();
+ const HalfEdge::Vertex * vertex = edge->vertex;
+ polygonVertices.append(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 (HalfEdge::Face::ConstEdgeIterator it(face->edges()); !it.isDone(); it.advance())
+ {
+ const HalfEdge::Edge * edge = it.current();
+ const HalfEdge::Vertex * vertex = edge->vertex;
+ polygonVertices.append(vertex->id);
+
+ Vector2 p;
+ p.x = dot(basis.tangent, vertex->pos);
+ p.y = dot(basis.bitangent, vertex->pos);
+
+ polygonPoints.append(p);
+ }
+ polygonAngles.resize(edgeCount);
+
+ while (polygonVertices.size() > 2) {
+ uint size = polygonVertices.size();
+
+ // Update polygon angles. @@ Update only those that have changed.
+ float minAngle = 2 * PI;
+ uint bestEar = 0; // Use first one if none of them is valid.
+ bool bestIsValid = false;
+ for (uint i = 0; i < size; i++) {
+ uint i0 = i;
+ uint i1 = (i+1) % size; // Use Sean's polygon interation trick.
+ uint i2 = (i+2) % size;
+
+ Vector2 p0 = polygonPoints[i0];
+ Vector2 p1 = polygonPoints[i1];
+ Vector2 p2 = polygonPoints[i2];
+
+ float d = clamp(dot(p0-p1, p2-p1) / (length(p0-p1) * length(p2-p1)), -1.0f, 1.0f);
+ float angle = acosf(d);
+
+ float area = triangleArea(p0, p1, p2);
+ if (area < 0.0f) angle = 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 (uint 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;
+ }
+ }
+ }
+
+ nvDebugCheck(minAngle <= 2 * PI);
+
+ // Clip best ear:
+
+ uint i0 = (bestEar+size-1) % size;
+ uint i1 = (bestEar+0) % size;
+ uint i2 = (bestEar+1) % size;
+
+ int v0 = polygonVertices[i0];
+ int v1 = polygonVertices[i1];
+ int v2 = polygonVertices[i2];
+
+ mesh->addFace(v0, v1, v2);
+
+ polygonVertices.removeAt(i1);
+ polygonPoints.removeAt(i1);
+ polygonAngles.removeAt(i1);
+ }
+ }
+
+#if 0
+
+ uint i = 0;
+ while (polygonVertices.size() > 2 && i < polygonVertices.size()) {
+ uint size = polygonVertices.size();
+ uint i0 = (i+0) % size;
+ uint i1 = (i+1) % size;
+ uint i2 = (i+2) % size;
+
+ const HalfEdge::Vertex * v0 = polygonVertices[i0];
+ const HalfEdge::Vertex * v1 = polygonVertices[i1];
+ const HalfEdge::Vertex * v2 = polygonVertices[i2];
+
+ const Vector3 p0 = v0->pos;
+ const Vector3 p1 = v1->pos;
+ const Vector3 p2 = v2->pos;
+
+ const Vector3 e0 = p2 - p1;
+ const Vector3 e1 = p0 - p1;
+
+ // If this ear forms a valid triangle, setup relations, remove v1 and repeat.
+ Vector3 n = cross(e0, e1);
+ float len = dot(fn, n); // = sin(angle)
+
+ float angle = asin(len);
+
+
+ if (len > 0.0f) {
+ mesh->addFace(v0->id(), v1->id(), v2->id());
+ polygonVertices.removeAt(i1);
+ polygonAngles.removeAt(i1);
+ if (i2 > i1) i2--;
+ // @@ Update angles at i0 and i2
+ }
+ else {
+ i++;
+ }
+ }
+
+ // @@ Create a few degenerate triangles to avoid introducing holes.
+ i = 0;
+ const uint size = polygonVertices.size();
+ while (i < size - 2) {
+ uint i0 = (i+0) % size;
+ uint i1 = (i+1) % size;
+ uint i2 = (i+2) % size;
+
+ const HalfEdge::Vertex * v0 = polygonVertices[i0];
+ const HalfEdge::Vertex * v1 = polygonVertices[i1];
+ const HalfEdge::Vertex * v2 = polygonVertices[i2];
+
+ mesh->addFace(v0->id(), v1->id(), v2->id());
+ i++;
+ }
+#endif
+ }
+
+ mesh->linkBoundary();
+
+ return mesh;
+}
+
+
diff --git a/thirdparty/thekla_atlas/nvmesh/param/Util.h b/thirdparty/thekla_atlas/nvmesh/param/Util.h
new file mode 100644
index 0000000000..774563ac0b
--- /dev/null
+++ b/thirdparty/thekla_atlas/nvmesh/param/Util.h
@@ -0,0 +1,18 @@
+// This code is in the public domain -- castano@gmail.com
+
+#include "nvmesh/nvmesh.h"
+
+namespace nv {
+
+ namespace HalfEdge { class Mesh; class Vertex; }
+
+ bool isQuadMesh(const HalfEdge::Mesh * mesh);
+ bool isTriangularMesh(const HalfEdge::Mesh * mesh);
+
+ uint countMeshTriangles(const HalfEdge::Mesh * mesh);
+ const HalfEdge::Vertex * findBoundaryVertex(const HalfEdge::Mesh * mesh);
+
+ HalfEdge::Mesh * unifyVertices(const HalfEdge::Mesh * inputMesh);
+ HalfEdge::Mesh * triangulate(const HalfEdge::Mesh * inputMesh);
+
+} // nv namespace
diff --git a/thirdparty/thekla_atlas/nvmesh/raster/ClippedTriangle.h b/thirdparty/thekla_atlas/nvmesh/raster/ClippedTriangle.h
new file mode 100644
index 0000000000..0947d4851c
--- /dev/null
+++ b/thirdparty/thekla_atlas/nvmesh/raster/ClippedTriangle.h
@@ -0,0 +1,159 @@
+// Copyright NVIDIA Corporation 2007 -- Denis Kovacs <den.kovacs@gmail.com>
+
+#pragma once
+#ifndef NV_MESH_CLIPPEDTRIANGLE_H
+#define NV_MESH_CLIPPEDTRIANGLE_H
+
+#include <nvmath/Vector.h>
+
+namespace nv
+{
+
+ 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;
+ }
+
+ uint vertexCount()
+ {
+ return m_numVertices;
+ }
+
+ const Vector2 * vertices()
+ {
+ return m_vertexBuffers[m_activeVertexBuffer];
+ }
+
+ inline 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;
+ uint p=0;
+
+ for (uint 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 (uint k=0; k<m_numVertices; k++) printf("(%f, %f)\n", v2[k].x, v2[k].y); printf("\n");
+ }
+
+ inline 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;
+ uint p=0;
+
+ for (uint 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;
+
+ //for (uint k=0; k<m_numVertices; k++) printf("(%f, %f)\n", v2[k].x, v2[k].y); printf("\n");
+ }
+
+ void computeAreaCentroid()
+ {
+ Vector2 * v = m_vertexBuffers[m_activeVertexBuffer];
+ v[m_numVertices] = v[0];
+
+ m_area = 0;
+ float centroidx=0, centroidy=0;
+ for (uint 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 * fabs(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];
+ uint m_numVertices;
+ uint m_activeVertexBuffer;
+ float m_area;
+ Vector2 m_centroid;
+ };
+
+} // nv namespace
+
+#endif // NV_MESH_CLIPPEDTRIANGLE_H
diff --git a/thirdparty/thekla_atlas/nvmesh/raster/Raster.cpp b/thirdparty/thekla_atlas/nvmesh/raster/Raster.cpp
new file mode 100644
index 0000000000..d46b34f045
--- /dev/null
+++ b/thirdparty/thekla_atlas/nvmesh/raster/Raster.cpp
@@ -0,0 +1,626 @@
+// This code is in the public domain -- castanyo@yahoo.es
+
+/** @file Raster.cpp
+ * @brief Triangle rasterization library using affine interpolation. Not
+ * specially optimized, but enough for my purposes.
+**/
+
+#include "nvmesh.h" // pch
+
+#include "Raster.h"
+#include "ClippedTriangle.h"
+
+#include "nvcore/Utils.h" // min, max
+
+#include "nvmath/Vector.inl"
+#include "nvmath/ftoi.h"
+
+
+#define RA_EPSILON 0.00001f
+
+using namespace nv;
+using namespace nv::Raster;
+
+namespace
+{
+ static inline float delta(float bot, float top, float ih)
+ {
+ return (bot - top) * ih;
+ }
+
+ static inline Vector2 delta(Vector2::Arg bot, Vector2::Arg top, float ih)
+ {
+ return (bot - top) * ih;
+ }
+
+ static inline Vector3 delta(Vector3::Arg bot, Vector3::Arg top, float ih)
+ {
+ return (bot - top) * ih;
+ }
+
+ // @@ The implementation in nvmath.h should be equivalent.
+ static inline int iround(float f)
+ {
+ // @@ Optimize this.
+ return int(floorf(f+0.5f));
+ //return int(round(f));
+ //return int(f);
+ }
+
+ /// A triangle vertex.
+ struct Vertex
+ {
+ Vector2 pos; // Position.
+ Vector3 tex; // Texcoord. (Barycentric coordinate)
+ };
+
+
+ /// 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);
+
+ bool computeDeltas();
+
+ bool draw(const Vector2 & extents, bool enableScissors, SamplingCallback cb, void * param);
+ bool drawAA(const Vector2 & extents, bool enableScissors, SamplingCallback cb, void * param);
+ bool drawC(const Vector2 & extents, bool enableScissors, SamplingCallback cb, void * param);
+ void flipBackface();
+ void computeUnitInwardNormals();
+
+ // Vertices.
+ Vector2 v1, v2, v3;
+ Vector2 n1, n2, n3; // unit inward normals
+ Vector3 t1, t2, t3;
+
+ // Deltas.
+ Vector3 dx, dy;
+
+ float sign;
+ bool valid;
+ };
+
+
+ /// Triangle ctor.
+ 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 Triangle::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 (!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;
+ }
+
+ // compute unit inward normals for each edge.
+ void Triangle::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));
+ }
+
+ void Triangle::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
+ }
+ }
+
+ bool Triangle::draw(const Vector2 & extents, bool enableScissors, SamplingCallback cb, void * param)
+ {
+ // 28.4 fixed-point coordinates
+ const int Y1 = iround(16.0f * v1.y);
+ const int Y2 = iround(16.0f * v2.y);
+ const int Y3 = iround(16.0f * v3.y);
+
+ const int X1 = iround(16.0f * v1.x);
+ const int X2 = iround(16.0f * v2.x);
+ const int X3 = iround(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 = (nv::max(min3(X1, X2, X3), frustumX0) + 0xF) >> 4;
+ miny = (nv::max(min3(Y1, Y2, Y3), frustumY0) + 0xF) >> 4;
+ maxx = (nv::min(max3(X1, X2, X3), frustumX1) + 0xF) >> 4;
+ maxy = (nv::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.
+ nvCheck(minx >= 0);
+ nvCheck(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;
+ }
+
+
+#define PX_INSIDE 1.0f/sqrt(2.0f)
+#define PX_OUTSIDE -1.0f/sqrt(2.0f)
+
+#define BK_SIZE 8
+#define BK_INSIDE sqrt(BK_SIZE*BK_SIZE/2.0f)
+#define BK_OUTSIDE -sqrt(BK_SIZE*BK_SIZE/2.0f)
+
+ // extents has to be multiple of BK_SIZE!!
+ bool Triangle::drawAA(const Vector2 & extents, bool enableScissors, SamplingCallback cb, void * param)
+ {
+ float minx, miny, maxx, maxy;
+ if (enableScissors) {
+ // Bounding rectangle
+ minx = floorf(max(min3(v1.x, v2.x, v3.x), 0.0f));
+ miny = floorf(max(min3(v1.y, v2.y, v3.y), 0.0f));
+ maxx = ceilf( min(max3(v1.x, v2.x, v3.x), extents.x-1.0f));
+ maxy = ceilf( 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 tex = t1 + dx * (x - v1.x) + dy * (y - v1.y);
+ if (!cb(param, (int)x, (int)y, tex, 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;
+ //nvCheck(texCent.x >= -0.1f && texCent.x <= 1.1f); // @@ Centroid is not very exact...
+ //nvCheck(texCent.y >= -0.1f && texCent.y <= 1.1f);
+ //nvCheck(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;
+ }
+
+} // namespace
+
+
+/// Process the given triangle.
+bool nv::Raster::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;
+}
+
+inline static float triangleArea(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);
+}
+
+/// Process the given quad.
+bool nv::Raster::drawQuad(Mode mode, Vector2::Arg extents, bool enableScissors, const Vector2 v[4], SamplingCallback cb, void * param)
+{
+ bool sign0 = triangleArea(v[0], v[1], v[2]) > 0.0f;
+ bool sign1 = triangleArea(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;
+}
+
+
+static bool drawPoint(const Vector2 & p, const Vector2 v[2], LineSamplingCallback cb, void * param) {
+
+ int x = ftoi_round(p.x);
+ int y = ftoi_round(p.y);
+ Vector2 ip = Vector2(float(x) + 0.5f, float(y) + 0.5f);
+
+ float t;
+
+ // Return minimum distance between line segment vw and point p
+ Vector2 dv = v[1] - v[0];
+ const float l2 = nv::lengthSquared(dv); // i.e. |w-v|^2 - avoid a sqrt
+ if (l2 == 0.0) {
+ t = 0; // v0 == v1 case
+ }
+ else {
+ // Consider the line extending the segment, parameterized as v + t (w - v).
+ // We find projection of point p onto the line.
+ // It falls where t = [(p-v) . (w-v)] / |w-v|^2
+ t = dot(ip - v[0], dv) / l2;
+ if (t < 0.0) {
+ t = 0; // Beyond the 'v0' end of the segment
+ }
+ else if (t > 1.0) {
+ t = 1; // Beyond the 'v1' end of the segment
+ }
+ }
+
+ Vector2 projection = v[0] + t * dv; // Projection falls on the segment
+
+ float d = distance(ip, projection);
+
+ return cb(param, x, y, t, saturate(1-d));
+}
+
+
+void nv::Raster::drawLine(bool antialias, Vector2::Arg extents, bool enableScissors, const Vector2 v[2], LineSamplingCallback cb, void * param)
+{
+ nvCheck(antialias == true); // @@ Not implemented.
+ //nvCheck(enableScissors == false); // @@ Not implemented.
+
+ // Very crappy DDA implementation.
+
+ Vector2 p = v[0];
+ Vector2 dp, dpdy;
+
+ float dx = v[1].x - v[0].x;
+ float dy = v[1].y - v[0].y;
+ int n;
+
+ // Degenerate line.
+ if (dx == 0 && dy == 0) return;
+
+ if (fabsf(dx) >= fabsf(dy)) {
+ n = iround(fabsf(dx));
+ dp.x = dx / fabsf(dx);
+ dp.y = dy / fabsf(dx);
+ nvDebugCheck(fabsf(dp.y) <= 1.0f);
+ dpdy.x = 0;
+ dpdy.y = 1;
+ }
+ else {
+ n = iround(fabs(dy));
+ dp.x = dx / fabsf(dy);
+ dp.y = dy / fabsf(dy);
+ nvDebugCheck(fabsf(dp.x) <= 1.0f);
+ dpdy.x = 1;
+ dpdy.y = 0;
+ }
+
+ for (int i = 0; i <= n; i++) {
+ drawPoint(p, v, cb, param);
+ drawPoint(p + dpdy, v, cb, param);
+ drawPoint(p - dpdy, v, cb, param);
+ p += dp;
+ }
+}
+
+
+// Draw vertical or horizontal segments. For degenerate triangles.
+/*bool nv::Raster::drawSegment(Vector2::Arg extents, bool enableScissors, const Vector2 v[2], LineSamplingCallback cb, void * param)
+{
+ nvCheck(enableScissors == false);
+
+
+ if (v[0].x == v[1].x) { // Vertical segment.
+
+ }
+ else if (v[0].y == v[1].y) { // Horizontal segment.
+ int y = ftoi_round(v[0].y);
+ int x0 = ftoi_floor(v[0].x);
+ int x1 = ftoi_floor(v[0].x);
+
+ for (int x = x0; x <= x1; x++) {
+
+ cb(param, x, y, t,
+ }
+ }
+
+ return false; // Not a valid segment.
+}
+*/
diff --git a/thirdparty/thekla_atlas/nvmesh/raster/Raster.h b/thirdparty/thekla_atlas/nvmesh/raster/Raster.h
new file mode 100644
index 0000000000..05af2ddb00
--- /dev/null
+++ b/thirdparty/thekla_atlas/nvmesh/raster/Raster.h
@@ -0,0 +1,49 @@
+// This code is in the public domain -- castanyo@yahoo.es
+
+#pragma once
+#ifndef NV_MESH_RASTER_H
+#define NV_MESH_RASTER_H
+
+/** @file Raster.h
+ * @brief Rasterization library.
+ *
+ * This is just a standard scanline rasterizer that I took from one of my old
+ * projects. The perspective correction wasn't necessary so I just removed it.
+**/
+
+#include "nvmath/Vector.h"
+#include "nvmesh/nvmesh.h"
+
+namespace nv
+{
+
+ namespace Raster
+ {
+ enum Mode {
+ Mode_Nearest,
+ Mode_Antialiased,
+ //Mode_Conservative
+ };
+
+
+ /// A callback to sample the environment. Return false to terminate rasterization.
+ typedef bool (NV_CDECL * SamplingCallback)(void * param, int x, int y, Vector3::Arg bar, Vector3::Arg dx, Vector3::Arg dy, float coverage);
+
+ // Process the given triangle. Returns false if rasterization was interrupted by the callback.
+ NVMESH_API bool drawTriangle(Mode mode, Vector2::Arg extents, bool enableScissors, const Vector2 v[3], SamplingCallback cb, void * param);
+
+ // Process the given quad. Returns false if rasterization was interrupted by the callback.
+ NVMESH_API bool drawQuad(Mode mode, Vector2::Arg extents, bool enableScissors, const Vector2 v[4], SamplingCallback cb, void * param);
+
+ typedef bool (NV_CDECL * LineSamplingCallback)(void * param, int x, int y, float t, float d); // t is the position along the segment, d is the distance to the line.
+
+ // Process the given line.
+ NVMESH_API void drawLine(bool antialias, Vector2::Arg extents, bool enableScissors, const Vector2 v[2], LineSamplingCallback cb, void * param);
+
+ // Draw vertical or horizontal segments. For degenerate triangles.
+ //NVMESH_API void drawSegment(Vector2::Arg extents, bool enableScissors, const Vector2 v[2], SamplingCallback cb, void * param);
+ }
+}
+
+
+#endif // NV_MESH_RASTER_H
diff --git a/thirdparty/thekla_atlas/nvmesh/weld/Snap.cpp b/thirdparty/thekla_atlas/nvmesh/weld/Snap.cpp
new file mode 100644
index 0000000000..b6bff4d83d
--- /dev/null
+++ b/thirdparty/thekla_atlas/nvmesh/weld/Snap.cpp
@@ -0,0 +1,100 @@
+// This code is in the public domain -- castanyo@yahoo.es
+
+#include <nvcore/RadixSort.h>
+
+#include <nvmesh/weld/Snap.h>
+#include <nvmesh/TriMesh.h>
+#include <nvmesh/geometry/Bounds.h>
+
+using namespace nv;
+
+namespace {
+
+ // Snap the given vertices.
+ void Snap(TriMesh::Vertex & a, TriMesh::Vertex & b, float texThreshold, float norThreshold)
+ {
+ a.pos = b.pos = (a.pos + b.pos) * 0.5f;
+
+ if (equal(a.tex.x, b.tex.x, texThreshold) && equal(a.tex.y, b.tex.y, texThreshold)) {
+ b.tex = a.tex = (a.tex + b.tex) * 0.5f;
+ }
+
+ if (equal(a.nor.x, b.nor.x, norThreshold) && equal(a.nor.y, b.nor.y, norThreshold) && equal(a.nor.z, b.nor.z, norThreshold)) {
+ b.nor = a.nor = (a.nor + b.nor) * 0.5f;
+ }
+ };
+
+} // nv namespace
+
+uint nv::SnapVertices(TriMesh * mesh, float posThreshold, float texThreshold, float norThreshold)
+{
+ nvDebug("--- Snapping vertices.\n");
+
+ // Determine largest axis.
+ Box box = MeshBounds::box(mesh);
+ Vector3 extents = box.extents();
+
+ int axis = 2;
+ if( extents.x > extents.y ) {
+ if( extents.x > extents.z ) {
+ axis = 0;
+ }
+ }
+ else if(extents.y > extents.z) {
+ axis = 1;
+ }
+
+ // @@ Use diagonal instead!
+
+
+ // Sort vertices according to the largest axis.
+ const uint vertexCount = mesh->vertexCount();
+ nvCheck(vertexCount > 2); // Must have at least two vertices.
+
+ // Get pos channel.
+ //PiMesh::Channel * pos_channel = mesh->GetChannel(mesh->FindChannel(VS_POS));
+ //nvCheck( pos_channel != NULL );
+
+ //const PiArray<Vec4> & pos_array = pos_channel->data;
+
+ Array<float> distArray;
+ distArray.resize(vertexCount);
+
+ for(uint v = 0; v < vertexCount; v++) {
+ if (axis == 0) distArray[v] = mesh->vertexAt(v).pos.x;
+ else if (axis == 1) distArray[v] = mesh->vertexAt(v).pos.y;
+ else distArray[v] = mesh->vertexAt(v).pos.z;
+ }
+
+ RadixSort radix;
+ const uint * xrefs = radix.sort(distArray.buffer(), distArray.count()).ranks();
+ nvCheck(xrefs != NULL);
+
+ uint snapCount = 0;
+ for(uint v = 0; v < vertexCount-1; v++) {
+ for(uint n = v+1; n < vertexCount; n++) {
+ nvDebugCheck( distArray[xrefs[v]] <= distArray[xrefs[n]] );
+
+ if (fabs(distArray[xrefs[n]] - distArray[xrefs[v]]) > posThreshold) {
+ break;
+ }
+
+ TriMesh::Vertex & v0 = mesh->vertexAt(xrefs[v]);
+ TriMesh::Vertex & v1 = mesh->vertexAt(xrefs[n]);
+
+ const float dist = length(v0.pos - v1.pos);
+
+ if (dist <= posThreshold) {
+ Snap(v0, v1, texThreshold, norThreshold);
+ snapCount++;
+ }
+ }
+ }
+
+ // @@ todo: debug, make sure that the distance between vertices is now >= threshold
+
+ nvDebug("--- %u vertices snapped\n", snapCount);
+
+ return snapCount;
+};
+
diff --git a/thirdparty/thekla_atlas/nvmesh/weld/Snap.h b/thirdparty/thekla_atlas/nvmesh/weld/Snap.h
new file mode 100644
index 0000000000..8e0566cda3
--- /dev/null
+++ b/thirdparty/thekla_atlas/nvmesh/weld/Snap.h
@@ -0,0 +1,18 @@
+// This code is in the public domain -- castanyo@yahoo.es
+
+#ifndef NV_MESH_SNAP_H
+#define NV_MESH_SNAP_H
+
+#include <nvmesh/nvmesh.h>
+#include <nvmath/nvmath.h>
+
+namespace nv
+{
+ class TriMesh;
+
+ NVMESH_API uint SnapVertices(TriMesh * mesh, float posThreshold=NV_EPSILON, float texThreshold=1.0f/1024, float norThreshold=NV_NORMAL_EPSILON);
+
+} // nv namespace
+
+
+#endif // NV_MESH_SNAP_H
diff --git a/thirdparty/thekla_atlas/nvmesh/weld/VertexWeld.cpp b/thirdparty/thekla_atlas/nvmesh/weld/VertexWeld.cpp
new file mode 100644
index 0000000000..2ba4dcae18
--- /dev/null
+++ b/thirdparty/thekla_atlas/nvmesh/weld/VertexWeld.cpp
@@ -0,0 +1,205 @@
+// Copyright NVIDIA Corporation 2006 -- Ignacio Castano <icastano@nvidia.com>
+
+#include <nvmesh/TriMesh.h>
+#include <nvmesh/QuadTriMesh.h>
+
+#include <nvmesh/weld/VertexWeld.h>
+#include <nvmesh/weld/Weld.h>
+
+using namespace nv;
+
+// Weld trimesh vertices
+void nv::WeldVertices(TriMesh * mesh)
+{
+ nvDebug("--- Welding vertices.\n");
+
+ nvCheck(mesh != NULL);
+
+ uint count = mesh->vertexCount();
+ Array<uint> xrefs;
+ Weld<TriMesh::Vertex> weld;
+ uint newCount = weld(mesh->vertices(), xrefs);
+
+ nvDebug("--- %d vertices welded\n", count - newCount);
+
+
+ // Remap faces.
+ const uint faceCount = mesh->faceCount();
+ for(uint f = 0; f < faceCount; f++)
+ {
+ TriMesh::Face & face = mesh->faceAt(f);
+ face.v[0] = xrefs[face.v[0]];
+ face.v[1] = xrefs[face.v[1]];
+ face.v[2] = xrefs[face.v[2]];
+ }
+}
+
+
+// Weld trimesh vertices
+void nv::WeldVertices(QuadTriMesh * mesh)
+{
+ nvDebug("--- Welding vertices.\n");
+
+ nvCheck(mesh != NULL);
+
+ uint count = mesh->vertexCount();
+ Array<uint> xrefs;
+ Weld<TriMesh::Vertex> weld;
+ uint newCount = weld(mesh->vertices(), xrefs);
+
+ nvDebug("--- %d vertices welded\n", count - newCount);
+
+ // Remap faces.
+ const uint faceCount = mesh->faceCount();
+ for(uint f = 0; f < faceCount; f++)
+ {
+ QuadTriMesh::Face & face = mesh->faceAt(f);
+ face.v[0] = xrefs[face.v[0]];
+ face.v[1] = xrefs[face.v[1]];
+ face.v[2] = xrefs[face.v[2]];
+
+ if (face.isQuadFace())
+ {
+ face.v[3] = xrefs[face.v[3]];
+ }
+ }
+}
+
+
+
+// OLD code
+
+#if 0
+
+namespace {
+
+struct VertexInfo {
+ uint id; ///< Original vertex id.
+ uint normal_face_group;
+ uint tangent_face_group;
+ uint material;
+ uint chart;
+};
+
+
+/// VertexInfo hash functor.
+struct VertexHash : public IHashFunctor<VertexInfo> {
+ VertexHash(PiMeshPtr m) : mesh(m) {
+ uint c = mesh->FindChannel(VS_POS);
+ piCheck(c != PI_NULL_INDEX);
+ channel = mesh->GetChannel(c);
+ piCheck(channel != NULL);
+ }
+
+ uint32 operator () (const VertexInfo & v) const {
+ return channel->data[v.id].GetHash();
+ }
+
+private:
+ PiMeshPtr mesh;
+ PiMesh::Channel * channel;
+};
+
+
+/// VertexInfo comparator.
+struct VertexEqual : public IBinaryPredicate<VertexInfo> {
+ VertexEqual(PiMeshPtr m) : mesh(m) {}
+
+ bool operator () (const VertexInfo & a, const VertexInfo & b) const {
+
+ bool equal = a.normal_face_group == b.normal_face_group &&
+ a.tangent_face_group == b.tangent_face_group &&
+ a.material == b.material &&
+ a.chart == b.chart;
+
+ // Split vertex shared by different face types.
+ if( !equal ) {
+ return false;
+ }
+
+ // They were the same vertex.
+ if( a.id == b.id ) {
+ return true;
+ }
+
+ // Vertex equal if all the channels are equal.
+ return mesh->IsVertexEqual(a.id, b.id);
+ }
+
+private:
+ PiMeshPtr mesh;
+};
+
+} // namespace
+
+
+/// Weld the vertices.
+void PiMeshVertexWeld::WeldVertices(const PiMeshSmoothGroup * mesh_smooth_group,
+ const PiMeshMaterial * mesh_material, const PiMeshAtlas * mesh_atlas )
+{
+ piDebug( "--- Welding vertices:\n" );
+
+ piDebug( "--- Expand mesh vertices.\n" );
+ PiArray<VertexInfo> vertex_array;
+
+ const uint face_num = mesh->GetFaceNum();
+ const uint vertex_max = face_num * 3;
+ vertex_array.Resize( vertex_max );
+
+ for(uint i = 0; i < vertex_max; i++) {
+
+ uint f = i/3;
+
+ const PiMesh::Face & face = mesh->GetFace(f);
+ vertex_array[i].id = face.v[i%3];
+
+ // Reset face attributes.
+ vertex_array[i].normal_face_group = PI_NULL_INDEX;
+ vertex_array[i].tangent_face_group = PI_NULL_INDEX;
+ vertex_array[i].material = PI_NULL_INDEX;
+ vertex_array[i].chart = PI_NULL_INDEX;
+
+ // Set available attributes.
+ if( mesh_smooth_group != NULL ) {
+ if( mesh_smooth_group->HasNormalFaceGroups() ) {
+ vertex_array[i].normal_face_group = mesh_smooth_group->GetNormalFaceGroup( f );
+ }
+ if( mesh_smooth_group->HasTangentFaceGroups() ) {
+ vertex_array[i].tangent_face_group = mesh_smooth_group->GetTangentFaceGroup( f );
+ }
+ }
+ if( mesh_material != NULL ) {
+ vertex_array[i].material = mesh_material->GetFaceMaterial( f );
+ }
+ if( mesh_atlas != NULL && mesh_atlas->HasCharts() ) {
+ vertex_array[i].chart = mesh_atlas->GetFaceChart( f );
+ }
+ }
+ piDebug( "--- %d vertices.\n", vertex_max );
+
+ piDebug( "--- Collapse vertices.\n" );
+
+ uint * xrefs = new uint[vertex_max];
+ VertexHash hash(mesh);
+ VertexEqual equal(mesh);
+ const uint vertex_num = Weld( vertex_array, xrefs, hash, equal );
+ piCheck(vertex_num <= vertex_max);
+ piDebug( "--- %d vertices.\n", vertex_num );
+
+ // Remap face indices.
+ piDebug( "--- Remapping face indices.\n" );
+ mesh->RemapFaceIndices(vertex_max, xrefs);
+
+
+ // Overwrite xrefs to map new vertices to old vertices.
+ for(uint v = 0; v < vertex_num; v++) {
+ xrefs[v] = vertex_array[v].id;
+ }
+
+ // Update vertex order.
+ mesh->ReorderVertices(vertex_num, xrefs);
+
+ delete [] xrefs;
+}
+
+#endif // 0
diff --git a/thirdparty/thekla_atlas/nvmesh/weld/VertexWeld.h b/thirdparty/thekla_atlas/nvmesh/weld/VertexWeld.h
new file mode 100644
index 0000000000..1dc2e4ba4d
--- /dev/null
+++ b/thirdparty/thekla_atlas/nvmesh/weld/VertexWeld.h
@@ -0,0 +1,19 @@
+// Copyright NVIDIA Corporation 2006 -- Ignacio Castano <icastano@nvidia.com>
+
+#ifndef NV_MESH_VERTEXWELD_H
+#define NV_MESH_VERTEXWELD_H
+
+#include <nvmesh/nvmesh.h>
+
+namespace nv
+{
+ class TriMesh;
+ class QuadMesh;
+
+ NVMESH_API void WeldVertices(TriMesh * mesh);
+ NVMESH_API void WeldVertices(QuadTriMesh * mesh);
+
+} // nv namespace
+
+
+#endif // NV_MESH_VERTEXWELD_H
diff --git a/thirdparty/thekla_atlas/nvmesh/weld/Weld.h b/thirdparty/thekla_atlas/nvmesh/weld/Weld.h
new file mode 100644
index 0000000000..e615539461
--- /dev/null
+++ b/thirdparty/thekla_atlas/nvmesh/weld/Weld.h
@@ -0,0 +1,171 @@
+// This code is in the public domain -- castanyo@yahoo.es
+
+#ifndef NV_MESH_WELD_H
+#define NV_MESH_WELD_H
+
+#include "nvcore/Array.h"
+#include "nvcore/Hash.h"
+#include "nvcore/Utils.h" // nextPowerOfTwo
+
+#include <string.h> // for memset, memcmp, memcpy
+
+// Weld function to remove array duplicates in linear time using hashing.
+
+namespace nv
+{
+
+/// Generic welding routine. This function welds the elements of the array p
+/// and returns the cross references in the xrefs array. To compare the elements
+/// it uses the given hash and equal functors.
+///
+/// This code is based on the ideas of Ville Miettinen and Pierre Terdiman.
+template <class T, class H=Hash<T>, class E=Equal<T> >
+struct Weld
+{
+ // xrefs maps old elements to new elements
+ uint operator()(Array<T> & p, Array<uint> & xrefs)
+ {
+ const uint N = p.size(); // # of input vertices.
+ uint outputCount = 0; // # of output vertices
+ uint hashSize = nextPowerOfTwo(N); // size of the hash table
+ uint * hashTable = new uint[hashSize + N]; // hash table + linked list
+ uint * next = hashTable + hashSize; // use bottom part as linked list
+
+ xrefs.resize(N);
+ memset( hashTable, NIL, hashSize*sizeof(uint) ); // init hash table (NIL = 0xFFFFFFFF so memset works)
+
+ H hash;
+ E equal;
+ for (uint i = 0; i < N; i++)
+ {
+ const T & e = p[i];
+ uint32 hashValue = hash(e) & (hashSize-1);
+ uint offset = hashTable[hashValue];
+
+ // traverse linked list
+ while( offset != NIL && !equal(p[offset], e) )
+ {
+ offset = next[offset];
+ }
+
+ xrefs[i] = offset;
+
+ // no match found - copy vertex & add to hash
+ if( offset == NIL )
+ {
+ // save xref
+ xrefs[i] = outputCount;
+
+ // copy element
+ p[outputCount] = e;
+
+ // link to hash table
+ next[outputCount] = hashTable[hashValue];
+
+ // update hash heads and increase output counter
+ hashTable[hashValue] = outputCount++;
+ }
+ }
+
+ // cleanup
+ delete [] hashTable;
+
+ p.resize(outputCount);
+
+ // number of output vertices
+ return outputCount;
+ }
+};
+
+
+/// Reorder the given array accoding to the indices given in xrefs.
+template <class T>
+void reorderArray(Array<T> & array, const Array<uint> & xrefs)
+{
+ const uint count = xrefs.count();
+ Array<T> new_array;
+ new_array.resize(count);
+
+ for(uint i = 0; i < count; i++) {
+ new_array[i] = array[xrefs[i]];
+ }
+
+ swap(array, new_array);
+}
+
+/// Reverse the given array so that new indices point to old indices.
+inline void reverseXRefs(Array<uint> & xrefs, uint count)
+{
+ Array<uint> new_xrefs;
+ new_xrefs.resize(count);
+
+ for(uint i = 0; i < xrefs.count(); i++) {
+ new_xrefs[xrefs[i]] = i;
+ }
+
+ swap(xrefs, new_xrefs);
+}
+
+
+
+//
+struct WeldN
+{
+ uint vertexSize;
+
+ WeldN(uint n) : vertexSize(n) {}
+
+ // xrefs maps old elements to new elements
+ uint operator()(uint8 * ptr, uint N, Array<uint> & xrefs)
+ {
+ uint outputCount = 0; // # of output vertices
+ uint hashSize = nextPowerOfTwo(N); // size of the hash table
+ uint * hashTable = new uint[hashSize + N]; // hash table + linked list
+ uint * next = hashTable + hashSize; // use bottom part as linked list
+
+ xrefs.resize(N);
+ memset( hashTable, NIL, hashSize*sizeof(uint) ); // init hash table (NIL = 0xFFFFFFFF so memset works)
+
+ for (uint i = 0; i < N; i++)
+ {
+ const uint8 * vertex = ptr + i * vertexSize;
+ uint32 hashValue = sdbmHash(vertex, vertexSize) & (hashSize-1);
+ uint offset = hashTable[hashValue];
+
+ // traverse linked list
+ while (offset != NIL && memcmp(ptr + offset * vertexSize, vertex, vertexSize) != 0)
+ {
+ offset = next[offset];
+ }
+
+ xrefs[i] = offset;
+
+ // no match found - copy vertex & add to hash
+ if (offset == NIL)
+ {
+ // save xref
+ xrefs[i] = outputCount;
+
+ // copy element
+ memcpy(ptr + outputCount * vertexSize, vertex, vertexSize);
+
+ // link to hash table
+ next[outputCount] = hashTable[hashValue];
+
+ // update hash heads and increase output counter
+ hashTable[hashValue] = outputCount++;
+ }
+ }
+
+ // cleanup
+ delete [] hashTable;
+
+ // number of output vertices
+ return outputCount;
+ }
+};
+
+
+} // nv namespace
+
+#endif // NV_MESH_WELD_H