Improved Surface Deform performance #116545
|
@ -198,7 +198,7 @@ class CurvesGeometry : public ::CurvesGeometry {
|
|||
Array<int> point_to_curve_map() const;
|
||||
|
||||
Span<float3> positions() const;
|
||||
MutableSpan<float3> positions_for_write();
|
||||
MutableSpan<float3> positions_for_write(bool preserve = true);
|
||||
|
||||
/** Whether the curve loops around to connect to itself, on the curve domain. */
|
||||
VArray<bool> cyclic() const;
|
||||
|
@ -727,7 +727,18 @@ void interpolate_to_evaluated(const GSpan src,
|
|||
const OffsetIndices<int> evaluated_offsets,
|
||||
GMutableSpan dst);
|
||||
|
||||
float4 calculate_basis(const float parameter);
|
||||
inline float4 calculate_basis(const float parameter)
|
||||
{
|
||||
/* Adapted from Cycles #catmull_rom_basis_eval function. */
|
||||
const float t = parameter;
|
||||
const float s = 1.0f - parameter;
|
||||
return {
|
||||
-t * s * s,
|
||||
2.0f + t * t * (3.0f * t - 5.0f),
|
||||
2.0f + s * s * (3.0f * s - 5.0f),
|
||||
-s * t * t,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Interpolate the control point values for the given parameter on the piecewise segment.
|
||||
|
|
|
@ -525,7 +525,8 @@ const void *CustomData_get_layer_named(const CustomData *data,
|
|||
void *CustomData_get_layer_named_for_write(CustomData *data,
|
||||
eCustomDataType type,
|
||||
blender::StringRef name,
|
||||
int totelem);
|
||||
int totelem,
|
||||
bool preserve_data = true);
|
||||
|
||||
int CustomData_get_offset(const CustomData *data, eCustomDataType type);
|
||||
int CustomData_get_offset_named(const CustomData *data,
|
||||
|
|
|
@ -24,19 +24,6 @@ int calculate_evaluated_num(const int points_num, const bool cyclic, const int r
|
|||
return eval_num + 1;
|
||||
}
|
||||
|
||||
float4 calculate_basis(const float parameter)
|
||||
{
|
||||
/* Adapted from Cycles #catmull_rom_basis_eval function. */
|
||||
const float t = parameter;
|
||||
const float s = 1.0f - parameter;
|
||||
return {
|
||||
-t * s * s,
|
||||
2.0f + t * t * (3.0f * t - 5.0f),
|
||||
2.0f + s * s * (3.0f * s - 5.0f),
|
||||
-s * t * t,
|
||||
};
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static void evaluate_segment(const T &a, const T &b, const T &c, const T &d, MutableSpan<T> dst)
|
||||
{
|
||||
|
|
|
@ -241,7 +241,8 @@ template<typename T>
|
|||
static MutableSpan<T> get_mutable_attribute(CurvesGeometry &curves,
|
||||
const AttrDomain domain,
|
||||
const StringRef name,
|
||||
const T default_value = T())
|
||||
const T default_value = T(),
|
||||
bool preserve = true)
|
||||
{
|
||||
const int num = domain_num(curves, domain);
|
||||
if (num <= 0) {
|
||||
|
@ -250,13 +251,13 @@ static MutableSpan<T> get_mutable_attribute(CurvesGeometry &curves,
|
|||
const eCustomDataType type = cpp_type_to_custom_data_type(CPPType::get<T>());
|
||||
CustomData &custom_data = domain_custom_data(curves, domain);
|
||||
|
||||
T *data = (T *)CustomData_get_layer_named_for_write(&custom_data, type, name, num);
|
||||
T *data = (T *)CustomData_get_layer_named_for_write(&custom_data, type, name, num, preserve);
|
||||
if (data != nullptr) {
|
||||
return {data, num};
|
||||
}
|
||||
data = (T *)CustomData_add_layer_named(&custom_data, type, CD_SET_DEFAULT, num, name);
|
||||
MutableSpan<T> span = {data, num};
|
||||
if (num > 0 && span.first() != default_value) {
|
||||
if (preserve && num > 0 && span.first() != default_value) {
|
||||
span.fill(default_value);
|
||||
}
|
||||
return span;
|
||||
|
@ -347,9 +348,10 @@ Span<float3> CurvesGeometry::positions() const
|
|||
{
|
||||
return get_span_attribute<float3>(*this, AttrDomain::Point, ATTR_POSITION);
|
||||
}
|
||||
MutableSpan<float3> CurvesGeometry::positions_for_write()
|
||||
MutableSpan<float3> CurvesGeometry::positions_for_write(bool preserve)
|
||||
{
|
||||
return get_mutable_attribute<float3>(*this, AttrDomain::Point, ATTR_POSITION);
|
||||
return get_mutable_attribute<float3>(
|
||||
*this, AttrDomain::Point, ATTR_POSITION, float3(), preserve);
|
||||
}
|
||||
|
||||
Span<int> CurvesGeometry::offsets() const
|
||||
|
|
|
@ -2354,11 +2354,16 @@ static bool customdata_typemap_is_valid(const CustomData *data)
|
|||
}
|
||||
#endif
|
||||
|
||||
static void *copy_layer_data(const eCustomDataType type, const void *data, const int totelem)
|
||||
static void *copy_layer_data(const eCustomDataType type,
|
||||
const void *data,
|
||||
const int totelem,
|
||||
bool preserve_data = true)
|
||||
{
|
||||
const LayerTypeInfo &type_info = *layerType_getInfo(type);
|
||||
const int64_t size_in_bytes = int64_t(totelem) * type_info.size;
|
||||
void *new_data = MEM_mallocN_aligned(size_in_bytes, type_info.alignment, __func__);
|
||||
if (!preserve_data)
|
||||
return new_data;
|
||||
if (type_info.copy) {
|
||||
type_info.copy(data, new_data, totelem);
|
||||
}
|
||||
|
@ -2560,7 +2565,9 @@ static const ImplicitSharingInfo *make_implicit_sharing_info_for_layer(const eCu
|
|||
/**
|
||||
* If the layer data is currently shared (hence it is immutable), create a copy that can be edited.
|
||||
*/
|
||||
static void ensure_layer_data_is_mutable(CustomDataLayer &layer, const int totelem)
|
||||
static void ensure_layer_data_is_mutable(CustomDataLayer &layer,
|
||||
const int totelem,
|
||||
bool preserve_data = true)
|
||||
{
|
||||
if (layer.data == nullptr) {
|
||||
return;
|
||||
|
@ -2577,7 +2584,7 @@ static void ensure_layer_data_is_mutable(CustomDataLayer &layer, const int totel
|
|||
const void *old_data = layer.data;
|
||||
/* Copy the layer before removing the user because otherwise the data might be freed while
|
||||
* we're still copying from it here. */
|
||||
layer.data = copy_layer_data(type, old_data, totelem);
|
||||
layer.data = copy_layer_data(type, old_data, totelem, preserve_data);
|
||||
layer.sharing_info->remove_user_and_delete_if_last();
|
||||
layer.sharing_info = make_implicit_sharing_info_for_layer(type, layer.data, totelem);
|
||||
}
|
||||
|
@ -3773,14 +3780,15 @@ const void *CustomData_get_layer_named(const CustomData *data,
|
|||
void *CustomData_get_layer_named_for_write(CustomData *data,
|
||||
const eCustomDataType type,
|
||||
const StringRef name,
|
||||
const int totelem)
|
||||
const int totelem,
|
||||
bool preserve_data)
|
||||
{
|
||||
const int layer_index = CustomData_get_named_layer_index(data, type, name);
|
||||
if (layer_index == -1) {
|
||||
return nullptr;
|
||||
}
|
||||
CustomDataLayer &layer = data->layers[layer_index];
|
||||
ensure_layer_data_is_mutable(layer, totelem);
|
||||
ensure_layer_data_is_mutable(layer, totelem, preserve_data);
|
||||
return layer.data;
|
||||
}
|
||||
|
||||
|
|
|
@ -1010,7 +1010,7 @@ void barycentric_weights_v2_quad(const float v1[2],
|
|||
/**
|
||||
* \return false for degenerated triangles.
|
||||
*/
|
||||
bool barycentric_coords_v2(
|
||||
MINLINE bool barycentric_coords_v2(
|
||||
const float v1[2], const float v2[2], const float v3[2], const float co[2], float w[3]);
|
||||
/**
|
||||
* \return
|
||||
|
|
|
@ -434,9 +434,9 @@ MINLINE bool is_zero_v2_db(const double v[2]) ATTR_WARN_UNUSED_RESULT;
|
|||
MINLINE bool is_zero_v3_db(const double v[3]) ATTR_WARN_UNUSED_RESULT;
|
||||
MINLINE bool is_zero_v4_db(const double v[4]) ATTR_WARN_UNUSED_RESULT;
|
||||
|
||||
bool is_finite_v2(const float v[2]) ATTR_WARN_UNUSED_RESULT;
|
||||
bool is_finite_v3(const float v[3]) ATTR_WARN_UNUSED_RESULT;
|
||||
bool is_finite_v4(const float v[4]) ATTR_WARN_UNUSED_RESULT;
|
||||
MINLINE bool is_finite_v2(const float v[2]) ATTR_WARN_UNUSED_RESULT;
|
||||
MINLINE bool is_finite_v3(const float v[3]) ATTR_WARN_UNUSED_RESULT;
|
||||
MINLINE bool is_finite_v4(const float v[4]) ATTR_WARN_UNUSED_RESULT;
|
||||
|
||||
MINLINE bool is_one_v3(const float v[3]) ATTR_WARN_UNUSED_RESULT;
|
||||
|
||||
|
|
|
@ -3673,30 +3673,6 @@ int barycentric_inside_triangle_v2(const float w[3])
|
|||
return 0;
|
||||
}
|
||||
|
||||
bool barycentric_coords_v2(
|
||||
const float v1[2], const float v2[2], const float v3[2], const float co[2], float w[3])
|
||||
{
|
||||
const float x = co[0], y = co[1];
|
||||
const float x1 = v1[0], y1 = v1[1];
|
||||
const float x2 = v2[0], y2 = v2[1];
|
||||
const float x3 = v3[0], y3 = v3[1];
|
||||
const float det = (y2 - y3) * (x1 - x3) + (x3 - x2) * (y1 - y3);
|
||||
|
||||
#ifndef NDEBUG /* Avoid floating point exception when debugging. */
|
||||
if (det != 0.0f)
|
||||
#endif
|
||||
{
|
||||
w[0] = ((y2 - y3) * (x - x3) + (x3 - x2) * (y - y3)) / det;
|
||||
w[1] = ((y3 - y1) * (x - x3) + (x1 - x3) * (y - y3)) / det;
|
||||
w[2] = 1.0f - w[0] - w[1];
|
||||
if (is_finite_v3(w)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void barycentric_weights_v2(
|
||||
const float v1[2], const float v2[2], const float v3[2], const float co[2], float w[3])
|
||||
{
|
||||
|
|
|
@ -166,6 +166,36 @@ MINLINE float shell_v2v2_mid_normalized_to_dist(const float a[2], const float b[
|
|||
return (UNLIKELY(angle_cos < SMALL_NUMBER)) ? 1.0f : (1.0f / angle_cos);
|
||||
}
|
||||
|
||||
MINLINE bool is_finite_v3_inline(const float v[3])
|
||||
{
|
||||
return (isfinite(v[0]) && isfinite(v[1]) && isfinite(v[2]));
|
||||
}
|
||||
|
||||
MINLINE bool barycentric_coords_v2(
|
||||
const float v1[2], const float v2[2], const float v3[2], const float co[2], float w[3])
|
||||
{
|
||||
const float x = co[0], y = co[1];
|
||||
const float x1 = v1[0], y1 = v1[1];
|
||||
const float x2 = v2[0], y2 = v2[1];
|
||||
const float x3 = v3[0], y3 = v3[1];
|
||||
float det = (y2 - y3) * (x1 - x3) + (x3 - x2) * (y1 - y3);
|
||||
|
||||
#ifndef NDEBUG /* Avoid floating point exception when debugging. */
|
||||
if (det != 0.0f)
|
||||
#endif
|
||||
{
|
||||
det = 1.0f / det;
|
||||
w[0] = ((y2 - y3) * (x - x3) + (x3 - x2) * (y - y3)) * det;
|
||||
w[1] = ((y3 - y1) * (x - x3) + (x1 - x3) * (y - y3)) * det;
|
||||
w[2] = 1.0f - w[0] - w[1];
|
||||
if (is_finite_v3_inline(w)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#undef SMALL_NUMBER
|
||||
|
||||
#endif /* __MATH_GEOM_INLINE_C__ */
|
||||
|
|
|
@ -342,27 +342,6 @@ void flip_v2_v2v2(float v[2], const float v1[2], const float v2[2])
|
|||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Comparison
|
||||
* \{ */
|
||||
|
||||
bool is_finite_v2(const float v[2])
|
||||
{
|
||||
return (isfinite(v[0]) && isfinite(v[1]));
|
||||
}
|
||||
|
||||
bool is_finite_v3(const float v[3])
|
||||
{
|
||||
return (isfinite(v[0]) && isfinite(v[1]) && isfinite(v[2]));
|
||||
}
|
||||
|
||||
bool is_finite_v4(const float v[4])
|
||||
{
|
||||
return (isfinite(v[0]) && isfinite(v[1]) && isfinite(v[2]) && isfinite(v[3]));
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Angles
|
||||
* \{ */
|
||||
|
|
|
@ -1305,6 +1305,21 @@ MINLINE bool is_zero_v4_db(const double v[4])
|
|||
return (v[0] == 0.0 && v[1] == 0.0 && v[2] == 0.0 && v[3] == 0.0);
|
||||
}
|
||||
|
||||
MINLINE bool is_finite_v2(const float v[2])
|
||||
{
|
||||
return (isfinite(v[0]) && isfinite(v[1]));
|
||||
}
|
||||
|
||||
MINLINE bool is_finite_v3(const float v[3])
|
||||
{
|
||||
return (isfinite(v[0]) && isfinite(v[1]) && isfinite(v[2]));
|
||||
}
|
||||
|
||||
MINLINE bool is_finite_v4(const float v[4])
|
||||
{
|
||||
return (isfinite(v[0]) && isfinite(v[1]) && isfinite(v[2]) && isfinite(v[3]));
|
||||
}
|
||||
|
||||
MINLINE bool is_one_v3(const float v[3])
|
||||
{
|
||||
return (v[0] == 1.0f && v[1] == 1.0f && v[2] == 1.0f);
|
||||
|
|
|
@ -9,6 +9,8 @@
|
|||
#include "BLI_math_vector_types.hh"
|
||||
#include "BLI_multi_value_map.hh"
|
||||
#include "BLI_span.hh"
|
||||
#include "BLI_vector.hh"
|
||||
#include "BLI_offset_indices.hh"
|
||||
|
||||
namespace blender::geometry {
|
||||
|
||||
|
@ -24,12 +26,20 @@ class ReverseUVSampler {
|
|||
private:
|
||||
Span<float2> uv_map_;
|
||||
|
||||
Span<int3> corner_tris_;
|
||||
int resolution_;
|
||||
std::unique_ptr<LookupGrid> lookup_grid_;
|
||||
int2 resolution_;
|
||||
int2 span_;
|
||||
int live_count_;
|
||||
|
||||
Vector<int> corner_tris_by_cell_;
|
||||
Vector<int> cell_populations_;
|
||||
|
||||
float2 uv_base_, uv_max_, supp_uv_min_, supp_uv_max_;
|
||||
|
||||
public:
|
||||
ReverseUVSampler(Span<float2> uv_map, Span<int3> corner_tris);
|
||||
~ReverseUVSampler();
|
||||
ReverseUVSampler(Span<float2> uv_map,
|
||||
Span<int3> corner_tris,
|
||||
float2 supp_uv_min = float2{0.0f, 0.0f},
|
||||
float2 supp_uv_max = float2{0.0f, 0.0f});
|
||||
|
||||
enum class ResultType {
|
||||
None,
|
||||
|
@ -43,8 +53,16 @@ class ReverseUVSampler {
|
|||
float3 bary_weights;
|
||||
};
|
||||
|
||||
Result sample(const float2 &query_uv) const;
|
||||
void sample_many(Span<float2> query_uvs, MutableSpan<Result> r_results) const;
|
||||
void sample(const float2 &query_uv, Result &result, bool hint = false) const;
|
||||
Result sample(const float2 &query_uv) const
|
||||
{
|
||||
Result result;
|
||||
sample(query_uv, result);
|
||||
return result;
|
||||
}
|
||||
void sample_many(Span<float2> query_uvs,
|
||||
MutableSpan<Result> r_results,
|
||||
bool hints = false) const;
|
||||
};
|
||||
|
||||
} // namespace blender::geometry
|
||||
|
|
|
@ -2,8 +2,9 @@
|
|||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include <algorithm>
|
||||
#include <fmt/format.h>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <random>
|
||||
|
||||
#include "GEO_reverse_uv_sampler.hh"
|
||||
|
||||
|
@ -19,279 +20,228 @@
|
|||
|
||||
namespace blender::geometry {
|
||||
|
||||
struct Row {
|
||||
/** The min and max horizontal cell index that is used in this row. */
|
||||
int x_min = 0;
|
||||
int x_max = 0;
|
||||
/** Offsets into the array of indices below. Also see #OffsetIndices. */
|
||||
Array<int> offsets;
|
||||
/** A flat array containing the triangle indices contained in each cell. */
|
||||
Array<int> tri_indices;
|
||||
};
|
||||
|
||||
struct ReverseUVSampler::LookupGrid {
|
||||
/** Minimum vertical cell index that contains triangles. */
|
||||
int y_min = 0;
|
||||
/** Information about all rows starting at `y_min`. */
|
||||
Array<Row> rows;
|
||||
};
|
||||
|
||||
struct TriWithRange {
|
||||
int tri_index;
|
||||
int x_min;
|
||||
int x_max;
|
||||
};
|
||||
|
||||
struct LocalRowData {
|
||||
linear_allocator::ChunkedList<TriWithRange, 8> tris;
|
||||
int x_min = INT32_MAX;
|
||||
int x_max = INT32_MIN;
|
||||
};
|
||||
|
||||
struct LocalData {
|
||||
LinearAllocator<> allocator;
|
||||
Map<int, destruct_ptr<LocalRowData>> rows;
|
||||
};
|
||||
|
||||
static int2 uv_to_cell(const float2 &uv, const int resolution)
|
||||
static inline int2 uv_to_cell_key(const float2 &uv,
|
||||
const float2 &uv_base,
|
||||
const int2 &resolution,
|
||||
int2 span)
|
||||
{
|
||||
return int2{uv * resolution};
|
||||
auto key = int2{int((uv.x - uv_base.x) * resolution.x), int((uv.y - uv_base.y) * resolution.y)};
|
||||
return int2{std::max(0, std::min(span.x - 1, key.x)), std::max(0, std::min(span.y - 1, key.y))};
|
||||
}
|
||||
|
||||
static Bounds<int2> tri_to_cell_bounds(const int3 &tri,
|
||||
const int resolution,
|
||||
const Span<float2> uv_map)
|
||||
ReverseUVSampler::ReverseUVSampler(const Span<float2> uv_map,
|
||||
const Span<int3> corner_tris,
|
||||
Eugene-Kuznetsov marked this conversation as resolved
Outdated
Iliya Katushenock
commented
`uint8_t` -> `int8_t` (maybe even `bool`?)
https://wiki.blender.org/wiki/Style_Guide/C_Cpp#Integer_Types
|
||||
float2 supp_uv_min,
|
||||
float2 supp_uv_max)
|
||||
: uv_map_(uv_map),
|
||||
corner_tris_(corner_tris),
|
||||
resolution_(int2{0, 0}),
|
||||
supp_uv_min_(supp_uv_min),
|
||||
supp_uv_max_(supp_uv_max)
|
||||
{
|
||||
const float2 &uv_0 = uv_map[tri[0]];
|
||||
const float2 &uv_1 = uv_map[tri[1]];
|
||||
const float2 &uv_2 = uv_map[tri[2]];
|
||||
|
||||
const int2 cell_0 = uv_to_cell(uv_0, resolution);
|
||||
const int2 cell_1 = uv_to_cell(uv_1, resolution);
|
||||
const int2 cell_2 = uv_to_cell(uv_2, resolution);
|
||||
|
||||
const int2 min_cell = math::min(math::min(cell_0, cell_1), cell_2);
|
||||
const int2 max_cell = math::max(math::max(cell_0, cell_1), cell_2);
|
||||
|
||||
return {min_cell, max_cell};
|
||||
}
|
||||
|
||||
/**
|
||||
* Add each triangle to the rows that it is in. After this, the information about each row is still
|
||||
* scattered across multiple thread-specific lists. Those separate lists are then joined in a
|
||||
* separate step.
|
||||
*/
|
||||
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)
|
||||
{
|
||||
threading::parallel_for(corner_tris.index_range(), 256, [&](const IndexRange tris_range) {
|
||||
LocalData &local_data = data_per_thread.local();
|
||||
for (const int tri_i : tris_range) {
|
||||
const int3 &tri = corner_tris[tri_i];
|
||||
|
||||
/* Compute the cells that the triangle touches approximately. */
|
||||
const Bounds<int2> cell_bounds = tri_to_cell_bounds(tri, resolution, uv_map);
|
||||
const TriWithRange tri_with_range{tri_i, cell_bounds.min.x, cell_bounds.max.x};
|
||||
|
||||
/* Go over each row that the triangle is in. */
|
||||
for (int cell_y = cell_bounds.min.y; cell_y <= cell_bounds.max.y; cell_y++) {
|
||||
LocalRowData &row = *local_data.rows.lookup_or_add_cb(
|
||||
cell_y, [&]() { return local_data.allocator.construct<LocalRowData>(); });
|
||||
row.tris.append(local_data.allocator, tri_with_range);
|
||||
row.x_min = std::min<int>(row.x_min, cell_bounds.min.x);
|
||||
row.x_max = std::max<int>(row.x_max, cell_bounds.max.x);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Consolidates the data that has been gather for each row so that it is each to look up which
|
||||
* triangles are in each cell.
|
||||
*/
|
||||
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)
|
||||
{
|
||||
threading::parallel_for(all_ys.index_range(), 8, [&](const IndexRange all_ys_range) {
|
||||
Vector<const LocalRowData *, 32> local_rows;
|
||||
for (const int y : all_ys.slice(all_ys_range)) {
|
||||
Row &row = lookup_grid.rows[y - y_bounds.min];
|
||||
|
||||
local_rows.clear();
|
||||
for (const LocalData *local_data : local_data_vec) {
|
||||
if (const destruct_ptr<LocalRowData> *local_row = local_data->rows.lookup_ptr(y)) {
|
||||
local_rows.append(local_row->get());
|
||||
}
|
||||
}
|
||||
|
||||
int x_min = INT32_MAX;
|
||||
int x_max = INT32_MIN;
|
||||
for (const LocalRowData *local_row : local_rows) {
|
||||
x_min = std::min(x_min, local_row->x_min);
|
||||
x_max = std::max(x_max, local_row->x_max);
|
||||
}
|
||||
|
||||
const int x_num = x_max - x_min + 1;
|
||||
row.offsets.reinitialize(x_num + 1);
|
||||
{
|
||||
/* Count how many triangles are in each cell in the current row. */
|
||||
MutableSpan<int> counts = row.offsets;
|
||||
counts.fill(0);
|
||||
for (const LocalRowData *local_row : local_rows) {
|
||||
for (const TriWithRange &tri_with_range : local_row->tris) {
|
||||
for (int x = tri_with_range.x_min; x <= tri_with_range.x_max; x++) {
|
||||
counts[x - x_min]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
offset_indices::accumulate_counts_to_offsets(counts);
|
||||
}
|
||||
const int tri_indices_num = row.offsets.last();
|
||||
row.tri_indices.reinitialize(tri_indices_num);
|
||||
|
||||
/* Populate the array containing all triangle indices in all cells in this row. */
|
||||
Array<int, 1000> current_offsets(x_num, 0);
|
||||
for (const LocalRowData *local_row : local_rows) {
|
||||
for (const TriWithRange &tri_with_range : local_row->tris) {
|
||||
for (int x = tri_with_range.x_min; x <= tri_with_range.x_max; x++) {
|
||||
const int offset_x = x - x_min;
|
||||
row.tri_indices[row.offsets[offset_x] + current_offsets[offset_x]] =
|
||||
tri_with_range.tri_index;
|
||||
current_offsets[offset_x]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
row.x_min = x_min;
|
||||
row.x_max = x_max;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ReverseUVSampler::ReverseUVSampler(const Span<float2> uv_map, const Span<int3> corner_tris)
|
||||
: uv_map_(uv_map), corner_tris_(corner_tris), lookup_grid_(std::make_unique<LookupGrid>())
|
||||
{
|
||||
/* A lower resolution means that there will be fewer cells and more triangles in each cell. Fewer
|
||||
* cells make construction faster, but more triangles per cell make lookup slower. This value
|
||||
* needs to be determined experimentally. */
|
||||
resolution_ = std::max<int>(3, std::sqrt(corner_tris.size()) * 3);
|
||||
if (corner_tris.is_empty()) {
|
||||
if (corner_tris_.size() == 0)
|
||||
return;
|
||||
|
||||
std::string format = "ReverseUVSampler::ReverseUVSampler(" +
|
||||
std::to_string(corner_tris_.size()) + ")";
|
||||
SCOPED_TIMER_AVERAGED(format);
|
||||
const int3 &tri = corner_tris_[0];
|
||||
const float2 &uv_0 = uv_map_[tri[0]];
|
||||
float2 min_uv, max_uv;
|
||||
min_uv = max_uv = uv_0;
|
||||
int live_count = 0;
|
||||
|
||||
Vector<int8_t> mask(corner_tris_.size());
|
||||
|
||||
bool support_set = (supp_uv_max_.x > supp_uv_min_.x && supp_uv_max_.y > supp_uv_min_.y);
|
||||
|
||||
Eugene-Kuznetsov marked this conversation as resolved
Outdated
Iliya Katushenock
commented
`math::max<int>`
|
||||
/* We walk through the list of triangles three times.
|
||||
* 1st pass: calculate the bounding box and determine which triangles are
|
||||
* to be used for sampling. Set the cell resolution.
|
||||
* 2nd pass: calculate how many triangles hit each cell. Allocate the index table.
|
||||
* 3rd pass: populate the index table. */
|
||||
for (const int tri_i : corner_tris_.index_range()) {
|
||||
const int3 &tri = corner_tris_[tri_i];
|
||||
const float2 &uv_0 = uv_map_[tri[0]];
|
||||
const float2 &uv_1 = uv_map_[tri[1]];
|
||||
const float2 &uv_2 = uv_map_[tri[2]];
|
||||
float2 tri_min_uv = math::min(math::min(uv_0, uv_1), uv_2);
|
||||
float2 tri_max_uv = math::max(math::max(uv_0, uv_1), uv_2);
|
||||
if ((!support_set) || (tri_max_uv.x >= supp_uv_min_.x && tri_min_uv.x <= supp_uv_max_.x &&
|
||||
tri_max_uv.y >= supp_uv_min_.y && tri_min_uv.y <= supp_uv_max_.y)) {
|
||||
live_count++;
|
||||
mask[tri_i] = 1;
|
||||
min_uv = math::min(min_uv, tri_min_uv);
|
||||
max_uv = math::max(max_uv, tri_max_uv);
|
||||
} else {
|
||||
mask[tri_i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
threading::EnumerableThreadSpecific<LocalData> data_per_thread;
|
||||
sort_tris_into_rows(uv_map_, corner_tris_, resolution_, data_per_thread);
|
||||
/* Balance between construction time and evaluation time. Smaller values
|
||||
* lead to faster construction (fewer cells) but slower lookups. At 1.0,
|
||||
* we expect ~2-4 triangles per cell. */
|
||||
float magic_number = 0.5f;
|
||||
float mult = magic_number * sqrt(float(live_count));
|
||||
live_count_ = live_count;
|
||||
|
||||
VectorSet<int> all_ys;
|
||||
Vector<const LocalData *> local_data_vec;
|
||||
for (const LocalData &local_data : data_per_thread) {
|
||||
local_data_vec.append(&local_data);
|
||||
for (const int y : local_data.rows.keys()) {
|
||||
all_ys.add(y);
|
||||
}
|
||||
}
|
||||
resolution_.x = math::max<int>(1, mult / (max_uv[0] - min_uv[0] + 1e-6));
|
||||
resolution_.y = math::max<int>(1, mult / (max_uv[1] - min_uv[1] + 1e-6));
|
||||
|
||||
const Bounds<int> y_bounds = *bounds::min_max(all_ys.as_span());
|
||||
lookup_grid_->y_min = y_bounds.min;
|
||||
uv_base_ = min_uv;
|
||||
uv_max_ = max_uv;
|
||||
span_ = int2{int((max_uv.x - min_uv.x) * resolution_.x),
|
||||
int((max_uv.y - min_uv.y) * resolution_.y)};
|
||||
span_.x += 1;
|
||||
span_.y += 1;
|
||||
|
||||
const int rows_num = y_bounds.max - y_bounds.min + 1;
|
||||
lookup_grid_->rows.reinitialize(rows_num);
|
||||
cell_populations_.resize(span_.x * span_.y + 1, 0);
|
||||
|
||||
finish_rows(all_ys, local_data_vec, y_bounds, *lookup_grid_);
|
||||
}
|
||||
for (const int tri_i : corner_tris_.index_range()) {
|
||||
if (!mask[tri_i])
|
||||
continue;
|
||||
|
||||
static Span<int> lookup_tris_in_cell(const int2 cell,
|
||||
const ReverseUVSampler::LookupGrid &lookup_grid)
|
||||
{
|
||||
if (cell.y < lookup_grid.y_min) {
|
||||
return {};
|
||||
}
|
||||
if (cell.y >= lookup_grid.y_min + lookup_grid.rows.size()) {
|
||||
return {};
|
||||
}
|
||||
const Row &row = lookup_grid.rows[cell.y - lookup_grid.y_min];
|
||||
if (cell.x < row.x_min) {
|
||||
return {};
|
||||
}
|
||||
if (cell.x > row.x_max) {
|
||||
return {};
|
||||
}
|
||||
const int offset = row.offsets[cell.x - row.x_min];
|
||||
const int tris_num = row.offsets[cell.x - row.x_min + 1] - offset;
|
||||
return row.tri_indices.as_span().slice(offset, tris_num);
|
||||
}
|
||||
|
||||
ReverseUVSampler::Result ReverseUVSampler::sample(const float2 &query_uv) const
|
||||
{
|
||||
const int2 cell = uv_to_cell(query_uv, resolution_);
|
||||
const Span<int> tri_indices = lookup_tris_in_cell(cell, *lookup_grid_);
|
||||
|
||||
float best_dist = FLT_MAX;
|
||||
float3 best_bary_weights;
|
||||
int best_tri_index;
|
||||
|
||||
/* The distance to an edge that is allowed to be inside or outside the triangle. Without this,
|
||||
* the lookup can fail for floating point accuracy reasons when the uv is almost exact on an
|
||||
* edge. */
|
||||
const float edge_epsilon = 0.00001f;
|
||||
|
||||
for (const int tri_i : tri_indices) {
|
||||
const int3 &tri = corner_tris_[tri_i];
|
||||
const float2 &uv_0 = uv_map_[tri[0]];
|
||||
const float2 &uv_1 = uv_map_[tri[1]];
|
||||
const float2 &uv_2 = uv_map_[tri[2]];
|
||||
float3 bary_weights;
|
||||
if (!barycentric_coords_v2(uv_0, uv_1, uv_2, query_uv, bary_weights)) {
|
||||
continue;
|
||||
float2 tri_min_uv = math::min(math::min(uv_0, uv_1), uv_2);
|
||||
float2 tri_max_uv = math::max(math::max(uv_0, uv_1), uv_2);
|
||||
const int2 min_key = uv_to_cell_key(tri_min_uv, uv_base_, resolution_, span_);
|
||||
const int2 max_key = uv_to_cell_key(tri_max_uv, uv_base_, resolution_, span_);
|
||||
for (int key_y = min_key.y; key_y <= max_key.y; key_y++) {
|
||||
for (int key_x = min_key.x; key_x <= max_key.x; key_x++) {
|
||||
cell_populations_[key_x + key_y * span_.x + 1]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* If #query_uv is in the triangle, the distance is <= 0. Otherwise, the larger the distance,
|
||||
* the further away the uv is from the triangle. */
|
||||
const float x_dist = std::max(-bary_weights.x, bary_weights.x - 1.0f);
|
||||
const float y_dist = std::max(-bary_weights.y, bary_weights.y - 1.0f);
|
||||
const float z_dist = std::max(-bary_weights.z, bary_weights.z - 1.0f);
|
||||
const float dist = std::max({x_dist, y_dist, z_dist});
|
||||
int total_entries = 0;
|
||||
for (int i = 0; i < span_.x * span_.y; i++) {
|
||||
total_entries += cell_populations_[i + 1];
|
||||
cell_populations_[i + 1] = total_entries;
|
||||
}
|
||||
corner_tris_by_cell_.resize(total_entries);
|
||||
|
||||
if (dist <= 0.0f && best_dist <= 0.0f) {
|
||||
const float worse_dist = std::max(dist, best_dist);
|
||||
/* Allow ignoring multiple triangle intersections if the uv is almost exactly on an edge. */
|
||||
if (worse_dist < -edge_epsilon) {
|
||||
/* The uv sample is in multiple triangles. */
|
||||
return Result{ResultType::Multiple};
|
||||
for (const int tri_i : corner_tris_.index_range()) {
|
||||
if (!mask[tri_i])
|
||||
continue;
|
||||
|
||||
const int3 &tri = corner_tris_[tri_i];
|
||||
const float2 &uv_0 = uv_map_[tri[0]];
|
||||
const float2 &uv_1 = uv_map_[tri[1]];
|
||||
const float2 &uv_2 = uv_map_[tri[2]];
|
||||
float2 tri_min_uv = math::min(math::min(uv_0, uv_1), uv_2);
|
||||
float2 tri_max_uv = math::max(math::max(uv_0, uv_1), uv_2);
|
||||
const int2 min_key = uv_to_cell_key(tri_min_uv, uv_base_, resolution_, span_);
|
||||
const int2 max_key = uv_to_cell_key(tri_max_uv, uv_base_, resolution_, span_);
|
||||
for (int key_y = min_key.y; key_y <= max_key.y; key_y++) {
|
||||
for (int key_x = min_key.x; key_x <= max_key.x; key_x++) {
|
||||
int &addr = cell_populations_[key_x + key_y * span_.x];
|
||||
corner_tris_by_cell_[addr] = tri_i;
|
||||
addr++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ReverseUVSampler::sample(
|
||||
const float2 &query_uv, ReverseUVSampler::Result &result, bool hint) const
|
||||
{
|
||||
if (hint && result.type == ResultType::Ok) {
|
||||
const int tri_i = result.tri_index;
|
||||
if (tri_i >= 0 && tri_i < corner_tris_.size()) {
|
||||
const int3 &tri = corner_tris_[tri_i];
|
||||
const float2 &uv_0 = uv_map_[tri[0]];
|
||||
const float2 &uv_1 = uv_map_[tri[1]];
|
||||
const float2 &uv_2 = uv_map_[tri[2]];
|
||||
float3 bary_weights;
|
||||
if (barycentric_coords_v2(uv_0, uv_1, uv_2, query_uv, bary_weights)) {
|
||||
/* If #query_uv is in the triangle, the distance is <= 0. Otherwise, the larger the
|
||||
* distance, the further away the uv is from the triangle. */
|
||||
const float x_dist = std::max(-bary_weights.x, bary_weights.x - 1.0f);
|
||||
const float y_dist = std::max(-bary_weights.y, bary_weights.y - 1.0f);
|
||||
const float z_dist = std::max(-bary_weights.z, bary_weights.z - 1.0f);
|
||||
const float dist = std::max(std::max(x_dist, y_dist), z_dist);
|
||||
|
||||
if (dist <= 0.0f) {
|
||||
result = Result{ResultType::Ok, tri_i, math::clamp(bary_weights, 0.0f, 1.0f)};
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (dist < best_dist) {
|
||||
best_dist = dist;
|
||||
best_bary_weights = bary_weights;
|
||||
best_tri_index = tri_i;
|
||||
const int2 cell_key = uv_to_cell_key(query_uv, uv_base_, resolution_, span_);
|
||||
|
||||
int cell = cell_key.x + cell_key.y * span_.x;
|
||||
int start_pos = (cell == 0) ? 0 : cell_populations_[cell - 1];
|
||||
int end_pos = cell_populations_[cell];
|
||||
auto tri_indices = Span<int>(&corner_tris_by_cell_[start_pos], end_pos - start_pos);
|
||||
|
||||
float best_dist = FLT_MAX;
|
||||
float3 best_bary_weights;
|
||||
int best_tri_index;
|
||||
|
||||
/* The distance to an edge that is allowed to be inside or outside the triangle. Without this,
|
||||
* the lookup can fail for floating point accuracy reasons when the uv is almost exact on an
|
||||
* edge. */
|
||||
const float edge_epsilon = 0.00001f;
|
||||
|
||||
for (const int tri_i : tri_indices) {
|
||||
const int3 &tri = corner_tris_[tri_i];
|
||||
const float2 &uv_0 = uv_map_[tri[0]];
|
||||
const float2 &uv_1 = uv_map_[tri[1]];
|
||||
const float2 &uv_2 = uv_map_[tri[2]];
|
||||
float3 bary_weights;
|
||||
if (!barycentric_coords_v2(uv_0, uv_1, uv_2, query_uv, bary_weights)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* If #query_uv is in the triangle, the distance is <= 0. Otherwise, the larger the distance,
|
||||
* the further away the uv is from the triangle. */
|
||||
const float x_dist = std::max(-bary_weights.x, bary_weights.x - 1.0f);
|
||||
const float y_dist = std::max(-bary_weights.y, bary_weights.y - 1.0f);
|
||||
const float z_dist = std::max(-bary_weights.z, bary_weights.z - 1.0f);
|
||||
const float dist = std::max({x_dist, y_dist, z_dist});
|
||||
|
||||
if (dist <= 0.0f && best_dist <= 0.0f) {
|
||||
const float worse_dist = std::max(dist, best_dist);
|
||||
/* Allow ignoring multiple triangle intersections if the uv is almost exactly on an edge.
|
||||
*/
|
||||
if (worse_dist < -edge_epsilon) {
|
||||
/* The uv sample is in multiple triangles. */
|
||||
result = Result{ResultType::Multiple};
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (dist < best_dist) {
|
||||
best_dist = dist;
|
||||
best_bary_weights = bary_weights;
|
||||
best_tri_index = tri_i;
|
||||
}
|
||||
}
|
||||
|
||||
/* Allow using the closest (but not intersecting) triangle if the uv is almost exactly on an
|
||||
* edge. */
|
||||
if (best_dist < edge_epsilon) {
|
||||
result = Result{ResultType::Ok, best_tri_index, math::clamp(best_bary_weights, 0.0f, 1.0f)};
|
||||
return;
|
||||
}
|
||||
|
||||
result = Result{ResultType::None, best_tri_index, best_bary_weights};
|
||||
}
|
||||
|
||||
/* Allow using the closest (but not intersecting) triangle if the uv is almost exactly on an
|
||||
* edge. */
|
||||
if (best_dist < edge_epsilon) {
|
||||
return Result{ResultType::Ok, best_tri_index, math::clamp(best_bary_weights, 0.0f, 1.0f)};
|
||||
void ReverseUVSampler::sample_many(
|
||||
const Span<float2> query_uvs, MutableSpan<Result> r_results, bool hints) const
|
||||
{
|
||||
BLI_assert(query_uvs.size() == r_results.size());
|
||||
threading::parallel_for(query_uvs.index_range(), 256, [&](const IndexRange range) {
|
||||
for (const int i : range) {
|
||||
this->sample(query_uvs[i], r_results[i], hints);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return Result{};
|
||||
}
|
||||
|
||||
ReverseUVSampler::~ReverseUVSampler() = default;
|
||||
|
||||
void ReverseUVSampler::sample_many(const Span<float2> query_uvs,
|
||||
MutableSpan<Result> r_results) const
|
||||
{
|
||||
BLI_assert(query_uvs.size() == r_results.size());
|
||||
threading::parallel_for(query_uvs.index_range(), 256, [&](const IndexRange range) {
|
||||
for (const int i : range) {
|
||||
r_results[i] = this->sample(query_uvs[i]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace blender::geometry
|
||||
|
|
|
@ -1742,6 +1742,12 @@ typedef struct NodeGeometryCurveTrim {
|
|||
uint8_t mode;
|
||||
} NodeGeometryCurveTrim;
|
||||
|
||||
// struct NodeGeometryDeformCurvesOnSurfaceCache;
|
||||
|
||||
typedef struct NodeGeometryDeformCurvesOnSurface {
|
||||
uint64_t cache;
|
||||
} NodeGeometryDeformCurvesOnSurface;
|
||||
|
||||
typedef struct NodeGeometryCurveToPoints {
|
||||
/** #GeometryNodeCurveResampleMode. */
|
||||
uint8_t mode;
|
||||
|
|
|
@ -60,7 +60,7 @@ static void node_layout(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr)
|
|||
|
||||
static void node_init(bNodeTree * /*tree*/, bNode *node)
|
||||
{
|
||||
NodeGeometryCurveTrim *data = MEM_cnew<NodeGeometryCurveTrim>(__func__);
|
||||
NodeGeometryCurveTrim *data = MEM_new<NodeGeometryCurveTrim>(__func__);
|
||||
|
||||
data->mode = GEO_NODE_CURVE_SAMPLE_FACTOR;
|
||||
node->storage = data;
|
||||
|
|
|
@ -21,13 +21,34 @@
|
|||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include <map>
|
||||
|
||||
using blender::geometry::ReverseUVSampler;
|
||||
|
||||
struct NodeGeometryDeformCurvesOnSurfaceCacheEntry {
|
||||
blender::Array<ReverseUVSampler::Result> old_samples;
|
||||
blender::Array<ReverseUVSampler::Result> new_samples;
|
||||
blender::uint2 tri_counts = {0, 0};
|
||||
blender::uint2 uv_map_counts = {0, 0};
|
||||
std::mutex mutex;
|
||||
};
|
||||
|
||||
struct NodeGeometryDeformCurvesOnSurfaceCache
|
||||
: public blender::Map<blender::uint2, NodeGeometryDeformCurvesOnSurfaceCacheEntry *> {
|
||||
~NodeGeometryDeformCurvesOnSurfaceCache()
|
||||
{
|
||||
foreach_item([&]([[maybe_unused]] auto k, auto v) {
|
||||
MEM_delete<NodeGeometryDeformCurvesOnSurfaceCacheEntry>(v);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
namespace blender::nodes::node_geo_deform_curves_on_surface_cc {
|
||||
|
||||
using bke::CurvesGeometry;
|
||||
using bke::attribute_math::mix3;
|
||||
using geometry::ReverseUVSampler;
|
||||
|
||||
NODE_STORAGE_FUNCS(NodeGeometryCurveTrim)
|
||||
NODE_STORAGE_FUNCS(NodeGeometryDeformCurvesOnSurface)
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
|
@ -35,28 +56,96 @@ static void node_declare(NodeDeclarationBuilder &b)
|
|||
b.add_output<decl::Geometry>("Curves").propagate_all();
|
||||
}
|
||||
|
||||
static void node_init(bNodeTree *, bNode *node)
|
||||
{
|
||||
NodeGeometryDeformCurvesOnSurface *data = MEM_new<NodeGeometryDeformCurvesOnSurface>(__func__);
|
||||
data->cache = 0;
|
||||
node->storage = data;
|
||||
}
|
||||
|
||||
/***
|
||||
* Surface_mesh_orig usually stays unchanged from call to call, but it may change.
|
||||
* We use tri_counts and uv_map_counts to detect changes, and use cached sampling results
|
||||
* as hints as long as counts stay the same. (We still need to run sampling, because we
|
||||
* don't keep full info about the previous mesh, so we can't detect all changes.)
|
||||
*
|
||||
* With regard to surface_mesh_eval, there are a few possible scenarios.
|
||||
* 1. surface_mesh_eval is topologically the same as surface_mesh_orig, except possibly
|
||||
* deformed by armature / shape keys (identical corner lists and UV maps); there's no
|
||||
* need to rerun ReverseUVSampler, because all curves stay attached to the same places.
|
||||
*
|
||||
* 2. The mesh is substantially different; commonly happens if there's a subdivide modifier
|
||||
* on the mesh (because 'surface_mesh_orig' will be the version without subdivision).
|
||||
*
|
||||
* -> 2.1. It is different from surface_mesh_orig, but it is (topologically) the same as
|
||||
* in the previous call. Use cached result as hints.
|
||||
*
|
||||
* -> 2.2. It is different from both surface_mesh_orig and from the previous call. Run
|
||||
* ReverseUVSampler without hints.
|
||||
*
|
||||
* */
|
||||
static void deform_curves(const CurvesGeometry &curves,
|
||||
const Mesh &surface_mesh_old,
|
||||
const Mesh &surface_mesh_new,
|
||||
const Span<float2> curve_attachment_uvs,
|
||||
const ReverseUVSampler &reverse_uv_sampler_old,
|
||||
const ReverseUVSampler &reverse_uv_sampler_new,
|
||||
const ReverseUVSampler *reverse_uv_sampler_old,
|
||||
const ReverseUVSampler *reverse_uv_sampler_new,
|
||||
const Span<float3> corner_normals_old,
|
||||
const Span<float3> corner_normals_new,
|
||||
const Span<float3> rest_positions,
|
||||
const float4x4 &surface_to_curves,
|
||||
const Span<float3> r_orig_positions,
|
||||
MutableSpan<float3> r_positions,
|
||||
MutableSpan<float3x3> r_rotations,
|
||||
std::atomic<int> &r_invalid_uv_count)
|
||||
std::atomic<int> &r_invalid_uv_count,
|
||||
uint2 uv_map_counts,
|
||||
NodeGeometryDeformCurvesOnSurfaceCacheEntry *cache = nullptr)
|
||||
{
|
||||
/* Find attachment points on old and new mesh. */
|
||||
const int curves_num = curves.curves_num();
|
||||
Array<ReverseUVSampler::Result> surface_samples_old(curves_num);
|
||||
Array<ReverseUVSampler::Result> surface_samples_new(curves_num);
|
||||
threading::parallel_invoke(
|
||||
1024 < curves_num,
|
||||
[&]() { reverse_uv_sampler_old.sample_many(curve_attachment_uvs, surface_samples_old); },
|
||||
[&]() { reverse_uv_sampler_new.sample_many(curve_attachment_uvs, surface_samples_new); });
|
||||
const uint old_mesh_size = surface_mesh_old.corner_tris().size();
|
||||
const uint new_mesh_size = surface_mesh_new.corner_tris().size();
|
||||
|
||||
/* this function is not expected to be called from multiple threads for the same object,
|
||||
but we'll lock up just in case */
|
||||
if (cache)
|
||||
cache->mutex.lock();
|
||||
|
||||
bool hints = false;
|
||||
if (cache && cache->old_samples.size() == curves_num && cache->tri_counts[0] == old_mesh_size &&
|
||||
cache->uv_map_counts[0] == uv_map_counts[0])
|
||||
{
|
||||
surface_samples_old = cache->old_samples;
|
||||
hints = true;
|
||||
}
|
||||
|
||||
reverse_uv_sampler_old->sample_many(curve_attachment_uvs, surface_samples_old, hints);
|
||||
|
||||
if (reverse_uv_sampler_new != nullptr) {
|
||||
/*
|
||||
* If we get here, it means that at least one entry in either corner_tris or uv_map is
|
||||
* different between 'orig' and 'eval'.
|
||||
*/
|
||||
hints = false;
|
||||
if (cache && cache->new_samples.size() == curves_num &&
|
||||
cache->tri_counts[1] == new_mesh_size && cache->uv_map_counts[1] == uv_map_counts[1])
|
||||
{
|
||||
hints = true;
|
||||
surface_samples_new = cache->new_samples;
|
||||
}
|
||||
else if (surface_mesh_old.corner_tris().size() == new_mesh_size &&
|
||||
uv_map_counts[0] == uv_map_counts[1])
|
||||
{
|
||||
hints = true;
|
||||
surface_samples_new = surface_samples_old;
|
||||
}
|
||||
reverse_uv_sampler_new->sample_many(curve_attachment_uvs, surface_samples_new, hints);
|
||||
}
|
||||
else {
|
||||
surface_samples_new = surface_samples_old;
|
||||
}
|
||||
|
||||
const float4x4 curves_to_surface = math::invert(surface_to_curves);
|
||||
|
||||
|
@ -71,17 +160,49 @@ static void deform_curves(const CurvesGeometry &curves,
|
|||
const OffsetIndices points_by_curve = curves.points_by_curve();
|
||||
|
||||
threading::parallel_for(curves.curves_range(), 256, [&](const IndexRange range) {
|
||||
int last_known_good_point = -1;
|
||||
for (const int curve_i : range) {
|
||||
bool invalid = false;
|
||||
const ReverseUVSampler::Result &surface_sample_old = surface_samples_old[curve_i];
|
||||
if (surface_sample_old.type != ReverseUVSampler::ResultType::Ok) {
|
||||
r_invalid_uv_count++;
|
||||
continue;
|
||||
}
|
||||
if (surface_sample_old.type != ReverseUVSampler::ResultType::Ok)
|
||||
invalid = true;
|
||||
|
||||
const ReverseUVSampler::Result &surface_sample_new = surface_samples_new[curve_i];
|
||||
if (surface_sample_new.type != ReverseUVSampler::ResultType::Ok) {
|
||||
if (surface_sample_new.type == ReverseUVSampler::ResultType::Multiple) {
|
||||
invalid = true;
|
||||
} else if(surface_sample_new.type == ReverseUVSampler::ResultType::None) {
|
||||
/* UVs may end up outside the triangle during mapping from unsubdivided to
|
||||
* subdivided mesh. */
|
||||
auto bary_weights = surface_sample_new.bary_weights;
|
||||
const float x_dist = std::max(-bary_weights.x, bary_weights.x - 1.0f);
|
||||
const float y_dist = std::max(-bary_weights.y, bary_weights.y - 1.0f);
|
||||
const float z_dist = std::max(-bary_weights.z, bary_weights.z - 1.0f);
|
||||
const float dist = std::max({x_dist, y_dist, z_dist});
|
||||
if(dist > 0.01f)
|
||||
invalid = true;
|
||||
}
|
||||
|
||||
if (invalid) {
|
||||
r_invalid_uv_count++;
|
||||
const IndexRange points = points_by_curve[curve_i];
|
||||
/* There are no particularly good options here. Deleting invalid curves is
|
||||
* potentially an very expensive operation. Leaving them at original locations
|
||||
* creates clumps of unattached hairs hanging in midair.
|
||||
*
|
||||
* We set all points of an invalid curve to the same value, reducing it to
|
||||
* a point (making it invisible in 'strand' mode and very small in 'strip' mode.)
|
||||
* To obscure it even further, we also move it to the location of the last known
|
||||
* good root.
|
||||
* */
|
||||
float3 pos = last_known_good_point>=0
|
||||
? r_positions[last_known_good_point]
|
||||
: r_orig_positions[points[0]];
|
||||
for (const int point_i : points)
|
||||
r_positions[point_i] = pos;
|
||||
continue;
|
||||
}
|
||||
if(points_by_curve[curve_i].size() > 0)
|
||||
last_known_good_point = points_by_curve[curve_i][0];
|
||||
|
||||
const int3 &tri_old = surface_corner_tris_old[surface_sample_old.tri_index];
|
||||
const int3 &tri_new = surface_corner_tris_new[surface_sample_new.tri_index];
|
||||
|
@ -185,7 +306,7 @@ static void deform_curves(const CurvesGeometry &curves,
|
|||
/* Actually transform all points. */
|
||||
const IndexRange points = points_by_curve[curve_i];
|
||||
for (const int point_i : points) {
|
||||
const float3 old_point_pos = r_positions[point_i];
|
||||
const float3 old_point_pos = r_orig_positions[point_i];
|
||||
const float3 new_point_pos = math::transform_point(curve_transform, old_point_pos);
|
||||
r_positions[point_i] = new_point_pos;
|
||||
}
|
||||
|
@ -197,6 +318,28 @@ static void deform_curves(const CurvesGeometry &curves,
|
|||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (cache) {
|
||||
cache->old_samples = std::move(surface_samples_old);
|
||||
cache->new_samples = std::move(surface_samples_new);
|
||||
cache->tri_counts = uint2{old_mesh_size, new_mesh_size};
|
||||
cache->uv_map_counts = uv_map_counts;
|
||||
cache->mutex.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T> bool threaded_compare(const T &va, const T &vb)
|
||||
{
|
||||
std::atomic<bool> difference(false);
|
||||
threading::parallel_for(IndexRange(va.size()), 1024, [&](const IndexRange range) {
|
||||
for (const int i : range) {
|
||||
if (va[i] != vb[i]) {
|
||||
difference = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
return difference;
|
||||
}
|
||||
|
||||
static void node_geo_exec(GeoNodeExecParams params)
|
||||
|
@ -301,8 +444,32 @@ static void node_geo_exec(GeoNodeExecParams params)
|
|||
|
||||
const Span<int3> corner_tris_orig = surface_mesh_orig->corner_tris();
|
||||
const Span<int3> corner_tris_eval = surface_mesh_eval->corner_tris();
|
||||
const ReverseUVSampler reverse_uv_sampler_orig{uv_map_orig, corner_tris_orig};
|
||||
const ReverseUVSampler reverse_uv_sampler_eval{uv_map_eval, corner_tris_eval};
|
||||
uint2 uv_map_counts{(uint)uv_map_orig.size(), (uint)uv_map_eval.size()};
|
||||
|
||||
bool same_mesh = false;
|
||||
float2 uv_min = {0, 0}, uv_max = {0, 0};
|
||||
|
||||
if (surface_uv_coords.size() > 0) {
|
||||
uv_min = uv_max = surface_uv_coords[0];
|
||||
for (auto x : surface_uv_coords) {
|
||||
uv_min = math::min(uv_min, x);
|
||||
uv_max = math::max(uv_max, x);
|
||||
}
|
||||
}
|
||||
|
||||
same_mesh = (corner_tris_orig.size() == corner_tris_eval.size()) &&
|
||||
(uv_map_orig.size() == uv_map_eval.size());
|
||||
if (same_mesh)
|
||||
same_mesh = !threaded_compare(corner_tris_eval, corner_tris_orig);
|
||||
if (same_mesh)
|
||||
same_mesh = !threaded_compare(uv_map_eval, uv_map_orig);
|
||||
|
||||
std::unique_ptr<ReverseUVSampler> reverse_uv_sampler_orig(
|
||||
new ReverseUVSampler(uv_map_orig, corner_tris_orig, uv_min, uv_max));
|
||||
std::unique_ptr<ReverseUVSampler> reverse_uv_sampler_eval;
|
||||
if (!same_mesh)
|
||||
reverse_uv_sampler_eval.reset(
|
||||
new ReverseUVSampler(uv_map_eval, corner_tris_eval, uv_min, uv_max));
|
||||
|
||||
/* Retrieve face corner normals from each mesh. It's necessary to use face corner normals
|
||||
* because face normals or vertex normals may lose information (custom normals, auto smooth) in
|
||||
|
@ -329,20 +496,45 @@ static void node_geo_exec(GeoNodeExecParams params)
|
|||
edit_hint_rotations = *edit_hints->deform_mats;
|
||||
}
|
||||
|
||||
bNode &node = const_cast<bNode &>(params.node());
|
||||
NodeGeometryDeformCurvesOnSurface &storage = node_storage(node);
|
||||
NodeGeometryDeformCurvesOnSurfaceCache *cache = nullptr;
|
||||
NodeGeometryDeformCurvesOnSurfaceCacheEntry *cache_entry = nullptr;
|
||||
|
||||
cache = reinterpret_cast<NodeGeometryDeformCurvesOnSurfaceCache *>(storage.cache);
|
||||
if (cache == 0)
|
||||
cache = MEM_new<NodeGeometryDeformCurvesOnSurfaceCache>(__func__);
|
||||
storage.cache = reinterpret_cast<uint64_t &>(cache);
|
||||
/* In case of multiple curves objects on the same mesh, a unique ID of the curves
|
||||
* object would work better, but I can't find one */
|
||||
uint2 key{(uint)surface_mesh_orig->id.session_uid, (uint)curves.curves_num()};
|
||||
if (!cache->contains(key))
|
||||
cache->add(key, MEM_new<NodeGeometryDeformCurvesOnSurfaceCacheEntry>(__func__));
|
||||
cache_entry = cache->lookup(key);
|
||||
|
||||
auto orig_positions = curves.positions();
|
||||
/* This is a somewhat expensive call: it allocates memory for the output array.
|
||||
We pass 'preserve'='false' to indicate that we don't need the contents of the
|
||||
buffer we receive; otherwise, it would be filled twice. */
|
||||
MutableSpan<float3> new_positions = curves.positions_for_write(false);
|
||||
|
||||
if (edit_hint_positions.is_empty()) {
|
||||
deform_curves(curves,
|
||||
*surface_mesh_orig,
|
||||
*surface_mesh_eval,
|
||||
surface_uv_coords,
|
||||
reverse_uv_sampler_orig,
|
||||
reverse_uv_sampler_eval,
|
||||
reverse_uv_sampler_orig.get(),
|
||||
reverse_uv_sampler_eval.get(),
|
||||
corner_normals_orig,
|
||||
corner_normals_eval,
|
||||
rest_positions,
|
||||
transforms.surface_to_curves,
|
||||
curves.positions_for_write(),
|
||||
orig_positions,
|
||||
new_positions,
|
||||
edit_hint_rotations,
|
||||
invalid_uv_count);
|
||||
invalid_uv_count,
|
||||
uv_map_counts,
|
||||
cache_entry);
|
||||
}
|
||||
else {
|
||||
/* First deform the actual curves in the input geometry. */
|
||||
|
@ -350,15 +542,19 @@ static void node_geo_exec(GeoNodeExecParams params)
|
|||
*surface_mesh_orig,
|
||||
*surface_mesh_eval,
|
||||
surface_uv_coords,
|
||||
reverse_uv_sampler_orig,
|
||||
reverse_uv_sampler_eval,
|
||||
reverse_uv_sampler_orig.get(),
|
||||
reverse_uv_sampler_eval.get(),
|
||||
corner_normals_orig,
|
||||
corner_normals_eval,
|
||||
rest_positions,
|
||||
transforms.surface_to_curves,
|
||||
curves.positions_for_write(),
|
||||
orig_positions,
|
||||
new_positions,
|
||||
{},
|
||||
invalid_uv_count);
|
||||
invalid_uv_count,
|
||||
uv_map_counts,
|
||||
cache_entry);
|
||||
|
||||
/* Then also deform edit curve information for use in sculpt mode. */
|
||||
const CurvesGeometry &curves_orig = edit_hints->curves_id_orig.geometry.wrap();
|
||||
const VArraySpan<float2> surface_uv_coords_orig = *curves_orig.attributes().lookup_or_default(
|
||||
|
@ -368,18 +564,19 @@ static void node_geo_exec(GeoNodeExecParams params)
|
|||
*surface_mesh_orig,
|
||||
*surface_mesh_eval,
|
||||
surface_uv_coords_orig,
|
||||
reverse_uv_sampler_orig,
|
||||
reverse_uv_sampler_eval,
|
||||
reverse_uv_sampler_orig.get(),
|
||||
reverse_uv_sampler_eval.get(),
|
||||
corner_normals_orig,
|
||||
corner_normals_eval,
|
||||
rest_positions,
|
||||
transforms.surface_to_curves,
|
||||
edit_hint_positions,
|
||||
edit_hint_positions,
|
||||
edit_hint_rotations,
|
||||
invalid_uv_count);
|
||||
invalid_uv_count,
|
||||
uv_map_counts);
|
||||
}
|
||||
}
|
||||
|
||||
curves.tag_positions_changed();
|
||||
|
||||
if (invalid_uv_count) {
|
||||
|
@ -391,6 +588,31 @@ static void node_geo_exec(GeoNodeExecParams params)
|
|||
params.set_output("Curves", curves_geometry);
|
||||
}
|
||||
|
||||
void node_free_CurvesOnSurface_storage(bNode *node);
|
||||
void node_copy_CurvesOnSurface_storage(bNodeTree *, bNode *, const bNode *);
|
||||
|
||||
void node_free_CurvesOnSurface_storage(bNode *node)
|
||||
{
|
||||
if (node->storage) {
|
||||
auto storage = (NodeGeometryDeformCurvesOnSurface *)node->storage;
|
||||
if (storage->cache) {
|
||||
NodeGeometryDeformCurvesOnSurfaceCache *cache =
|
||||
reinterpret_cast<NodeGeometryDeformCurvesOnSurfaceCache *>(storage->cache);
|
||||
MEM_delete<NodeGeometryDeformCurvesOnSurfaceCache>(cache);
|
||||
}
|
||||
MEM_delete<NodeGeometryDeformCurvesOnSurface>(storage);
|
||||
}
|
||||
}
|
||||
|
||||
void node_copy_CurvesOnSurface_storage(bNodeTree * /*dest_ntree*/,
|
||||
bNode *dest_node,
|
||||
[[maybe_unused]] const bNode *src_node)
|
||||
{
|
||||
auto storage = MEM_new<NodeGeometryDeformCurvesOnSurface>(__func__);
|
||||
storage->cache = 0;
|
||||
dest_node->storage = storage;
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bNodeType ntype;
|
||||
|
@ -398,6 +620,11 @@ static void node_register()
|
|||
&ntype, GEO_NODE_DEFORM_CURVES_ON_SURFACE, "Deform Curves on Surface", NODE_CLASS_GEOMETRY);
|
||||
ntype.geometry_node_execute = node_geo_exec;
|
||||
ntype.declare = node_declare;
|
||||
node_type_storage(&ntype,
|
||||
"NodeGeometryDeformCurvesOnSurface",
|
||||
node_free_CurvesOnSurface_storage,
|
||||
node_copy_CurvesOnSurface_storage);
|
||||
ntype.initfunc = node_init;
|
||||
blender::bke::node_type_size(&ntype, 170, 120, 700);
|
||||
nodeRegisterType(&ntype);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
You should not use
std::vector
instead ofblender::Vector
without strong reason.You should not use
Vector<Vector<A>>
instead ofArray<int> offsets; Array<A> data; GroupedSpan<A> values(offsets, data);
without strong reason (see as examples: #110707. #115142, #111081, #114683).I've switched to blender::Vector. Array/GroupedSpan can't be used here.