Coverage Report

Created: 2026-11-12 07:32

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/PROJ/src/projections/polyhedral/snyder.h
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  PROJ
4
 * Purpose:  Snyder equal-area polyhedral projection — forward and inverse.
5
 * Author:   Felix Palmer
6
 *
7
 ******************************************************************************
8
 * Derived from A5 (Apache-2.0).
9
 * https://github.com/felixpalmer/a5
10
 * (modules/projections/polyhedral.ts)
11
 *
12
 * Copyright (c) A5 contributors
13
 *
14
 * Licensed under the Apache License, Version 2.0 (the "License");
15
 * you may not use this file except in compliance with the License.
16
 * You may obtain a copy of the License at
17
 *
18
 *     http://www.apache.org/licenses/LICENSE-2.0
19
 *
20
 * Unless required by applicable law or agreed to in writing, software
21
 * distributed under the License is distributed on an "AS IS" BASIS,
22
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
23
 * See the License for the specific language governing permissions and
24
 * limitations under the License.
25
 ****************************************************************************
26
 * Derived from DGGAL (BSD 3-Clause License)
27
 *
28
 * Copyright (c) 2014-2025, Ecere Corporation
29
 *
30
 * Redistribution and use in source and binary forms, with or without
31
 * modification, are permitted provided that the following conditions are met:
32
 *
33
 * 1. Redistributions of source code must retain the above copyright
34
 *    notice, this list of conditions and the following disclaimer.
35
 *
36
 * 2. Redistributions in binary form must reproduce the above copyright notice,
37
 *    this list of conditions and the following disclaimer in the documentation
38
 *    and/or other materials provided with the distribution.
39
 *
40
 * 3. Neither the name of the copyright holder nor the names of its
41
 *    contributors may be used to endorse or promote products derived from
42
 *    this software without specific prior written permission.
43
 *
44
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
45
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
46
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
47
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
48
 * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
49
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
50
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
51
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
52
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
53
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
54
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
55
 * POSSIBILITY OF SUCH DAMAGE.
56
 ****************************************************************************
57
 *
58
 * Closed-form equations adapted from:
59
 * https://brsr.github.io/2021/08/31/snyder-equal-area.html
60
 *
61
 ****************************************************************************/
62
63
#ifndef SNYDER_H
64
#define SNYDER_H
65
66
#include "sphere.h"
67
#include "vec3.h"
68
69
namespace polyhedral {
70
71
struct Face2D {
72
    double x = 0.0, y = 0.0;
73
};
74
75
struct Barycentric {
76
    double u = 0.0, v = 0.0, w = 0.0;
77
};
78
79
struct FaceTriangle {
80
    Face2D a, b, c;
81
};
82
83
struct SphericalTriangle {
84
    Vec3 a, b, c;
85
};
86
87
0
inline Barycentric face_to_barycentric(Face2D p, const FaceTriangle &tri) {
88
0
    double d31[2] = {tri.a.x - tri.c.x, tri.a.y - tri.c.y};
89
0
    double d23[2] = {tri.c.x - tri.b.x, tri.c.y - tri.b.y};
90
0
    double d3p[2] = {p.x - tri.c.x, p.y - tri.c.y};
91
92
0
    double det = d23[0] * d31[1] - d23[1] * d31[0];
93
0
    double b0 = (d23[0] * d3p[1] - d23[1] * d3p[0]) / det;
94
0
    double b1 = (d31[0] * d3p[1] - d31[1] * d3p[0]) / det;
95
0
    double b2 = 1.0 - (b0 + b1);
96
0
    return {b0, b1, b2};
97
0
}
98
99
inline Face2D barycentric_to_face(const Barycentric &b,
100
5.35k
                                  const FaceTriangle &tri) {
101
5.35k
    return {b.u * tri.a.x + b.v * tri.b.x + b.w * tri.c.x,
102
5.35k
            b.u * tri.a.y + b.v * tri.b.y + b.w * tri.c.y};
103
5.35k
}
104
105
inline Face2D snyder_fwd(const Vec3 &v, const SphericalTriangle &stri,
106
5.35k
                         const FaceTriangle &ftri) {
107
5.35k
    Vec3 a = stri.a, b = stri.b, c = stri.c;
108
109
5.35k
    Vec3 z = vec3_normalize(vec3_subtract(v, a));
110
5.35k
    Vec3 p = vec3_normalize(quadruple_product(a, z, b, c));
111
112
    // Ensure p is on the same hemisphere as b and c (the quadruple product
113
    // may return the antipodal intersection depending on vertex winding)
114
5.35k
    if (vec3_dot(p, b) + vec3_dot(p, c) < 0) {
115
3.33k
        p = {-p.x, -p.y, -p.z};
116
3.33k
    }
117
118
5.35k
    double h = vector_difference(a, v) / vector_difference(a, p);
119
5.35k
    double area_abc = spherical_triangle_area(a, b, c);
120
5.35k
    double scaled_area = h / area_abc;
121
5.35k
    double area_apc = spherical_triangle_area(a, p, c);
122
5.35k
    double area_abp = spherical_triangle_area(a, b, p);
123
124
5.35k
    Barycentric bary = {1.0 - h, scaled_area * area_apc,
125
5.35k
                        scaled_area * area_abp};
126
5.35k
    return barycentric_to_face(bary, ftri);
127
5.35k
}
128
129
inline Vec3 snyder_inv(const Face2D &face_point, const FaceTriangle &ftri,
130
0
                       const SphericalTriangle &stri) {
131
0
    Vec3 a = stri.a, b = stri.b, c = stri.c;
132
0
    Barycentric bcoords = face_to_barycentric(face_point, ftri);
133
134
0
    constexpr double threshold = 1.0 - 1e-14;
135
0
    if (bcoords.u > threshold)
136
0
        return a;
137
0
    if (bcoords.v > threshold)
138
0
        return b;
139
0
    if (bcoords.w > threshold)
140
0
        return c;
141
142
0
    Vec3 c1 = vec3_cross(b, c);
143
0
    double area_abc = spherical_triangle_area(a, b, c);
144
0
    double h = 1.0 - bcoords.u;
145
0
    double r = bcoords.w / h;
146
0
    double alpha = r * area_abc;
147
0
    double s = std::sin(alpha);
148
0
    double half_c = std::sin(alpha / 2.0);
149
0
    double cc = 2.0 * half_c * half_c; // 1 - cos(alpha)
150
151
0
    double c01 = vec3_dot(a, b);
152
0
    double c12 = vec3_dot(b, c);
153
0
    double c20 = vec3_dot(c, a);
154
0
    double s12 = vec3_length(c1);
155
156
0
    double tp = vec3_dot(a, c1); // triple product A·(B×C)
157
0
    double f = s * tp + cc * (c01 * c12 - c20);
158
0
    double g = cc * s12 * (1.0 + c01);
159
0
    double q = (2.0 / std::acos(c12)) * std::atan2(g, f);
160
0
    Vec3 pp = slerp(b, c, q);
161
0
    double k = vector_difference(a, pp);
162
0
    double t = std::asin(h * k) / std::asin(k);
163
0
    return slerp(a, pp, t);
164
0
}
165
166
} // namespace polyhedral
167
168
#endif // SNYDER_H