| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | /* | ||
| 2 | * Copyright (c) 2000-2022 Inria | ||
| 3 | * All rights reserved. | ||
| 4 | * | ||
| 5 | * Redistribution and use in source and binary forms, with or without | ||
| 6 | * modification, are permitted provided that the following conditions are met: | ||
| 7 | * | ||
| 8 | * * Redistributions of source code must retain the above copyright notice, | ||
| 9 | * this list of conditions and the following disclaimer. | ||
| 10 | * * Redistributions in binary form must reproduce the above copyright notice, | ||
| 11 | * this list of conditions and the following disclaimer in the documentation | ||
| 12 | * and/or other materials provided with the distribution. | ||
| 13 | * * Neither the name of the ALICE Project-Team nor the names of its | ||
| 14 | * contributors may be used to endorse or promote products derived from this | ||
| 15 | * software without specific prior written permission. | ||
| 16 | * | ||
| 17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||
| 18 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
| 19 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | ||
| 20 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE | ||
| 21 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | ||
| 22 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | ||
| 23 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | ||
| 24 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | ||
| 25 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | ||
| 26 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | ||
| 27 | * POSSIBILITY OF SUCH DAMAGE. | ||
| 28 | * | ||
| 29 | * Contact: Bruno Levy | ||
| 30 | * | ||
| 31 | * https://www.inria.fr/fr/bruno-levy | ||
| 32 | * | ||
| 33 | * Inria, | ||
| 34 | * Domaine de Voluceau, | ||
| 35 | * 78150 Le Chesnay - Rocquencourt | ||
| 36 | * FRANCE | ||
| 37 | * | ||
| 38 | */ | ||
| 39 | |||
| 40 | #include <geogram/mesh/mesh_distance.h> | ||
| 41 | #include <geogram/mesh/mesh_AABB.h> | ||
| 42 | #include <geogram/mesh/mesh_repair.h> | ||
| 43 | #include <geogram/mesh/mesh_sampling.h> | ||
| 44 | #include <geogram/basic/process.h> | ||
| 45 | #include <geogram/basic/stopwatch.h> | ||
| 46 | |||
| 47 | #include <algorithm> | ||
| 48 | |||
| 49 | namespace { | ||
| 50 | |||
| 51 | using namespace GEO; | ||
| 52 | |||
| 53 | /** | ||
| 54 | * \brief Computes largest distance between | ||
| 55 | * an interval of an array of points and | ||
| 56 | * a surface stored in a MeshFacetsAABB. | ||
| 57 | * \details Used by compute_max_distance() that | ||
| 58 | * runs multiple instances of this class | ||
| 59 | * (one per core). | ||
| 60 | */ | ||
| 61 | class DistanceThread : public Thread { | ||
| 62 | public: | ||
| 63 | /** | ||
| 64 | * \brief Constructs a new DistanceThread | ||
| 65 | * \details This DistanceThread will compute the distances | ||
| 66 | * between a batch of points and an axis-aligned bounding box | ||
| 67 | * tree. | ||
| 68 | * \param[in] AABB The axis-aligned bounding box tree used to compute | ||
| 69 | * the distance | ||
| 70 | * \param[in] from first point index | ||
| 71 | * \param[in] to one position past the last point index | ||
| 72 | * \param[in] points_ptr pointer to the points | ||
| 73 | * \param[in] points_stride number of doubles between two | ||
| 74 | * consecutive points | ||
| 75 | */ | ||
| 76 | DistanceThread( | ||
| 77 | const MeshFacetsAABB& AABB, | ||
| 78 | index_t from, index_t to, | ||
| 79 | const double* points_ptr, index_t points_stride = 3 | ||
| 80 | 144 | ) : | |
| 81 | 144 | AABB_(AABB), | |
| 82 | 144 | from_(from), | |
| 83 | 144 | to_(to), | |
| 84 | 144 | max_sq_dist_(0.0), | |
| 85 | 144 | points_ptr_(points_ptr), | |
| 86 |
1/2✓ Branch 1 taken 144 times.
✗ Branch 2 not taken.
|
144 | points_stride_(points_stride) { |
| 87 | } | ||
| 88 | |||
| 89 | /** | ||
| 90 | * \brief Runs the thread. | ||
| 91 | * \details Computes the distances for the batch of points associated | ||
| 92 | * with this thread. | ||
| 93 | */ | ||
| 94 | 144 | void run() override { | |
| 95 |
2/2✓ Branch 0 taken 905643 times.
✓ Branch 1 taken 144 times.
|
905787 | for(index_t v = from_; v < to_; v++) { |
| 96 | // TODO: optimization | ||
| 97 | // if we know that the points are spatially | ||
| 98 | // sorted, then we can use AABB_.squared_distance_with_hint() | ||
| 99 | 905643 | double sq_dist = AABB_.squared_distance( | |
| 100 | *reinterpret_cast<const vec3*>( | ||
| 101 | 905643 | points_ptr_ + v * points_stride_ | |
| 102 | ) | ||
| 103 | ); | ||
| 104 | 905643 | max_sq_dist_ = std::max( | |
| 105 | max_sq_dist_, sq_dist | ||
| 106 | ); | ||
| 107 | } | ||
| 108 | 144 | } | |
| 109 | |||
| 110 | /** | ||
| 111 | * \brief Gets the computed max squared distance. | ||
| 112 | * \return the maximum squared distance computed so far | ||
| 113 | */ | ||
| 114 | double max_squared_distance() const { | ||
| 115 | 144 | return max_sq_dist_; | |
| 116 | } | ||
| 117 | |||
| 118 | private: | ||
| 119 | const MeshFacetsAABB& AABB_; | ||
| 120 | index_t from_; | ||
| 121 | index_t to_; | ||
| 122 | double max_sq_dist_; | ||
| 123 | const double* points_ptr_; | ||
| 124 | index_t points_stride_; | ||
| 125 | }; | ||
| 126 | |||
| 127 | /** | ||
| 128 | * \brief Computes largest distance between | ||
| 129 | * an array of points and a mesh stored in | ||
| 130 | * a MeshFacetsAABB. | ||
| 131 | * \details Uses a multithread implementation. | ||
| 132 | * \param[out] result the maximum squared distance | ||
| 133 | * \param[in] AABB the mesh stored in an axis-aligned bounding box | ||
| 134 | * \param[in] nb_points number of query points | ||
| 135 | * \param[in] points_ptr pointer to the points coordinates | ||
| 136 | * \param[in] points_stride number of doubles between two consecutive | ||
| 137 | * points | ||
| 138 | */ | ||
| 139 | 36 | void compute_max_distance( | |
| 140 | double& result, | ||
| 141 | const MeshFacetsAABB& AABB, | ||
| 142 | index_t nb_points, | ||
| 143 | const double* points_ptr, | ||
| 144 | index_t points_stride = 3 | ||
| 145 | ) { | ||
| 146 | 36 | Stopwatch W; | |
| 147 | TypedThreadGroup<DistanceThread> threads; | ||
| 148 |
1/2✓ Branch 1 taken 36 times.
✗ Branch 2 not taken.
|
36 | index_t nb_threads = Process::maximum_concurrent_threads(); |
| 149 | 36 | index_t batch_size = nb_points / nb_threads; | |
| 150 | index_t cur = 0; | ||
| 151 | index_t remaining = nb_points; | ||
| 152 |
2/2✓ Branch 0 taken 144 times.
✓ Branch 1 taken 36 times.
|
180 | for(index_t i = 0; i < nb_threads; i++) { |
| 153 | index_t this_batch_size = batch_size; | ||
| 154 |
2/2✓ Branch 0 taken 36 times.
✓ Branch 1 taken 108 times.
|
144 | if(i == nb_threads - 1) { |
| 155 | this_batch_size = remaining; | ||
| 156 | } | ||
| 157 | threads.push_back( | ||
| 158 | 144 | new DistanceThread( | |
| 159 | AABB, | ||
| 160 | cur, cur + this_batch_size, | ||
| 161 | points_ptr, points_stride | ||
| 162 |
2/4✓ Branch 1 taken 144 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 144 times.
✗ Branch 5 not taken.
|
144 | ) |
| 163 | ); | ||
| 164 | cur += this_batch_size; | ||
| 165 | 144 | remaining -= this_batch_size; | |
| 166 | } | ||
| 167 |
1/8✗ Branch 0 not taken.
✓ Branch 1 taken 36 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
|
36 | geo_assert(remaining == 0); |
| 168 |
1/2✓ Branch 1 taken 36 times.
✗ Branch 2 not taken.
|
36 | Process::run_threads(threads); |
| 169 |
3/4✗ Branch 0 not taken.
✓ Branch 1 taken 180 times.
✓ Branch 2 taken 144 times.
✓ Branch 3 taken 36 times.
|
180 | for(index_t t = 0; t < threads.size(); t++) { |
| 170 |
2/2✓ Branch 0 taken 49 times.
✓ Branch 1 taken 95 times.
|
193 | result = std::max(result, threads[t]->max_squared_distance()); |
| 171 | } | ||
| 172 |
1/2✓ Branch 1 taken 36 times.
✗ Branch 2 not taken.
|
36 | double elapsed = W.elapsed_time(); |
| 173 |
2/2✓ Branch 0 taken 5 times.
✓ Branch 1 taken 31 times.
|
36 | if(elapsed == 0.0) { |
| 174 |
2/4✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 5 times.
✗ Branch 5 not taken.
|
10 | Logger::out("AABB") |
| 175 | << "???? Mqueries / s (too fast to be measured)" | ||
| 176 | << std::endl; | ||
| 177 | } else { | ||
| 178 |
2/4✓ Branch 1 taken 31 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 31 times.
✗ Branch 5 not taken.
|
62 | Logger::out("AABB") |
| 179 |
1/2✓ Branch 1 taken 31 times.
✗ Branch 2 not taken.
|
31 | << double(nb_points) / (1.0e6 * elapsed) |
| 180 | << " Mqueries / s" | ||
| 181 | << std::endl; | ||
| 182 | } | ||
| 183 | 36 | } | |
| 184 | } | ||
| 185 | |||
| 186 | /****************************************************************************/ | ||
| 187 | |||
| 188 | namespace GEO { | ||
| 189 | |||
| 190 | 18 | double mesh_one_sided_Hausdorff_distance( | |
| 191 | Mesh& m1, Mesh& m2, double sampling_step | ||
| 192 | ) { | ||
| 193 | 18 | double result = 0.0; | |
| 194 | 18 | MeshFacetsAABB AABB(m2); | |
| 195 | |||
| 196 | index_t nb_points = 0; | ||
| 197 | |||
| 198 |
3/4✓ Branch 0 taken 17 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 17 times.
✗ Branch 3 not taken.
|
18 | if(m1.cells.nb() == 0 && m1.edges.nb() == 0) { |
| 199 | nb_points = m1.vertices.nb(); | ||
| 200 |
1/2✓ Branch 1 taken 17 times.
✗ Branch 2 not taken.
|
17 | compute_max_distance( |
| 201 | result, AABB, m1.vertices.nb(), | ||
| 202 | m1.vertices.point_ptr(0), m1.vertices.dimension() | ||
| 203 | ); | ||
| 204 | } else { | ||
| 205 | // If the mesh has cells, then we need to remove the vertices | ||
| 206 | // that are not on the surface, else their distance to the | ||
| 207 | // other surface will be included in the computation ! | ||
| 208 | 1 | vector<bool> on_surface(m1.vertices.nb(),false); | |
| 209 |
2/2✓ Branch 0 taken 4000 times.
✓ Branch 1 taken 1 times.
|
4001 | for(index_t f: m1.facets) { |
| 210 |
2/2✓ Branch 0 taken 12000 times.
✓ Branch 1 taken 4000 times.
|
16000 | for(index_t v: m1.facets.vertices(f)) { |
| 211 | 12000 | on_surface[v] = true; | |
| 212 | } | ||
| 213 | } | ||
| 214 |
2/2✓ Branch 0 taken 2661 times.
✓ Branch 1 taken 1 times.
|
2662 | for(index_t v: m1.vertices) { |
| 215 |
2/2✓ Branch 0 taken 1996 times.
✓ Branch 1 taken 665 times.
|
2661 | if(on_surface[v]) { |
| 216 | 1996 | ++nb_points; | |
| 217 | } | ||
| 218 | } | ||
| 219 | vector<double> points; | ||
| 220 |
1/2✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
|
1 | points.reserve(nb_points*m1.vertices.dimension()); |
| 221 |
2/2✓ Branch 0 taken 2661 times.
✓ Branch 1 taken 1 times.
|
2662 | for(index_t v: m1.vertices) { |
| 222 |
2/2✓ Branch 0 taken 1996 times.
✓ Branch 1 taken 665 times.
|
2661 | if(on_surface[v]) { |
| 223 |
2/2✓ Branch 0 taken 5988 times.
✓ Branch 1 taken 1996 times.
|
13972 | for(index_t c=0; c<m1.vertices.dimension(); ++c) { |
| 224 |
1/2✓ Branch 0 taken 5988 times.
✗ Branch 1 not taken.
|
5988 | points.push_back(m1.vertices.point_ptr(v)[c]); |
| 225 | } | ||
| 226 | } | ||
| 227 | } | ||
| 228 | |||
| 229 |
1/2✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
|
1 | compute_max_distance( |
| 230 | result, AABB, nb_points, points.data(), | ||
| 231 | m1.vertices.dimension() | ||
| 232 | ); | ||
| 233 | } | ||
| 234 | |||
| 235 | index_t nb_samples = index_t( | ||
| 236 |
1/2✓ Branch 1 taken 18 times.
✗ Branch 2 not taken.
|
18 | Geom::mesh_area(m1, 3) / geo_sqr(sampling_step) |
| 237 | 18 | ); | |
| 238 | |||
| 239 |
1/2✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
|
18 | if(nb_samples > nb_points) { |
| 240 | |||
| 241 | 18 | nb_samples -= nb_points; | |
| 242 |
2/4✓ Branch 1 taken 18 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 18 times.
✗ Branch 5 not taken.
|
18 | Logger::out("Distance") << "Using " << nb_samples |
| 243 | << " additional samples" | ||
| 244 | << std::endl; | ||
| 245 |
1/2✓ Branch 1 taken 18 times.
✗ Branch 2 not taken.
|
18 | vector<double> samples(nb_samples * 3); |
| 246 | Attribute<double> weight; // left unbound | ||
| 247 |
1/2✓ Branch 1 taken 18 times.
✗ Branch 2 not taken.
|
18 | mesh_generate_random_samples_on_surface<3>( |
| 248 | m1, samples.data(), nb_samples, weight | ||
| 249 | ); | ||
| 250 |
1/2✓ Branch 1 taken 18 times.
✗ Branch 2 not taken.
|
18 | compute_max_distance( |
| 251 | result, AABB, nb_samples, samples.data(), 3 | ||
| 252 | ); | ||
| 253 | } | ||
| 254 | |||
| 255 | 18 | return ::sqrt(result); | |
| 256 | } | ||
| 257 | |||
| 258 | ✗ | double mesh_symmetric_Hausdorff_distance( | |
| 259 | Mesh& m1, Mesh& m2, double sampling_step | ||
| 260 | ) { | ||
| 261 | return std::max( | ||
| 262 | ✗ | mesh_one_sided_Hausdorff_distance(m1, m2, sampling_step), | |
| 263 | ✗ | mesh_one_sided_Hausdorff_distance(m2, m1, sampling_step) | |
| 264 | ✗ | ); | |
| 265 | } | ||
| 266 | } | ||
| 267 |