summaryrefslogtreecommitdiff
path: root/thirdparty/thekla_atlas/nvmesh/param/ParameterizationQuality.cpp
blob: 683ee603cdcdf0487d52ae0443f16a71a4dc5838 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
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);
}