Blender V4.3
reverse_uv_sampler.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
5#include <algorithm>
6#include <fmt/format.h>
7
9
10#include "BLI_bounds.hh"
12#include "BLI_index_mask.hh"
14#include "BLI_math_geom.h"
15#include "BLI_math_vector.hh"
16#include "BLI_offset_indices.hh"
17#include "BLI_task.hh"
18#include "BLI_timeit.hh"
19
20namespace blender::geometry {
21
34
41
44 int x_min;
45 int x_max;
46};
47
53
58
59static int2 uv_to_cell(const float2 &uv, const int resolution)
60{
61 return int2{uv * resolution};
62}
63
65 const int resolution,
66 const Span<float2> uv_map)
67{
68 const float2 &uv_0 = uv_map[tri[0]];
69 const float2 &uv_1 = uv_map[tri[1]];
70 const float2 &uv_2 = uv_map[tri[2]];
71
72 const int2 cell_0 = uv_to_cell(uv_0, resolution);
73 const int2 cell_1 = uv_to_cell(uv_1, resolution);
74 const int2 cell_2 = uv_to_cell(uv_2, resolution);
75
76 const int2 min_cell = math::min(math::min(cell_0, cell_1), cell_2);
77 const int2 max_cell = math::max(math::max(cell_0, cell_1), cell_2);
78
79 return {min_cell, max_cell};
80}
81
87static void sort_tris_into_rows(const Span<float2> uv_map,
88 const Span<int3> corner_tris,
89 const int resolution,
91{
92 threading::parallel_for(corner_tris.index_range(), 256, [&](const IndexRange tris_range) {
93 LocalData &local_data = data_per_thread.local();
94 for (const int tri_i : tris_range) {
95 const int3 &tri = corner_tris[tri_i];
96
97 /* Compute the cells that the triangle touches approximately. */
98 const Bounds<int2> cell_bounds = tri_to_cell_bounds(tri, resolution, uv_map);
99 const TriWithRange tri_with_range{tri_i, cell_bounds.min.x, cell_bounds.max.x};
100
101 /* Go over each row that the triangle is in. */
102 for (int cell_y = cell_bounds.min.y; cell_y <= cell_bounds.max.y; cell_y++) {
103 LocalRowData &row = *local_data.rows.lookup_or_add_cb(
104 cell_y, [&]() { return local_data.allocator.construct<LocalRowData>(); });
105 row.tris.append(local_data.allocator, tri_with_range);
106 row.x_min = std::min<int>(row.x_min, cell_bounds.min.x);
107 row.x_max = std::max<int>(row.x_max, cell_bounds.max.x);
108 }
109 }
110 });
111}
112
117static void finish_rows(const Span<int> all_ys,
118 const Span<const LocalData *> local_data_vec,
119 const Bounds<int> y_bounds,
120 ReverseUVSampler::LookupGrid &lookup_grid)
121{
122 threading::parallel_for(all_ys.index_range(), 8, [&](const IndexRange all_ys_range) {
123 Vector<const LocalRowData *, 32> local_rows;
124 for (const int y : all_ys.slice(all_ys_range)) {
125 Row &row = lookup_grid.rows[y - y_bounds.min];
126
127 local_rows.clear();
128 for (const LocalData *local_data : local_data_vec) {
129 if (const destruct_ptr<LocalRowData> *local_row = local_data->rows.lookup_ptr(y)) {
130 local_rows.append(local_row->get());
131 }
132 }
133
134 int x_min = INT32_MAX;
135 int x_max = INT32_MIN;
136 for (const LocalRowData *local_row : local_rows) {
137 x_min = std::min(x_min, local_row->x_min);
138 x_max = std::max(x_max, local_row->x_max);
139 }
140
141 const int x_num = x_max - x_min + 1;
142 row.offsets.reinitialize(x_num + 1);
143 {
144 /* Count how many triangles are in each cell in the current row. */
145 MutableSpan<int> counts = row.offsets;
146 counts.fill(0);
147 for (const LocalRowData *local_row : local_rows) {
148 for (const TriWithRange &tri_with_range : local_row->tris) {
149 for (int x = tri_with_range.x_min; x <= tri_with_range.x_max; x++) {
150 counts[x - x_min]++;
151 }
152 }
153 }
154 offset_indices::accumulate_counts_to_offsets(counts);
155 }
156 const int tri_indices_num = row.offsets.last();
157 row.tri_indices.reinitialize(tri_indices_num);
158
159 /* Populate the array containing all triangle indices in all cells in this row. */
160 Array<int, 1000> current_offsets(x_num, 0);
161 for (const LocalRowData *local_row : local_rows) {
162 for (const TriWithRange &tri_with_range : local_row->tris) {
163 for (int x = tri_with_range.x_min; x <= tri_with_range.x_max; x++) {
164 const int offset_x = x - x_min;
165 row.tri_indices[row.offsets[offset_x] + current_offsets[offset_x]] =
166 tri_with_range.tri_index;
167 current_offsets[offset_x]++;
168 }
169 }
170 }
171
172 row.x_min = x_min;
173 row.x_max = x_max;
174 }
175 });
176}
177
178ReverseUVSampler::ReverseUVSampler(const Span<float2> uv_map, const Span<int3> corner_tris)
179 : uv_map_(uv_map), corner_tris_(corner_tris), lookup_grid_(std::make_unique<LookupGrid>())
180{
181 /* A lower resolution means that there will be fewer cells and more triangles in each cell. Fewer
182 * cells make construction faster, but more triangles per cell make lookup slower. This value
183 * needs to be determined experimentally. */
184 resolution_ = std::max<int>(3, std::sqrt(corner_tris.size()) * 3);
185 if (corner_tris.is_empty()) {
186 return;
187 }
188
190 sort_tris_into_rows(uv_map_, corner_tris_, resolution_, data_per_thread);
191
192 VectorSet<int> all_ys;
193 Vector<const LocalData *> local_data_vec;
194 for (const LocalData &local_data : data_per_thread) {
195 local_data_vec.append(&local_data);
196 for (const int y : local_data.rows.keys()) {
197 all_ys.add(y);
198 }
199 }
200
201 const Bounds<int> y_bounds = *bounds::min_max(all_ys.as_span());
202 lookup_grid_->y_min = y_bounds.min;
203
204 const int rows_num = y_bounds.max - y_bounds.min + 1;
205 lookup_grid_->rows.reinitialize(rows_num);
206
207 finish_rows(all_ys, local_data_vec, y_bounds, *lookup_grid_);
208}
209
211 const ReverseUVSampler::LookupGrid &lookup_grid)
212{
213 if (cell.y < lookup_grid.y_min) {
214 return {};
215 }
216 if (cell.y >= lookup_grid.y_min + lookup_grid.rows.size()) {
217 return {};
218 }
219 const Row &row = lookup_grid.rows[cell.y - lookup_grid.y_min];
220 if (cell.x < row.x_min) {
221 return {};
222 }
223 if (cell.x > row.x_max) {
224 return {};
225 }
226 if (row.tri_indices.is_empty()) {
227 return {};
228 }
229 const int offset = row.offsets[cell.x - row.x_min];
230 const int tris_num = row.offsets[cell.x - row.x_min + 1] - offset;
231 return row.tri_indices.as_span().slice(offset, tris_num);
232}
233
235{
236 const int2 cell = uv_to_cell(query_uv, resolution_);
237 const Span<int> tri_indices = lookup_tris_in_cell(cell, *lookup_grid_);
238
239 float best_dist = FLT_MAX;
240 float3 best_bary_weights;
241 int best_tri_index;
242
243 /* The distance to an edge that is allowed to be inside or outside the triangle. Without this,
244 * the lookup can fail for floating point accuracy reasons when the uv is almost exact on an
245 * edge. */
246 const float edge_epsilon = 0.00001f;
247 /* If uv triangles are very small, it may look like the query hits multiple triangles due to
248 * floating point precision issues. Better just pick one of the triangles instead of failing the
249 * entire operation in this case. */
250 const float area_epsilon = 0.00001f;
251
252 for (const int tri_i : tri_indices) {
253 const int3 &tri = corner_tris_[tri_i];
254 const float2 &uv_0 = uv_map_[tri[0]];
255 const float2 &uv_1 = uv_map_[tri[1]];
256 const float2 &uv_2 = uv_map_[tri[2]];
257 float3 bary_weights;
258 if (!barycentric_coords_v2(uv_0, uv_1, uv_2, query_uv, bary_weights)) {
259 continue;
260 }
261
262 /* If #query_uv is in the triangle, the distance is <= 0. Otherwise, the larger the distance,
263 * the further away the uv is from the triangle. */
264 const float x_dist = std::max(-bary_weights.x, bary_weights.x - 1.0f);
265 const float y_dist = std::max(-bary_weights.y, bary_weights.y - 1.0f);
266 const float z_dist = std::max(-bary_weights.z, bary_weights.z - 1.0f);
267 const float dist = std::max({x_dist, y_dist, z_dist});
268
269 if (dist <= 0.0f && best_dist <= 0.0f) {
270 const float worse_dist = std::max(dist, best_dist);
271 /* Allow ignoring multiple triangle intersections if the uv is almost exactly on an edge. */
272 if (worse_dist < -edge_epsilon) {
273 const int3 &best_tri = corner_tris_[tri_i];
274 const float best_tri_area = area_tri_v2(
275 uv_map_[best_tri[0]], uv_map_[best_tri[1]], uv_map_[best_tri[2]]);
276 const float current_tri_area = area_tri_v2(uv_0, uv_1, uv_2);
277 if (best_tri_area > area_epsilon && current_tri_area > area_epsilon) {
278 /* The uv sample is in multiple triangles. */
280 }
281 }
282 }
283
284 if (dist < best_dist) {
285 best_dist = dist;
286 best_bary_weights = bary_weights;
287 best_tri_index = tri_i;
288 }
289 }
290
291 /* Allow using the closest (but not intersecting) triangle if the uv is almost exactly on an
292 * edge. */
293 if (best_dist < edge_epsilon) {
294 return Result{ResultType::Ok, best_tri_index, math::clamp(best_bary_weights, 0.0f, 1.0f)};
295 }
296
297 return Result{};
298}
299
301
303 MutableSpan<Result> r_results) const
304{
305 BLI_assert(query_uvs.size() == r_results.size());
306 threading::parallel_for(query_uvs.index_range(), 256, [&](const IndexRange range) {
307 for (const int i : range) {
308 r_results[i] = this->sample(query_uvs[i]);
309 }
310 });
311}
312
313} // namespace blender::geometry
#define BLI_assert(a)
Definition BLI_assert.h:50
MINLINE float area_tri_v2(const float v1[2], const float v2[2], const float v3[2])
bool barycentric_coords_v2(const float v1[2], const float v2[2], const float v3[2], const float co[2], float w[3])
Span< T > as_span() const
Definition BLI_array.hh:232
bool is_empty() const
Definition BLI_array.hh:253
constexpr int64_t size() const
Definition BLI_span.hh:494
constexpr Span slice(int64_t start, int64_t size) const
Definition BLI_span.hh:138
constexpr int64_t size() const
Definition BLI_span.hh:253
constexpr IndexRange index_range() const
Definition BLI_span.hh:402
constexpr bool is_empty() const
Definition BLI_span.hh:261
bool add(const Key &key)
Span< Key > as_span() const
void append(const T &value)
Result sample(const float2 &query_uv) const
void sample_many(Span< float2 > query_uvs, MutableSpan< Result > r_results) const
std::optional< Bounds< T > > min_max(const std::optional< Bounds< T > > &a, const T &b)
Definition BLI_bounds.hh:46
static void finish_rows(const Span< int > all_ys, const Span< const LocalData * > local_data_vec, const Bounds< int > y_bounds, ReverseUVSampler::LookupGrid &lookup_grid)
static Span< int > lookup_tris_in_cell(const int2 cell, const ReverseUVSampler::LookupGrid &lookup_grid)
static void sort_tris_into_rows(const Span< float2 > uv_map, const Span< int3 > corner_tris, const int resolution, threading::EnumerableThreadSpecific< LocalData > &data_per_thread)
static Bounds< int2 > tri_to_cell_bounds(const int3 &tri, const int resolution, const Span< float2 > uv_map)
static int2 uv_to_cell(const float2 &uv, const int resolution)
T clamp(const T &a, const T &min, const T &max)
T min(const T &a, const T &b)
T max(const T &a, const T &b)
void parallel_for(const IndexRange range, const int64_t grain_size, const Function &function, const TaskSizeHints &size_hints=detail::TaskSizeHints_Static(1))
Definition BLI_task.hh:95
#define FLT_MAX
Definition stdcycles.h:14
#define INT32_MAX
Definition stdint.h:137
#define INT32_MIN
Definition stdint.h:136
Map< int, destruct_ptr< LocalRowData > > rows
linear_allocator::ChunkedList< TriWithRange, 8 > tris