World Builder  1.1.0-pre
A geodynamic initial conditions generator
surface.cc
Go to the documentation of this file.
1 /*
2  Copyright (C) 2018-2024 by the authors of the World Builder code.
3 
4  This file is part of the World Builder.
5 
6  This program is free software: you can redistribute it and/or modify
7  it under the terms of the GNU Lesser General Public License as published
8  by the Free Software Foundation, either version 2 of the License, or
9  (at your option) any later version.
10 
11  This program is distributed in the hope that it will be useful,
12  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  GNU Lesser General Public License for more details.
15 
16  You should have received a copy of the GNU Lesser General Public License
17  along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19 #include "world_builder/assert.h"
21 
22 #include <iostream>
23 
24 #include "delaunator-cpp/delaunator.hpp"
25 
26 
27 namespace WorldBuilder
28 {
29  namespace Objects
30  {
31 
32  namespace
33  {
38  bool in_triangle(const std::array<std::array<double,3>,3> &points,
39  const std::array<double,8> &precomputed,
40  const Point<2> &check_point,
41  double &interpolate_value,
42  double &interpolator_s,
43  double &interpolator_t)
44  {
45  double factor = 1e4;
46  // based on https://stackoverflow.com/questions/2049582/how-to-determine-if-a-point-is-in-a-2d-triangle
47  // compute s, t and area
48  const double s_no_area = -(precomputed[0] + precomputed[1]*check_point[0] + precomputed[2]*check_point[1]);
49  const double t_no_area = -(precomputed[3] + precomputed[4]*check_point[0] + precomputed[5]*check_point[1]);
50 
51  if (s_no_area >= -factor*std::numeric_limits<double>::epsilon() && t_no_area >= -factor*std::numeric_limits<double>::epsilon() && s_no_area+t_no_area-precomputed[6]<=precomputed[6]*factor*std::numeric_limits<double>::epsilon())
52  {
53  // point is in this triangle
54  interpolator_s = precomputed[7]*s_no_area;
55  interpolator_t = precomputed[7]*t_no_area;
56  interpolate_value = points[0][2]*(1-interpolator_s-interpolator_t)+points[1][2]*interpolator_s+points[2][2]*interpolator_t;
57  return true;
58  }
59  return false;
60  }
61  }
62 
64  = default;
65 
66 
67  Surface::Surface(std::pair<std::vector<double>,std::vector<double>> values_at_points)
68  {
69  // first find the min and max. This need always to be done;
70  WBAssertThrow(!values_at_points.first.empty(), "internal error: no values in values_at_points.first.");
71  minimum = values_at_points.first[0];
72  maximum = values_at_points.first[0];
73 
74  for (const auto &value: values_at_points.first)
75  {
76  if (value < minimum)
77  {
78  minimum = value;
79  }
80  if (value > maximum)
81  {
82  maximum = value;
83  }
84  }
85 
86  // if there are points defined there is not a constant value,
87  // if there are no points defined there is a constant value.
88  if (!values_at_points.second.empty())
89  {
90  constant_value = false;
91 
92  // Next make the triangulation and the kd-tree.
93  // Feed the delaunator
94  delaunator::Delaunator triangulation(values_at_points.second);
95 
96  // now loop over all the triangles and add them to the correct vectors
97  triangles.resize(triangulation.triangles.size()/3);
98  std::vector<KDTree::Node> node_list;
99  for (std::size_t i = 0; i < triangulation.triangles.size(); i+=3)
100  {
101  delaunator::Delaunator &t = triangulation;
102  // 1. We need to create a triangle list where each entry contains the coordinates
103  // and value of the points of the triangle:
104  // [[[x0,y0,v0],[x1,y1,v1],[x2,y2,v2]],[[x1,y1,v1],[x0,y0,v0],[x3,y3,v3]],...]
105  // 2. we need to compute the centeroid locations and add those to the KD-tree
106  // with the index stored on the triangle list
107  triangles[i/3][0][0] = t.coords[2 * t.triangles[i]]; // tx0
108  triangles[i/3][0][1] = t.coords[2 * t.triangles[i]+1]; // ty0
109  triangles[i/3][0][2] = values_at_points.first[t.triangles[i]]; // v0
110  triangles[i/3][1][0] = t.coords[2 * t.triangles[i+1]]; // tx1
111  triangles[i/3][1][1] = t.coords[2 * t.triangles[i+1]+1]; // ty1
112  triangles[i/3][1][2] = values_at_points.first[t.triangles[i+1]]; // v1
113  triangles[i/3][2][0] = t.coords[2 * t.triangles[i+2]]; // tx2
114  triangles[i/3][2][1] = t.coords[2 * t.triangles[i+2]+1]; // ty2
115  triangles[i/3][2][2] = values_at_points.first[t.triangles[i+2]]; // v2
116 
117  node_list.emplace_back(i/3,
118  (t.coords[2*t.triangles[i]]+t.coords[2*t.triangles[i+1]]+t.coords[2*t.triangles[i+2]])/3.,
119  (t.coords[2*t.triangles[i]+1]+t.coords[2*t.triangles[i+1]+1]+t.coords[2*t.triangles[i+2]+1])/3.);
120 
121  }
122  // now feed and create the KD-tree
123  tree = KDTree::KDTree(node_list);
124  tree.create_tree(0, node_list.size()-1, false);
125 
126 
127  // precompute values for in_triangle
128  in_triangle_precomputed.resize(tree.get_nodes().size());
129  for (size_t iii = 0; iii < tree.get_nodes().size(); iii++)
130  {
131  in_triangle_precomputed[iii][0] = triangles[iii][0][1]*triangles[iii][2][0] - triangles[iii][0][0]*triangles[iii][2][1];
132  in_triangle_precomputed[iii][1] = triangles[iii][2][1] - triangles[iii][0][1];
133  in_triangle_precomputed[iii][2] = triangles[iii][0][0] - triangles[iii][2][0];
134  in_triangle_precomputed[iii][3] = triangles[iii][0][0]*triangles[iii][1][1] - triangles[iii][0][1]*triangles[iii][1][0];
135  in_triangle_precomputed[iii][4] = triangles[iii][0][1] - triangles[iii][1][1];
136  in_triangle_precomputed[iii][5] = triangles[iii][1][0] - triangles[iii][0][0];
137  in_triangle_precomputed[iii][6] = -(-triangles[iii][1][1]*triangles[iii][2][0] + triangles[iii][0][1]*(-triangles[iii][1][0] + triangles[iii][2][0]) + triangles[iii][0][0]*(triangles[iii][1][1] - triangles[iii][2][1]) + triangles[iii][1][0]*triangles[iii][2][1]);
139  }
140  }
141  else
142  {
143  constant_value = true;
144  }
145 
146  }
147 
149  Surface::local_value(const Point<2> &check_point) const
150  {
151  if (constant_value)
152  {
153  // just min and max are the same since it is constant. Just return min.
154  return minimum;
155  }
156  // first find the closest centeroids
157  const KDTree::IndexDistances index_distances = tree.find_closest_points(check_point);
158 
159  // try triangle of the closest centroid
160  double interpolated_value = 0;
161  double interpolator_s = NaN::DQNAN;
162  double interpolator_t = NaN::DQNAN;
163 
164  if (in_triangle(triangles[tree.get_nodes()[index_distances.min_index].index],in_triangle_precomputed[tree.get_nodes()[index_distances.min_index].index],check_point,interpolated_value,interpolator_s,interpolator_t))
165  {
166  return SurfaceValueInfo {tree.get_nodes()[index_distances.min_index].index,interpolated_value,interpolator_s,interpolator_t};
167  }
168 
169  Point<2> other_point = check_point;
170  KDTree::IndexDistances index_distances_other;
171  const bool spherical = check_point.get_coordinate_system() == CoordinateSystem::spherical;
172  if (spherical)
173  {
174  other_point[0] += check_point[0] < 0 ? 2.0 * WorldBuilder::Consts::PI : -2.0 * WorldBuilder::Consts::PI;
175  index_distances_other = tree.find_closest_points(other_point);
176 
177  if (in_triangle(triangles[tree.get_nodes()[index_distances_other.min_index].index],in_triangle_precomputed[tree.get_nodes()[index_distances_other.min_index].index],other_point,interpolated_value,interpolator_s,interpolator_t))
178  {
179  return SurfaceValueInfo {tree.get_nodes()[index_distances_other.min_index].index,interpolated_value,interpolator_s,interpolator_t};
180  }
181  }
182 
183 
184  {
185  // if not found go to closets nodes
186  // Todo: could remove the cosest node, because it was already tested. Could also sort based no distance.
187  for (const auto &index_distance: index_distances.vector)
188  {
189  if (in_triangle(triangles[tree.get_nodes()[index_distance.index].index],in_triangle_precomputed[tree.get_nodes()[index_distance.index].index],check_point,interpolated_value,interpolator_s,interpolator_t))
190  {
191  return SurfaceValueInfo {tree.get_nodes()[index_distance.index].index,interpolated_value,interpolator_s,interpolator_t};
192  }
193  }
194  if (spherical)
195  {
196  for (auto &index_distance: index_distances_other.vector)
197  {
198  if (in_triangle(triangles[tree.get_nodes()[index_distance.index].index],in_triangle_precomputed[tree.get_nodes()[index_distance.index].index],other_point,interpolated_value,interpolator_s,interpolator_t))
199  {
200  return SurfaceValueInfo {tree.get_nodes()[index_distance.index].index,interpolated_value,interpolator_s,interpolator_t};
201  }
202  }
203  }
204 
205  // if still not found, go through all nodes
206  // Todo: Although this shouldonly very rearly happen, could remove already tested nodes.
207  for (const auto &nodes: tree.get_nodes())
208  {
209  if (in_triangle(triangles[nodes.index],in_triangle_precomputed[nodes.index],check_point,interpolated_value,interpolator_s,interpolator_t))
210  {
211  return SurfaceValueInfo {nodes.index,interpolated_value,interpolator_s,interpolator_t};
212  }
213  if (spherical && in_triangle(triangles[nodes.index],in_triangle_precomputed[nodes.index],other_point,interpolated_value,interpolator_s,interpolator_t))
214  {
215  return SurfaceValueInfo {nodes.index,interpolated_value,interpolator_s,interpolator_t};
216  }
217  }
218  WBAssertThrow(false, "Internal error: The requested point was not in any triangle. "
219  << "This could be due to rounding errors if the difference between the check point and triangle points are small, "
220  << "or you are requesting a point outside the boundaries defined by the additional points. The check point was "
221  << check_point[0] << ":" << check_point[1] << ".");
222  }
223  return 0;
224  }
225  } // namespace Objects
226 } // namespace WorldBuilder
227 
std::vector< IndexDistance > vector
Definition: kd_tree.h:54
std::vector< std::array< double, 8 > > in_triangle_precomputed
Stores precomputed values.
Definition: surface.h:103
CoordinateSystem get_coordinate_system() const
Definition: point.h:403
const double DQNAN
Definition: nan.h:32
const std::vector< Node > & get_nodes() const
Definition: kd_tree.cc:53
void create_tree(const size_t left, const size_t right, const bool x_axis)
Definition: kd_tree.cc:34
#define WBAssertThrow(condition, message)
Definition: assert.h:40
SurfaceValueInfo local_value(const Point< 2 > &check_point) const
Definition: surface.cc:149
std::vector< std::array< std::array< double, 3 >, 3 > > triangles
Definition: surface.h:98
IndexDistances find_closest_points(const Point< 2 > &check_point) const
Definition: kd_tree.cc:134
constexpr double PI
Definition: consts.h:30