Coverage Report

Created: 2025-08-29 07:11

/src/PROJ/src/transformations/tinshift_impl.hpp
Line
Count
Source (jump to first uncovered line)
1
/******************************************************************************
2
 * Project:  PROJ
3
 * Purpose:  Functionality related to TIN based transformations
4
 * Author:   Even Rouault, <even.rouault at spatialys.com>
5
 *
6
 ******************************************************************************
7
 * Copyright (c) 2020, Even Rouault, <even.rouault at spatialys.com>
8
 *
9
 * Permission is hereby granted, free of charge, to any person obtaining a
10
 * copy of this software and associated documentation files (the "Software"),
11
 * to deal in the Software without restriction, including without limitation
12
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
13
 * and/or sell copies of the Software, and to permit persons to whom the
14
 * Software is furnished to do so, subject to the following conditions:
15
 *
16
 * The above copyright notice and this permission notice shall be included
17
 * in all copies or substantial portions of the Software.
18
 *
19
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
20
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
22
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
24
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
25
 * DEALINGS IN THE SOFTWARE.
26
 *****************************************************************************/
27
28
#ifndef TINSHIFT_NAMESPACE
29
#error "Should be included only by tinshift.hpp"
30
#endif
31
32
#include <algorithm>
33
#include <cmath>
34
#include <limits>
35
36
namespace TINSHIFT_NAMESPACE {
37
38
// ---------------------------------------------------------------------------
39
40
0
static std::string getString(const json &j, const char *key, bool optional) {
41
0
    if (!j.contains(key)) {
42
0
        if (optional) {
43
0
            return std::string();
44
0
        }
45
0
        throw ParsingException(std::string("Missing \"") + key + "\" key");
46
0
    }
47
0
    const json v = j[key];
48
0
    if (!v.is_string()) {
49
0
        throw ParsingException(std::string("The value of \"") + key +
50
0
                               "\" should be a string");
51
0
    }
52
0
    return v.get<std::string>();
53
0
}
54
55
0
static std::string getReqString(const json &j, const char *key) {
56
0
    return getString(j, key, false);
57
0
}
58
59
0
static std::string getOptString(const json &j, const char *key) {
60
0
    return getString(j, key, true);
61
0
}
62
63
// ---------------------------------------------------------------------------
64
65
0
static json getArrayMember(const json &j, const char *key) {
66
0
    if (!j.contains(key)) {
67
0
        throw ParsingException(std::string("Missing \"") + key + "\" key");
68
0
    }
69
0
    const json obj = j[key];
70
0
    if (!obj.is_array()) {
71
0
        throw ParsingException(std::string("The value of \"") + key +
72
0
                               "\" should be a array");
73
0
    }
74
0
    return obj;
75
0
}
76
77
// ---------------------------------------------------------------------------
78
79
0
std::unique_ptr<TINShiftFile> TINShiftFile::parse(const std::string &text) {
80
0
    std::unique_ptr<TINShiftFile> tinshiftFile(new TINShiftFile());
81
0
    json j;
82
0
    try {
83
0
        j = json::parse(text);
84
0
    } catch (const std::exception &e) {
85
0
        throw ParsingException(e.what());
86
0
    }
87
0
    if (!j.is_object()) {
88
0
        throw ParsingException("Not an object");
89
0
    }
90
0
    tinshiftFile->mFileType = getReqString(j, "file_type");
91
0
    tinshiftFile->mFormatVersion = getReqString(j, "format_version");
92
0
    tinshiftFile->mName = getOptString(j, "name");
93
0
    tinshiftFile->mVersion = getOptString(j, "version");
94
0
    tinshiftFile->mLicense = getOptString(j, "license");
95
0
    tinshiftFile->mDescription = getOptString(j, "description");
96
0
    tinshiftFile->mPublicationDate = getOptString(j, "publication_date");
97
98
0
    tinshiftFile->mFallbackStrategy = FALLBACK_NONE;
99
0
    if (j.contains("fallback_strategy")) {
100
0
        if (tinshiftFile->mFormatVersion != "1.1") {
101
0
            throw ParsingException(
102
0
                "fallback_strategy needs format_version 1.1");
103
0
        }
104
0
        const auto fallback_strategy = getOptString(j, "fallback_strategy");
105
0
        if (fallback_strategy == "nearest_side") {
106
0
            tinshiftFile->mFallbackStrategy = FALLBACK_NEAREST_SIDE;
107
0
        } else if (fallback_strategy == "nearest_centroid") {
108
0
            tinshiftFile->mFallbackStrategy = FALLBACK_NEAREST_CENTROID;
109
0
        } else if (fallback_strategy == "none") {
110
0
            tinshiftFile->mFallbackStrategy = FALLBACK_NONE;
111
0
        } else {
112
0
            throw ParsingException("invalid fallback_strategy");
113
0
        }
114
0
    }
115
116
0
    if (j.contains("authority")) {
117
0
        const json jAuthority = j["authority"];
118
0
        if (!jAuthority.is_object()) {
119
0
            throw ParsingException("authority is not a object");
120
0
        }
121
0
        tinshiftFile->mAuthority.name = getOptString(jAuthority, "name");
122
0
        tinshiftFile->mAuthority.url = getOptString(jAuthority, "url");
123
0
        tinshiftFile->mAuthority.address = getOptString(jAuthority, "address");
124
0
        tinshiftFile->mAuthority.email = getOptString(jAuthority, "email");
125
0
    }
126
127
0
    if (j.contains("links")) {
128
0
        const json jLinks = j["links"];
129
0
        if (!jLinks.is_array()) {
130
0
            throw ParsingException("links is not an array");
131
0
        }
132
0
        for (const json &jLink : jLinks) {
133
0
            if (!jLink.is_object()) {
134
0
                throw ParsingException("links[] item is not an object");
135
0
            }
136
0
            Link link;
137
0
            link.href = getOptString(jLink, "href");
138
0
            link.rel = getOptString(jLink, "rel");
139
0
            link.type = getOptString(jLink, "type");
140
0
            link.title = getOptString(jLink, "title");
141
0
            tinshiftFile->mLinks.emplace_back(std::move(link));
142
0
        }
143
0
    }
144
0
    tinshiftFile->mInputCRS = getOptString(j, "input_crs");
145
0
    tinshiftFile->mOutputCRS = getOptString(j, "output_crs");
146
147
0
    const auto jTransformedComponents =
148
0
        getArrayMember(j, "transformed_components");
149
0
    for (const json &jComp : jTransformedComponents) {
150
0
        if (!jComp.is_string()) {
151
0
            throw ParsingException(
152
0
                "transformed_components[] item is not a string");
153
0
        }
154
0
        const auto jCompStr = jComp.get<std::string>();
155
0
        if (jCompStr == "horizontal") {
156
0
            tinshiftFile->mTransformHorizontalComponent = true;
157
0
        } else if (jCompStr == "vertical") {
158
0
            tinshiftFile->mTransformVerticalComponent = true;
159
0
        } else {
160
0
            throw ParsingException("transformed_components[] = " + jCompStr +
161
0
                                   " is not handled");
162
0
        }
163
0
    }
164
165
0
    const auto jVerticesColumns = getArrayMember(j, "vertices_columns");
166
0
    int sourceXCol = -1;
167
0
    int sourceYCol = -1;
168
0
    int sourceZCol = -1;
169
0
    int targetXCol = -1;
170
0
    int targetYCol = -1;
171
0
    int targetZCol = -1;
172
0
    int offsetZCol = -1;
173
0
    for (size_t i = 0; i < jVerticesColumns.size(); ++i) {
174
0
        const json &jColumn = jVerticesColumns[i];
175
0
        if (!jColumn.is_string()) {
176
0
            throw ParsingException("vertices_columns[] item is not a string");
177
0
        }
178
0
        const auto jColumnStr = jColumn.get<std::string>();
179
0
        if (jColumnStr == "source_x") {
180
0
            sourceXCol = static_cast<int>(i);
181
0
        } else if (jColumnStr == "source_y") {
182
0
            sourceYCol = static_cast<int>(i);
183
0
        } else if (jColumnStr == "source_z") {
184
0
            sourceZCol = static_cast<int>(i);
185
0
        } else if (jColumnStr == "target_x") {
186
0
            targetXCol = static_cast<int>(i);
187
0
        } else if (jColumnStr == "target_y") {
188
0
            targetYCol = static_cast<int>(i);
189
0
        } else if (jColumnStr == "target_z") {
190
0
            targetZCol = static_cast<int>(i);
191
0
        } else if (jColumnStr == "offset_z") {
192
0
            offsetZCol = static_cast<int>(i);
193
0
        }
194
0
    }
195
0
    if (sourceXCol < 0) {
196
0
        throw ParsingException(
197
0
            "source_x must be specified in vertices_columns[]");
198
0
    }
199
0
    if (sourceYCol < 0) {
200
0
        throw ParsingException(
201
0
            "source_y must be specified in vertices_columns[]");
202
0
    }
203
0
    if (tinshiftFile->mTransformHorizontalComponent) {
204
0
        if (targetXCol < 0) {
205
0
            throw ParsingException(
206
0
                "target_x must be specified in vertices_columns[]");
207
0
        }
208
0
        if (targetYCol < 0) {
209
0
            throw ParsingException(
210
0
                "target_y must be specified in vertices_columns[]");
211
0
        }
212
0
    }
213
0
    if (tinshiftFile->mTransformVerticalComponent) {
214
0
        if (offsetZCol >= 0) {
215
            // do nothing
216
0
        } else {
217
0
            if (sourceZCol < 0) {
218
0
                throw ParsingException("source_z or delta_z must be specified "
219
0
                                       "in vertices_columns[]");
220
0
            }
221
0
            if (targetZCol < 0) {
222
0
                throw ParsingException(
223
0
                    "target_z must be specified in vertices_columns[]");
224
0
            }
225
0
        }
226
0
    }
227
228
0
    const auto jTrianglesColumns = getArrayMember(j, "triangles_columns");
229
0
    int idxVertex1Col = -1;
230
0
    int idxVertex2Col = -1;
231
0
    int idxVertex3Col = -1;
232
0
    for (size_t i = 0; i < jTrianglesColumns.size(); ++i) {
233
0
        const json &jColumn = jTrianglesColumns[i];
234
0
        if (!jColumn.is_string()) {
235
0
            throw ParsingException("triangles_columns[] item is not a string");
236
0
        }
237
0
        const auto jColumnStr = jColumn.get<std::string>();
238
0
        if (jColumnStr == "idx_vertex1") {
239
0
            idxVertex1Col = static_cast<int>(i);
240
0
        } else if (jColumnStr == "idx_vertex2") {
241
0
            idxVertex2Col = static_cast<int>(i);
242
0
        } else if (jColumnStr == "idx_vertex3") {
243
0
            idxVertex3Col = static_cast<int>(i);
244
0
        }
245
0
    }
246
0
    if (idxVertex1Col < 0) {
247
0
        throw ParsingException(
248
0
            "idx_vertex1 must be specified in triangles_columns[]");
249
0
    }
250
0
    if (idxVertex2Col < 0) {
251
0
        throw ParsingException(
252
0
            "idx_vertex2 must be specified in triangles_columns[]");
253
0
    }
254
0
    if (idxVertex3Col < 0) {
255
0
        throw ParsingException(
256
0
            "idx_vertex3 must be specified in triangles_columns[]");
257
0
    }
258
259
0
    const auto jVertices = getArrayMember(j, "vertices");
260
0
    tinshiftFile->mVerticesColumnCount = 2;
261
0
    if (tinshiftFile->mTransformHorizontalComponent)
262
0
        tinshiftFile->mVerticesColumnCount += 2;
263
0
    if (tinshiftFile->mTransformVerticalComponent)
264
0
        tinshiftFile->mVerticesColumnCount += 1;
265
266
0
    tinshiftFile->mVertices.reserve(tinshiftFile->mVerticesColumnCount *
267
0
                                    jVertices.size());
268
0
    for (const auto &jVertex : jVertices) {
269
0
        if (!jVertex.is_array()) {
270
0
            throw ParsingException("vertices[] item is not an array");
271
0
        }
272
0
        if (jVertex.size() != jVerticesColumns.size()) {
273
0
            throw ParsingException(
274
0
                "vertices[] item has not expected number of elements");
275
0
        }
276
0
        if (!jVertex[sourceXCol].is_number()) {
277
0
            throw ParsingException("vertices[][] item is not a number");
278
0
        }
279
0
        tinshiftFile->mVertices.push_back(jVertex[sourceXCol].get<double>());
280
0
        if (!jVertex[sourceYCol].is_number()) {
281
0
            throw ParsingException("vertices[][] item is not a number");
282
0
        }
283
0
        tinshiftFile->mVertices.push_back(jVertex[sourceYCol].get<double>());
284
0
        if (tinshiftFile->mTransformHorizontalComponent) {
285
0
            if (!jVertex[targetXCol].is_number()) {
286
0
                throw ParsingException("vertices[][] item is not a number");
287
0
            }
288
0
            tinshiftFile->mVertices.push_back(
289
0
                jVertex[targetXCol].get<double>());
290
0
            if (!jVertex[targetYCol].is_number()) {
291
0
                throw ParsingException("vertices[][] item is not a number");
292
0
            }
293
0
            tinshiftFile->mVertices.push_back(
294
0
                jVertex[targetYCol].get<double>());
295
0
        }
296
0
        if (tinshiftFile->mTransformVerticalComponent) {
297
0
            if (offsetZCol >= 0) {
298
0
                if (!jVertex[offsetZCol].is_number()) {
299
0
                    throw ParsingException("vertices[][] item is not a number");
300
0
                }
301
0
                tinshiftFile->mVertices.push_back(
302
0
                    jVertex[offsetZCol].get<double>());
303
0
            } else {
304
0
                if (!jVertex[sourceZCol].is_number()) {
305
0
                    throw ParsingException("vertices[][] item is not a number");
306
0
                }
307
0
                const double sourceZ = jVertex[sourceZCol].get<double>();
308
0
                if (!jVertex[targetZCol].is_number()) {
309
0
                    throw ParsingException("vertices[][] item is not a number");
310
0
                }
311
0
                const double targetZ = jVertex[targetZCol].get<double>();
312
0
                tinshiftFile->mVertices.push_back(targetZ - sourceZ);
313
0
            }
314
0
        }
315
0
    }
316
317
0
    const auto jTriangles = getArrayMember(j, "triangles");
318
0
    tinshiftFile->mTriangles.reserve(jTriangles.size());
319
0
    for (const auto &jTriangle : jTriangles) {
320
0
        if (!jTriangle.is_array()) {
321
0
            throw ParsingException("triangles[] item is not an array");
322
0
        }
323
0
        if (jTriangle.size() != jTrianglesColumns.size()) {
324
0
            throw ParsingException(
325
0
                "triangles[] item has not expected number of elements");
326
0
        }
327
328
0
        if (jTriangle[idxVertex1Col].type() != json::value_t::number_unsigned) {
329
0
            throw ParsingException("triangles[][] item is not an integer");
330
0
        }
331
0
        const unsigned vertex1 = jTriangle[idxVertex1Col].get<unsigned>();
332
0
        if (vertex1 >= jVertices.size()) {
333
0
            throw ParsingException("Invalid value for a vertex index");
334
0
        }
335
336
0
        if (jTriangle[idxVertex2Col].type() != json::value_t::number_unsigned) {
337
0
            throw ParsingException("triangles[][] item is not an integer");
338
0
        }
339
0
        const unsigned vertex2 = jTriangle[idxVertex2Col].get<unsigned>();
340
0
        if (vertex2 >= jVertices.size()) {
341
0
            throw ParsingException("Invalid value for a vertex index");
342
0
        }
343
344
0
        if (jTriangle[idxVertex3Col].type() != json::value_t::number_unsigned) {
345
0
            throw ParsingException("triangles[][] item is not an integer");
346
0
        }
347
0
        const unsigned vertex3 = jTriangle[idxVertex3Col].get<unsigned>();
348
0
        if (vertex3 >= jVertices.size()) {
349
0
            throw ParsingException("Invalid value for a vertex index");
350
0
        }
351
352
0
        VertexIndices vi;
353
0
        vi.idx1 = vertex1;
354
0
        vi.idx2 = vertex2;
355
0
        vi.idx3 = vertex3;
356
0
        tinshiftFile->mTriangles.push_back(vi);
357
0
    }
358
359
0
    return tinshiftFile;
360
0
}
361
362
// ---------------------------------------------------------------------------
363
364
static NS_PROJ::QuadTree::RectObj GetBounds(const TINShiftFile &file,
365
0
                                            bool forward) {
366
0
    NS_PROJ::QuadTree::RectObj rect;
367
0
    rect.minx = std::numeric_limits<double>::max();
368
0
    rect.miny = std::numeric_limits<double>::max();
369
0
    rect.maxx = -std::numeric_limits<double>::max();
370
0
    rect.maxy = -std::numeric_limits<double>::max();
371
0
    const auto &vertices = file.vertices();
372
0
    const unsigned colCount = file.verticesColumnCount();
373
0
    const int idxX = file.transformHorizontalComponent() && !forward ? 2 : 0;
374
0
    const int idxY = file.transformHorizontalComponent() && !forward ? 3 : 1;
375
0
    for (size_t i = 0; i + colCount - 1 < vertices.size(); i += colCount) {
376
0
        const double x = vertices[i + idxX];
377
0
        const double y = vertices[i + idxY];
378
0
        rect.minx = std::min(rect.minx, x);
379
0
        rect.miny = std::min(rect.miny, y);
380
0
        rect.maxx = std::max(rect.maxx, x);
381
0
        rect.maxy = std::max(rect.maxy, y);
382
0
    }
383
0
    return rect;
384
0
}
385
386
// ---------------------------------------------------------------------------
387
388
static std::unique_ptr<NS_PROJ::QuadTree::QuadTree<unsigned>>
389
0
BuildQuadTree(const TINShiftFile &file, bool forward) {
390
0
    auto quadtree = std::unique_ptr<NS_PROJ::QuadTree::QuadTree<unsigned>>(
391
0
        new NS_PROJ::QuadTree::QuadTree<unsigned>(GetBounds(file, forward)));
392
0
    const auto &triangles = file.triangles();
393
0
    const auto &vertices = file.vertices();
394
0
    const int idxX = file.transformHorizontalComponent() && !forward ? 2 : 0;
395
0
    const int idxY = file.transformHorizontalComponent() && !forward ? 3 : 1;
396
0
    const unsigned colCount = file.verticesColumnCount();
397
0
    for (size_t i = 0; i < triangles.size(); ++i) {
398
0
        const unsigned i1 = triangles[i].idx1;
399
0
        const unsigned i2 = triangles[i].idx2;
400
0
        const unsigned i3 = triangles[i].idx3;
401
0
        const double x1 = vertices[i1 * colCount + idxX];
402
0
        const double y1 = vertices[i1 * colCount + idxY];
403
0
        const double x2 = vertices[i2 * colCount + idxX];
404
0
        const double y2 = vertices[i2 * colCount + idxY];
405
0
        const double x3 = vertices[i3 * colCount + idxX];
406
0
        const double y3 = vertices[i3 * colCount + idxY];
407
0
        NS_PROJ::QuadTree::RectObj rect;
408
0
        rect.minx = x1;
409
0
        rect.miny = y1;
410
0
        rect.maxx = x1;
411
0
        rect.maxy = y1;
412
0
        rect.minx = std::min(rect.minx, x2);
413
0
        rect.miny = std::min(rect.miny, y2);
414
0
        rect.maxx = std::max(rect.maxx, x2);
415
0
        rect.maxy = std::max(rect.maxy, y2);
416
0
        rect.minx = std::min(rect.minx, x3);
417
0
        rect.miny = std::min(rect.miny, y3);
418
0
        rect.maxx = std::max(rect.maxx, x3);
419
0
        rect.maxy = std::max(rect.maxy, y3);
420
0
        quadtree->insert(static_cast<unsigned>(i), rect);
421
0
    }
422
423
0
    return quadtree;
424
0
}
425
426
// ---------------------------------------------------------------------------
427
428
Evaluator::Evaluator(std::unique_ptr<TINShiftFile> &&fileIn)
429
0
    : mFile(std::move(fileIn)) {}
430
431
// ---------------------------------------------------------------------------
432
433
0
static inline double sqr(double x) { return x * x; }
434
static inline double squared_distance(double x1, double y1, double x2,
435
0
                                      double y2) {
436
0
    return sqr(x1 - x2) + sqr(y1 - y2);
437
0
}
438
static double distance_point_segment(double x, double y, double x1, double y1,
439
0
                                     double x2, double y2, double dist12) {
440
    // squared distance of point x/y to line segment x1/y1 -- x2/y2
441
0
    double t = ((x - x1) * (x2 - x1) + (y - y1) * (y2 - y1)) / dist12;
442
0
    if (t <= 0.0) {
443
        // closest to x1/y1
444
0
        return squared_distance(x, y, x1, y1);
445
0
    }
446
0
    if (t >= 1.0) {
447
        // closest to y2/y2
448
0
        return squared_distance(x, y, x2, y2);
449
0
    }
450
451
    // closest to line segment x1/y1 -- x2/y2
452
0
    return squared_distance(x, y, x1 + t * (x2 - x1), y1 + t * (y2 - y1));
453
0
}
454
455
static const TINShiftFile::VertexIndices *
456
FindTriangle(const TINShiftFile &file,
457
             const NS_PROJ::QuadTree::QuadTree<unsigned> &quadtree,
458
             std::vector<unsigned> &triangleIndices, double x, double y,
459
0
             bool forward, double &lambda1, double &lambda2, double &lambda3) {
460
0
#define USE_QUADTREE
461
0
#ifdef USE_QUADTREE
462
0
    triangleIndices.clear();
463
0
    quadtree.search(x, y, triangleIndices);
464
0
#endif
465
0
    const auto &triangles = file.triangles();
466
0
    const auto &vertices = file.vertices();
467
0
    constexpr double EPS = 1e-10;
468
0
    const int idxX = file.transformHorizontalComponent() && !forward ? 2 : 0;
469
0
    const int idxY = file.transformHorizontalComponent() && !forward ? 3 : 1;
470
0
    const unsigned colCount = file.verticesColumnCount();
471
0
#ifdef USE_QUADTREE
472
0
    for (unsigned i : triangleIndices)
473
#else
474
    for (size_t i = 0; i < triangles.size(); ++i)
475
#endif
476
0
    {
477
0
        const auto &triangle = triangles[i];
478
0
        const unsigned i1 = triangle.idx1;
479
0
        const unsigned i2 = triangle.idx2;
480
0
        const unsigned i3 = triangle.idx3;
481
0
        const double x1 = vertices[i1 * colCount + idxX];
482
0
        const double y1 = vertices[i1 * colCount + idxY];
483
0
        const double x2 = vertices[i2 * colCount + idxX];
484
0
        const double y2 = vertices[i2 * colCount + idxY];
485
0
        const double x3 = vertices[i3 * colCount + idxX];
486
0
        const double y3 = vertices[i3 * colCount + idxY];
487
0
        const double det_T = (y2 - y3) * (x1 - x3) + (x3 - x2) * (y1 - y3);
488
0
        lambda1 = ((y2 - y3) * (x - x3) + (x3 - x2) * (y - y3)) / det_T;
489
0
        lambda2 = ((y3 - y1) * (x - x3) + (x1 - x3) * (y - y3)) / det_T;
490
0
        if (lambda1 >= -EPS && lambda1 <= 1 + EPS && lambda2 >= -EPS &&
491
0
            lambda2 <= 1 + EPS) {
492
0
            lambda3 = 1 - lambda1 - lambda2;
493
0
            if (lambda3 >= 0) {
494
0
                return &triangle;
495
0
            }
496
0
        }
497
0
    }
498
0
    if (file.fallbackStrategy() == FALLBACK_NONE) {
499
0
        return nullptr;
500
0
    }
501
    // find triangle with the shortest squared distance
502
    //
503
    // TODO: extend quadtree to support nearest neighbor search
504
0
    double closest_dist = std::numeric_limits<double>::infinity();
505
0
    double closest_dist2 = std::numeric_limits<double>::infinity();
506
0
    size_t closest_i = 0;
507
0
    for (size_t i = 0; i < triangles.size(); ++i) {
508
0
        const auto &triangle = triangles[i];
509
0
        const unsigned i1 = triangle.idx1;
510
0
        const unsigned i2 = triangle.idx2;
511
0
        const unsigned i3 = triangle.idx3;
512
0
        const double x1 = vertices[i1 * colCount + idxX];
513
0
        const double y1 = vertices[i1 * colCount + idxY];
514
0
        const double x2 = vertices[i2 * colCount + idxX];
515
0
        const double y2 = vertices[i2 * colCount + idxY];
516
0
        const double x3 = vertices[i3 * colCount + idxX];
517
0
        const double y3 = vertices[i3 * colCount + idxY];
518
519
        // don't check this triangle if the query point plusminus the
520
        // currently closest found distance is outside the triangle's AABB
521
0
        if (x + closest_dist < std::min(x1, std::min(x2, x3)) ||
522
0
            x - closest_dist > std::max(x1, std::max(x2, x3)) ||
523
0
            y + closest_dist < std::min(y1, std::min(y2, y3)) ||
524
0
            y - closest_dist > std::max(y1, std::max(y2, y3))) {
525
0
            continue;
526
0
        }
527
528
0
        double dist12 = squared_distance(x1, y1, x2, y2);
529
0
        double dist23 = squared_distance(x2, y2, x3, y3);
530
0
        double dist13 = squared_distance(x1, y1, x3, y3);
531
0
        if (dist12 < EPS || dist23 < EPS || dist13 < EPS) {
532
            // do not use degenerate triangles
533
0
            continue;
534
0
        }
535
0
        double dist2;
536
0
        if (file.fallbackStrategy() == FALLBACK_NEAREST_SIDE) {
537
            // we don't know whether the points of the triangle are given
538
            // clockwise or counter-clockwise, so we have to check the distance
539
            // of the point to all three sides of the triangle
540
0
            dist2 = distance_point_segment(x, y, x1, y1, x2, y2, dist12);
541
0
            if (dist2 < closest_dist2) {
542
0
                closest_dist2 = dist2;
543
0
                closest_dist = sqrt(dist2);
544
0
                closest_i = i;
545
0
            }
546
0
            dist2 = distance_point_segment(x, y, x2, y2, x3, y3, dist23);
547
0
            if (dist2 < closest_dist2) {
548
0
                closest_dist2 = dist2;
549
0
                closest_dist = sqrt(dist2);
550
0
                closest_i = i;
551
0
            }
552
0
            dist2 = distance_point_segment(x, y, x1, y1, x3, y3, dist13);
553
0
            if (dist2 < closest_dist2) {
554
0
                closest_dist2 = dist2;
555
0
                closest_dist = sqrt(dist2);
556
0
                closest_i = i;
557
0
            }
558
0
        } else if (file.fallbackStrategy() == FALLBACK_NEAREST_CENTROID) {
559
0
            double c_x = (x1 + x2 + x3) / 3.0;
560
0
            double c_y = (y1 + y2 + y3) / 3.0;
561
0
            dist2 = squared_distance(x, y, c_x, c_y);
562
0
            if (dist2 < closest_dist2) {
563
0
                closest_dist2 = dist2;
564
0
                closest_dist = sqrt(dist2);
565
0
                closest_i = i;
566
0
            }
567
0
        }
568
0
    }
569
0
    if (std::isinf(closest_dist)) {
570
        // nothing was found due to empty triangle list or only degenerate
571
        // triangles
572
0
        return nullptr;
573
0
    }
574
0
    const auto &triangle = triangles[closest_i];
575
0
    const unsigned i1 = triangle.idx1;
576
0
    const unsigned i2 = triangle.idx2;
577
0
    const unsigned i3 = triangle.idx3;
578
0
    const double x1 = vertices[i1 * colCount + idxX];
579
0
    const double y1 = vertices[i1 * colCount + idxY];
580
0
    const double x2 = vertices[i2 * colCount + idxX];
581
0
    const double y2 = vertices[i2 * colCount + idxY];
582
0
    const double x3 = vertices[i3 * colCount + idxX];
583
0
    const double y3 = vertices[i3 * colCount + idxY];
584
0
    const double det_T = (y2 - y3) * (x1 - x3) + (x3 - x2) * (y1 - y3);
585
0
    if (std::fabs(det_T) < EPS) {
586
        // the nearest triangle is degenerate
587
0
        return nullptr;
588
0
    }
589
0
    lambda1 = ((y2 - y3) * (x - x3) + (x3 - x2) * (y - y3)) / det_T;
590
0
    lambda2 = ((y3 - y1) * (x - x3) + (x1 - x3) * (y - y3)) / det_T;
591
0
    lambda3 = 1 - lambda1 - lambda2;
592
0
    return &triangle;
593
0
}
594
595
// ---------------------------------------------------------------------------
596
597
bool Evaluator::forward(double x, double y, double z, double &x_out,
598
0
                        double &y_out, double &z_out) {
599
0
    if (!mQuadTreeForward)
600
0
        mQuadTreeForward = BuildQuadTree(*(mFile.get()), true);
601
602
0
    double lambda1 = 0.0;
603
0
    double lambda2 = 0.0;
604
0
    double lambda3 = 0.0;
605
0
    const auto *triangle =
606
0
        FindTriangle(*mFile, *mQuadTreeForward, mTriangleIndices, x, y, true,
607
0
                     lambda1, lambda2, lambda3);
608
0
    if (!triangle)
609
0
        return false;
610
0
    const auto &vertices = mFile->vertices();
611
0
    const unsigned i1 = triangle->idx1;
612
0
    const unsigned i2 = triangle->idx2;
613
0
    const unsigned i3 = triangle->idx3;
614
0
    const unsigned colCount = mFile->verticesColumnCount();
615
0
    if (mFile->transformHorizontalComponent()) {
616
0
        constexpr unsigned idxTargetColX = 2;
617
0
        constexpr unsigned idxTargetColY = 3;
618
0
        x_out = vertices[i1 * colCount + idxTargetColX] * lambda1 +
619
0
                vertices[i2 * colCount + idxTargetColX] * lambda2 +
620
0
                vertices[i3 * colCount + idxTargetColX] * lambda3;
621
0
        y_out = vertices[i1 * colCount + idxTargetColY] * lambda1 +
622
0
                vertices[i2 * colCount + idxTargetColY] * lambda2 +
623
0
                vertices[i3 * colCount + idxTargetColY] * lambda3;
624
0
    } else {
625
0
        x_out = x;
626
0
        y_out = y;
627
0
    }
628
0
    if (mFile->transformVerticalComponent()) {
629
0
        const int idxCol = mFile->transformHorizontalComponent() ? 4 : 2;
630
0
        z_out = z + (vertices[i1 * colCount + idxCol] * lambda1 +
631
0
                     vertices[i2 * colCount + idxCol] * lambda2 +
632
0
                     vertices[i3 * colCount + idxCol] * lambda3);
633
0
    } else {
634
0
        z_out = z;
635
0
    }
636
0
    return true;
637
0
}
638
639
// ---------------------------------------------------------------------------
640
641
bool Evaluator::inverse(double x, double y, double z, double &x_out,
642
0
                        double &y_out, double &z_out) {
643
0
    NS_PROJ::QuadTree::QuadTree<unsigned> *quadtree;
644
0
    if (!mFile->transformHorizontalComponent() &&
645
0
        mFile->transformVerticalComponent()) {
646
0
        if (!mQuadTreeForward)
647
0
            mQuadTreeForward = BuildQuadTree(*(mFile.get()), true);
648
0
        quadtree = mQuadTreeForward.get();
649
0
    } else {
650
0
        if (!mQuadTreeInverse)
651
0
            mQuadTreeInverse = BuildQuadTree(*(mFile.get()), false);
652
0
        quadtree = mQuadTreeInverse.get();
653
0
    }
654
655
0
    double lambda1 = 0.0;
656
0
    double lambda2 = 0.0;
657
0
    double lambda3 = 0.0;
658
0
    const auto *triangle = FindTriangle(*mFile, *quadtree, mTriangleIndices, x,
659
0
                                        y, false, lambda1, lambda2, lambda3);
660
0
    if (!triangle)
661
0
        return false;
662
0
    const auto &vertices = mFile->vertices();
663
0
    const unsigned i1 = triangle->idx1;
664
0
    const unsigned i2 = triangle->idx2;
665
0
    const unsigned i3 = triangle->idx3;
666
0
    const unsigned colCount = mFile->verticesColumnCount();
667
0
    if (mFile->transformHorizontalComponent()) {
668
0
        constexpr unsigned idxTargetColX = 0;
669
0
        constexpr unsigned idxTargetColY = 1;
670
0
        x_out = vertices[i1 * colCount + idxTargetColX] * lambda1 +
671
0
                vertices[i2 * colCount + idxTargetColX] * lambda2 +
672
0
                vertices[i3 * colCount + idxTargetColX] * lambda3;
673
0
        y_out = vertices[i1 * colCount + idxTargetColY] * lambda1 +
674
0
                vertices[i2 * colCount + idxTargetColY] * lambda2 +
675
0
                vertices[i3 * colCount + idxTargetColY] * lambda3;
676
0
    } else {
677
0
        x_out = x;
678
0
        y_out = y;
679
0
    }
680
0
    if (mFile->transformVerticalComponent()) {
681
0
        const int idxCol = mFile->transformHorizontalComponent() ? 4 : 2;
682
0
        z_out = z - (vertices[i1 * colCount + idxCol] * lambda1 +
683
0
                     vertices[i2 * colCount + idxCol] * lambda2 +
684
0
                     vertices[i3 * colCount + idxCol] * lambda3);
685
0
    } else {
686
0
        z_out = z;
687
0
    }
688
0
    return true;
689
0
}
690
691
} // namespace TINSHIFT_NAMESPACE