Instead of using `DefaultMixer`, use the simpler `mix2` function. This just decreases the overhead of computing each value. In a simple test storing an attribute with the position of each edge, I observed a 1.7x performance improvement: a change from 16 ms to ~9ms for an 8 million edge mesh. Resolves #133196.
1026 lines
37 KiB
C++
1026 lines
37 KiB
C++
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
#include "BLI_generic_virtual_array.hh"
|
|
#include "BLI_math_quaternion.hh"
|
|
#include "BLI_virtual_array.hh"
|
|
|
|
#include "BKE_attribute_math.hh"
|
|
#include "BKE_deform.hh"
|
|
#include "BKE_mesh.hh"
|
|
|
|
#include "DNA_meshdata_types.h"
|
|
#include "DNA_object_types.h"
|
|
|
|
#include "BLI_listbase.h"
|
|
|
|
#include "FN_multi_function_builder.hh"
|
|
|
|
#include "attribute_access_intern.hh"
|
|
|
|
namespace blender::bke {
|
|
|
|
template<typename T>
|
|
static void adapt_mesh_domain_corner_to_point_impl(const Mesh &mesh,
|
|
const VArray<T> &old_values,
|
|
MutableSpan<T> r_values)
|
|
{
|
|
BLI_assert(r_values.size() == mesh.verts_num);
|
|
const Span<int> corner_verts = mesh.corner_verts();
|
|
|
|
attribute_math::DefaultMixer<T> mixer(r_values);
|
|
for (const int corner : IndexRange(mesh.corners_num)) {
|
|
mixer.mix_in(corner_verts[corner], old_values[corner]);
|
|
}
|
|
mixer.finalize();
|
|
}
|
|
|
|
/* A vertex is selected if all connected face corners were selected and it is not loose. */
|
|
template<>
|
|
void adapt_mesh_domain_corner_to_point_impl(const Mesh &mesh,
|
|
const VArray<bool> &old_values,
|
|
MutableSpan<bool> r_values)
|
|
{
|
|
BLI_assert(r_values.size() == mesh.verts_num);
|
|
const Span<int> corner_verts = mesh.corner_verts();
|
|
|
|
r_values.fill(true);
|
|
for (const int corner : IndexRange(mesh.corners_num)) {
|
|
const int point_index = corner_verts[corner];
|
|
|
|
if (!old_values[corner]) {
|
|
r_values[point_index] = false;
|
|
}
|
|
}
|
|
|
|
/* Deselect loose vertices without corners that are still selected from the 'true' default. */
|
|
const LooseVertCache &loose_verts = mesh.verts_no_face();
|
|
if (loose_verts.count > 0) {
|
|
const BitSpan bits = loose_verts.is_loose_bits;
|
|
threading::parallel_for(bits.index_range(), 2048, [&](const IndexRange range) {
|
|
for (const int vert_index : range) {
|
|
if (bits[vert_index]) {
|
|
r_values[vert_index] = false;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
static GVArray adapt_mesh_domain_corner_to_point(const Mesh &mesh, const GVArray &varray)
|
|
{
|
|
GArray<> values(varray.type(), mesh.verts_num);
|
|
attribute_math::convert_to_static_type(varray.type(), [&](auto dummy) {
|
|
using T = decltype(dummy);
|
|
if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) {
|
|
/* We compute all interpolated values at once, because for this interpolation, one has to
|
|
* iterate over all loops anyway. */
|
|
adapt_mesh_domain_corner_to_point_impl<T>(
|
|
mesh, varray.typed<T>(), values.as_mutable_span().typed<T>());
|
|
}
|
|
});
|
|
return GVArray::ForGArray(std::move(values));
|
|
}
|
|
|
|
/**
|
|
* Each corner's value is simply a copy of the value at its vertex.
|
|
*/
|
|
static GVArray adapt_mesh_domain_point_to_corner(const Mesh &mesh, const GVArray &varray)
|
|
{
|
|
const Span<int> corner_verts = mesh.corner_verts();
|
|
|
|
GVArray new_varray;
|
|
attribute_math::convert_to_static_type(varray.type(), [&](auto dummy) {
|
|
using T = decltype(dummy);
|
|
new_varray = VArray<T>::ForFunc(
|
|
mesh.corners_num, [corner_verts, varray = varray.typed<T>()](const int64_t corner) {
|
|
return varray[corner_verts[corner]];
|
|
});
|
|
});
|
|
return new_varray;
|
|
}
|
|
|
|
static GVArray adapt_mesh_domain_corner_to_face(const Mesh &mesh, const GVArray &varray)
|
|
{
|
|
const OffsetIndices faces = mesh.faces();
|
|
|
|
GVArray new_varray;
|
|
attribute_math::convert_to_static_type(varray.type(), [&](auto dummy) {
|
|
using T = decltype(dummy);
|
|
if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) {
|
|
if constexpr (std::is_same_v<T, bool>) {
|
|
new_varray = VArray<T>::ForFunc(
|
|
faces.size(), [faces, varray = varray.typed<bool>()](const int face_index) {
|
|
/* A face is selected if all of its corners were selected. */
|
|
for (const int loop_index : faces[face_index]) {
|
|
if (!varray[loop_index]) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
});
|
|
}
|
|
else {
|
|
new_varray = VArray<T>::ForFunc(
|
|
faces.size(), [faces, varray = varray.typed<T>()](const int face_index) {
|
|
T return_value;
|
|
attribute_math::DefaultMixer<T> mixer({&return_value, 1});
|
|
for (const int loop_index : faces[face_index]) {
|
|
const T value = varray[loop_index];
|
|
mixer.mix_in(0, value);
|
|
}
|
|
mixer.finalize();
|
|
return return_value;
|
|
});
|
|
}
|
|
}
|
|
});
|
|
return new_varray;
|
|
}
|
|
|
|
template<typename T>
|
|
static void adapt_mesh_domain_corner_to_edge_impl(const Mesh &mesh,
|
|
const VArray<T> &old_values,
|
|
MutableSpan<T> r_values)
|
|
{
|
|
BLI_assert(r_values.size() == mesh.edges_num);
|
|
const OffsetIndices faces = mesh.faces();
|
|
const Span<int> corner_edges = mesh.corner_edges();
|
|
|
|
attribute_math::DefaultMixer<T> mixer(r_values);
|
|
|
|
for (const int face_index : faces.index_range()) {
|
|
const IndexRange face = faces[face_index];
|
|
|
|
/* For every edge, mix values from the two adjacent corners (the current and next corner). */
|
|
for (const int corner : face) {
|
|
const int next_corner = mesh::face_corner_next(face, corner);
|
|
const int edge_index = corner_edges[corner];
|
|
mixer.mix_in(edge_index, old_values[corner]);
|
|
mixer.mix_in(edge_index, old_values[next_corner]);
|
|
}
|
|
}
|
|
|
|
mixer.finalize();
|
|
}
|
|
|
|
/* An edge is selected if all corners on adjacent faces were selected. */
|
|
template<>
|
|
void adapt_mesh_domain_corner_to_edge_impl(const Mesh &mesh,
|
|
const VArray<bool> &old_values,
|
|
MutableSpan<bool> r_values)
|
|
{
|
|
BLI_assert(r_values.size() == mesh.edges_num);
|
|
const OffsetIndices faces = mesh.faces();
|
|
const Span<int> corner_edges = mesh.corner_edges();
|
|
|
|
r_values.fill(true);
|
|
for (const int face_index : faces.index_range()) {
|
|
const IndexRange face = faces[face_index];
|
|
|
|
for (const int corner : face) {
|
|
const int next_corner = mesh::face_corner_next(face, corner);
|
|
const int edge_index = corner_edges[corner];
|
|
if (!old_values[corner] || !old_values[next_corner]) {
|
|
r_values[edge_index] = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
const LooseEdgeCache &loose_edges = mesh.loose_edges();
|
|
if (loose_edges.count > 0) {
|
|
/* Deselect loose edges without corners that are still selected from the 'true' default. */
|
|
threading::parallel_for(IndexRange(mesh.edges_num), 2048, [&](const IndexRange range) {
|
|
for (const int edge_index : range) {
|
|
if (loose_edges.is_loose_bits[edge_index]) {
|
|
r_values[edge_index] = false;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
static GVArray adapt_mesh_domain_corner_to_edge(const Mesh &mesh, const GVArray &varray)
|
|
{
|
|
GArray<> values(varray.type(), mesh.edges_num);
|
|
attribute_math::convert_to_static_type(varray.type(), [&](auto dummy) {
|
|
using T = decltype(dummy);
|
|
if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) {
|
|
adapt_mesh_domain_corner_to_edge_impl<T>(
|
|
mesh, varray.typed<T>(), values.as_mutable_span().typed<T>());
|
|
}
|
|
});
|
|
return GVArray::ForGArray(std::move(values));
|
|
}
|
|
|
|
template<typename T>
|
|
void adapt_mesh_domain_face_to_point_impl(const Mesh &mesh,
|
|
const VArray<T> &old_values,
|
|
MutableSpan<T> r_values)
|
|
{
|
|
BLI_assert(r_values.size() == mesh.verts_num);
|
|
const OffsetIndices faces = mesh.faces();
|
|
const Span<int> corner_verts = mesh.corner_verts();
|
|
|
|
attribute_math::DefaultMixer<T> mixer(r_values);
|
|
|
|
for (const int face_index : faces.index_range()) {
|
|
const T value = old_values[face_index];
|
|
for (const int vert : corner_verts.slice(faces[face_index])) {
|
|
mixer.mix_in(vert, value);
|
|
}
|
|
}
|
|
|
|
mixer.finalize();
|
|
}
|
|
|
|
/* A vertex is selected if any of the connected faces were selected. */
|
|
template<>
|
|
void adapt_mesh_domain_face_to_point_impl(const Mesh &mesh,
|
|
const VArray<bool> &old_values,
|
|
MutableSpan<bool> r_values)
|
|
{
|
|
BLI_assert(r_values.size() == mesh.verts_num);
|
|
const OffsetIndices faces = mesh.faces();
|
|
const Span<int> corner_verts = mesh.corner_verts();
|
|
|
|
r_values.fill(false);
|
|
threading::parallel_for(faces.index_range(), 2048, [&](const IndexRange range) {
|
|
for (const int face_index : range) {
|
|
if (old_values[face_index]) {
|
|
for (const int vert : corner_verts.slice(faces[face_index])) {
|
|
r_values[vert] = true;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
static GVArray adapt_mesh_domain_face_to_point(const Mesh &mesh, const GVArray &varray)
|
|
{
|
|
GArray<> values(varray.type(), mesh.verts_num);
|
|
attribute_math::convert_to_static_type(varray.type(), [&](auto dummy) {
|
|
using T = decltype(dummy);
|
|
if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) {
|
|
adapt_mesh_domain_face_to_point_impl<T>(
|
|
mesh, varray.typed<T>(), values.as_mutable_span().typed<T>());
|
|
}
|
|
});
|
|
return GVArray::ForGArray(std::move(values));
|
|
}
|
|
|
|
/* Each corner's value is simply a copy of the value at its face. */
|
|
template<typename T>
|
|
void adapt_mesh_domain_face_to_corner_impl(const Mesh &mesh,
|
|
const VArray<T> &old_values,
|
|
MutableSpan<T> r_values)
|
|
{
|
|
BLI_assert(r_values.size() == mesh.corners_num);
|
|
const OffsetIndices faces = mesh.faces();
|
|
|
|
threading::parallel_for(faces.index_range(), 1024, [&](const IndexRange range) {
|
|
for (const int face_index : range) {
|
|
MutableSpan<T> face_corner_values = r_values.slice(faces[face_index]);
|
|
face_corner_values.fill(old_values[face_index]);
|
|
}
|
|
});
|
|
}
|
|
|
|
static GVArray adapt_mesh_domain_face_to_corner(const Mesh &mesh, const GVArray &varray)
|
|
{
|
|
GArray<> values(varray.type(), mesh.corners_num);
|
|
attribute_math::convert_to_static_type(varray.type(), [&](auto dummy) {
|
|
using T = decltype(dummy);
|
|
if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) {
|
|
adapt_mesh_domain_face_to_corner_impl<T>(
|
|
mesh, varray.typed<T>(), values.as_mutable_span().typed<T>());
|
|
}
|
|
});
|
|
return GVArray::ForGArray(std::move(values));
|
|
}
|
|
|
|
template<typename T>
|
|
void adapt_mesh_domain_face_to_edge_impl(const Mesh &mesh,
|
|
const VArray<T> &old_values,
|
|
MutableSpan<T> r_values)
|
|
{
|
|
BLI_assert(r_values.size() == mesh.edges_num);
|
|
const OffsetIndices faces = mesh.faces();
|
|
const Span<int> corner_edges = mesh.corner_edges();
|
|
|
|
attribute_math::DefaultMixer<T> mixer(r_values);
|
|
|
|
for (const int face_index : faces.index_range()) {
|
|
const T value = old_values[face_index];
|
|
for (const int edge : corner_edges.slice(faces[face_index])) {
|
|
mixer.mix_in(edge, value);
|
|
}
|
|
}
|
|
mixer.finalize();
|
|
}
|
|
|
|
/* An edge is selected if any connected face was selected. */
|
|
template<>
|
|
void adapt_mesh_domain_face_to_edge_impl(const Mesh &mesh,
|
|
const VArray<bool> &old_values,
|
|
MutableSpan<bool> r_values)
|
|
{
|
|
BLI_assert(r_values.size() == mesh.edges_num);
|
|
const OffsetIndices faces = mesh.faces();
|
|
const Span<int> corner_edges = mesh.corner_edges();
|
|
|
|
r_values.fill(false);
|
|
threading::parallel_for(faces.index_range(), 2048, [&](const IndexRange range) {
|
|
for (const int face_index : range) {
|
|
if (old_values[face_index]) {
|
|
for (const int edge : corner_edges.slice(faces[face_index])) {
|
|
r_values[edge] = true;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
static GVArray adapt_mesh_domain_face_to_edge(const Mesh &mesh, const GVArray &varray)
|
|
{
|
|
GArray<> values(varray.type(), mesh.edges_num);
|
|
attribute_math::convert_to_static_type(varray.type(), [&](auto dummy) {
|
|
using T = decltype(dummy);
|
|
if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) {
|
|
adapt_mesh_domain_face_to_edge_impl<T>(
|
|
mesh, varray.typed<T>(), values.as_mutable_span().typed<T>());
|
|
}
|
|
});
|
|
return GVArray::ForGArray(std::move(values));
|
|
}
|
|
|
|
static GVArray adapt_mesh_domain_point_to_face(const Mesh &mesh, const GVArray &varray)
|
|
{
|
|
const OffsetIndices faces = mesh.faces();
|
|
const Span<int> corner_verts = mesh.corner_verts();
|
|
|
|
GVArray new_varray;
|
|
attribute_math::convert_to_static_type(varray.type(), [&](auto dummy) {
|
|
using T = decltype(dummy);
|
|
if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) {
|
|
if constexpr (std::is_same_v<T, bool>) {
|
|
new_varray = VArray<T>::ForFunc(
|
|
mesh.faces_num,
|
|
[corner_verts, faces, varray = varray.typed<bool>()](const int face_index) {
|
|
/* A face is selected if all of its vertices were selected. */
|
|
for (const int vert : corner_verts.slice(faces[face_index])) {
|
|
if (!varray[vert]) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
});
|
|
}
|
|
else {
|
|
new_varray = VArray<T>::ForFunc(
|
|
mesh.faces_num,
|
|
[corner_verts, faces, varray = varray.typed<T>()](const int face_index) {
|
|
T return_value;
|
|
attribute_math::DefaultMixer<T> mixer({&return_value, 1});
|
|
for (const int vert : corner_verts.slice(faces[face_index])) {
|
|
mixer.mix_in(0, varray[vert]);
|
|
}
|
|
mixer.finalize();
|
|
return return_value;
|
|
});
|
|
}
|
|
}
|
|
});
|
|
return new_varray;
|
|
}
|
|
|
|
static GVArray adapt_mesh_domain_point_to_edge(const Mesh &mesh, const GVArray &varray)
|
|
{
|
|
const Span<int2> edges = mesh.edges();
|
|
|
|
GVArray new_varray;
|
|
attribute_math::convert_to_static_type(varray.type(), [&](auto dummy) {
|
|
using T = decltype(dummy);
|
|
if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) {
|
|
if constexpr (std::is_same_v<T, bool>) {
|
|
/* An edge is selected if both of its vertices were selected. */
|
|
new_varray = VArray<bool>::ForFunc(
|
|
edges.size(), [edges, varray = varray.typed<bool>()](const int edge_index) {
|
|
const int2 &edge = edges[edge_index];
|
|
return varray[edge[0]] && varray[edge[1]];
|
|
});
|
|
}
|
|
else {
|
|
new_varray = VArray<T>::ForFunc(
|
|
edges.size(), [edges, varray = varray.typed<T>()](const int edge_index) {
|
|
const int2 &edge = edges[edge_index];
|
|
return attribute_math::mix2(0.5f, varray[edge[0]], varray[edge[1]]);
|
|
});
|
|
}
|
|
}
|
|
});
|
|
return new_varray;
|
|
}
|
|
|
|
template<typename T>
|
|
void adapt_mesh_domain_edge_to_corner_impl(const Mesh &mesh,
|
|
const VArray<T> &old_values,
|
|
MutableSpan<T> r_values)
|
|
{
|
|
BLI_assert(r_values.size() == mesh.corners_num);
|
|
const OffsetIndices faces = mesh.faces();
|
|
const Span<int> corner_edges = mesh.corner_edges();
|
|
|
|
attribute_math::DefaultMixer<T> mixer(r_values);
|
|
|
|
for (const int face_index : faces.index_range()) {
|
|
const IndexRange face = faces[face_index];
|
|
|
|
/* For every corner, mix the values from the adjacent edges on the face. */
|
|
for (const int loop_index : face) {
|
|
const int loop_index_prev = mesh::face_corner_prev(face, loop_index);
|
|
const int edge = corner_edges[loop_index];
|
|
const int edge_prev = corner_edges[loop_index_prev];
|
|
mixer.mix_in(loop_index, old_values[edge]);
|
|
mixer.mix_in(loop_index, old_values[edge_prev]);
|
|
}
|
|
}
|
|
|
|
mixer.finalize();
|
|
}
|
|
|
|
/* A corner is selected if its two adjacent edges were selected. */
|
|
template<>
|
|
void adapt_mesh_domain_edge_to_corner_impl(const Mesh &mesh,
|
|
const VArray<bool> &old_values,
|
|
MutableSpan<bool> r_values)
|
|
{
|
|
BLI_assert(r_values.size() == mesh.corners_num);
|
|
const OffsetIndices faces = mesh.faces();
|
|
const Span<int> corner_edges = mesh.corner_edges();
|
|
|
|
r_values.fill(false);
|
|
|
|
threading::parallel_for(faces.index_range(), 2048, [&](const IndexRange range) {
|
|
for (const int face_index : range) {
|
|
const IndexRange face = faces[face_index];
|
|
for (const int loop_index : face) {
|
|
const int loop_index_prev = mesh::face_corner_prev(face, loop_index);
|
|
const int edge = corner_edges[loop_index];
|
|
const int edge_prev = corner_edges[loop_index_prev];
|
|
if (old_values[edge] && old_values[edge_prev]) {
|
|
r_values[loop_index] = true;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
static GVArray adapt_mesh_domain_edge_to_corner(const Mesh &mesh, const GVArray &varray)
|
|
{
|
|
GArray<> values(varray.type(), mesh.corners_num);
|
|
attribute_math::convert_to_static_type(varray.type(), [&](auto dummy) {
|
|
using T = decltype(dummy);
|
|
if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) {
|
|
adapt_mesh_domain_edge_to_corner_impl<T>(
|
|
mesh, varray.typed<T>(), values.as_mutable_span().typed<T>());
|
|
}
|
|
});
|
|
return GVArray::ForGArray(std::move(values));
|
|
}
|
|
|
|
template<typename T>
|
|
static void adapt_mesh_domain_edge_to_point_impl(const Mesh &mesh,
|
|
const VArray<T> &old_values,
|
|
MutableSpan<T> r_values)
|
|
{
|
|
BLI_assert(r_values.size() == mesh.verts_num);
|
|
const Span<int2> edges = mesh.edges();
|
|
|
|
attribute_math::DefaultMixer<T> mixer(r_values);
|
|
|
|
for (const int edge_index : IndexRange(mesh.edges_num)) {
|
|
const int2 &edge = edges[edge_index];
|
|
const T value = old_values[edge_index];
|
|
mixer.mix_in(edge[0], value);
|
|
mixer.mix_in(edge[1], value);
|
|
}
|
|
|
|
mixer.finalize();
|
|
}
|
|
|
|
/* A vertex is selected if any connected edge was selected. */
|
|
template<>
|
|
void adapt_mesh_domain_edge_to_point_impl(const Mesh &mesh,
|
|
const VArray<bool> &old_values,
|
|
MutableSpan<bool> r_values)
|
|
{
|
|
BLI_assert(r_values.size() == mesh.verts_num);
|
|
const Span<int2> edges = mesh.edges();
|
|
|
|
/* Multiple threads can write to the same index here, but they are only
|
|
* writing true, and writing to single bytes is expected to be threadsafe. */
|
|
r_values.fill(false);
|
|
threading::parallel_for(edges.index_range(), 4096, [&](const IndexRange range) {
|
|
for (const int edge_index : range) {
|
|
if (old_values[edge_index]) {
|
|
const int2 &edge = edges[edge_index];
|
|
r_values[edge[0]] = true;
|
|
r_values[edge[1]] = true;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
static GVArray adapt_mesh_domain_edge_to_point(const Mesh &mesh, const GVArray &varray)
|
|
{
|
|
GArray<> values(varray.type(), mesh.verts_num);
|
|
attribute_math::convert_to_static_type(varray.type(), [&](auto dummy) {
|
|
using T = decltype(dummy);
|
|
if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) {
|
|
adapt_mesh_domain_edge_to_point_impl<T>(
|
|
mesh, varray.typed<T>(), values.as_mutable_span().typed<T>());
|
|
}
|
|
});
|
|
return GVArray::ForGArray(std::move(values));
|
|
}
|
|
|
|
static GVArray adapt_mesh_domain_edge_to_face(const Mesh &mesh, const GVArray &varray)
|
|
{
|
|
const OffsetIndices faces = mesh.faces();
|
|
const Span<int> corner_edges = mesh.corner_edges();
|
|
|
|
GVArray new_varray;
|
|
attribute_math::convert_to_static_type(varray.type(), [&](auto dummy) {
|
|
using T = decltype(dummy);
|
|
if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) {
|
|
if constexpr (std::is_same_v<T, bool>) {
|
|
/* A face is selected if all of its edges are selected. */
|
|
new_varray = VArray<bool>::ForFunc(
|
|
faces.size(), [corner_edges, faces, varray = varray.typed<T>()](const int face_index) {
|
|
for (const int edge : corner_edges.slice(faces[face_index])) {
|
|
if (!varray[edge]) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
});
|
|
}
|
|
else {
|
|
new_varray = VArray<T>::ForFunc(
|
|
faces.size(), [corner_edges, faces, varray = varray.typed<T>()](const int face_index) {
|
|
T return_value;
|
|
attribute_math::DefaultMixer<T> mixer({&return_value, 1});
|
|
for (const int edge : corner_edges.slice(faces[face_index])) {
|
|
mixer.mix_in(0, varray[edge]);
|
|
}
|
|
mixer.finalize();
|
|
return return_value;
|
|
});
|
|
}
|
|
}
|
|
});
|
|
return new_varray;
|
|
}
|
|
|
|
static bool can_simple_adapt_for_single(const Mesh &mesh,
|
|
const AttrDomain from_domain,
|
|
const AttrDomain to_domain)
|
|
{
|
|
/* For some domain combinations, a single value will always map directly. For others, there may
|
|
* be loose elements on the result domain that should have the default value rather than the
|
|
* single value from the source. */
|
|
switch (from_domain) {
|
|
case AttrDomain::Point:
|
|
/* All other domains are always connected to points. */
|
|
return true;
|
|
case AttrDomain::Edge:
|
|
if (to_domain == AttrDomain::Point) {
|
|
return mesh.loose_verts().count == 0;
|
|
}
|
|
return true;
|
|
case AttrDomain::Face:
|
|
if (to_domain == AttrDomain::Point) {
|
|
return mesh.verts_no_face().count == 0;
|
|
}
|
|
if (to_domain == AttrDomain::Edge) {
|
|
return mesh.loose_edges().count == 0;
|
|
}
|
|
return true;
|
|
case AttrDomain::Corner:
|
|
if (to_domain == AttrDomain::Point) {
|
|
return mesh.verts_no_face().count == 0;
|
|
}
|
|
if (to_domain == AttrDomain::Edge) {
|
|
return mesh.loose_edges().count == 0;
|
|
}
|
|
return true;
|
|
default:
|
|
BLI_assert_unreachable();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static GVArray adapt_mesh_attribute_domain(const Mesh &mesh,
|
|
const GVArray &varray,
|
|
const AttrDomain from_domain,
|
|
const AttrDomain to_domain)
|
|
{
|
|
if (!varray) {
|
|
return {};
|
|
}
|
|
if (varray.is_empty()) {
|
|
return {};
|
|
}
|
|
if (from_domain == to_domain) {
|
|
return varray;
|
|
}
|
|
if (varray.is_single()) {
|
|
if (can_simple_adapt_for_single(mesh, from_domain, to_domain)) {
|
|
BUFFER_FOR_CPP_TYPE_VALUE(varray.type(), value);
|
|
varray.get_internal_single(value);
|
|
return GVArray::ForSingle(varray.type(), mesh.attributes().domain_size(to_domain), value);
|
|
}
|
|
}
|
|
|
|
switch (from_domain) {
|
|
case AttrDomain::Corner: {
|
|
switch (to_domain) {
|
|
case AttrDomain::Point:
|
|
return adapt_mesh_domain_corner_to_point(mesh, varray);
|
|
case AttrDomain::Face:
|
|
return adapt_mesh_domain_corner_to_face(mesh, varray);
|
|
case AttrDomain::Edge:
|
|
return adapt_mesh_domain_corner_to_edge(mesh, varray);
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
case AttrDomain::Point: {
|
|
switch (to_domain) {
|
|
case AttrDomain::Corner:
|
|
return adapt_mesh_domain_point_to_corner(mesh, varray);
|
|
case AttrDomain::Face:
|
|
return adapt_mesh_domain_point_to_face(mesh, varray);
|
|
case AttrDomain::Edge:
|
|
return adapt_mesh_domain_point_to_edge(mesh, varray);
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
case AttrDomain::Face: {
|
|
switch (to_domain) {
|
|
case AttrDomain::Point:
|
|
return adapt_mesh_domain_face_to_point(mesh, varray);
|
|
case AttrDomain::Corner:
|
|
return adapt_mesh_domain_face_to_corner(mesh, varray);
|
|
case AttrDomain::Edge:
|
|
return adapt_mesh_domain_face_to_edge(mesh, varray);
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
case AttrDomain::Edge: {
|
|
switch (to_domain) {
|
|
case AttrDomain::Corner:
|
|
return adapt_mesh_domain_edge_to_corner(mesh, varray);
|
|
case AttrDomain::Point:
|
|
return adapt_mesh_domain_edge_to_point(mesh, varray);
|
|
case AttrDomain::Face:
|
|
return adapt_mesh_domain_edge_to_face(mesh, varray);
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
static void tag_component_positions_changed(void *owner)
|
|
{
|
|
Mesh *mesh = static_cast<Mesh *>(owner);
|
|
if (mesh != nullptr) {
|
|
mesh->tag_positions_changed();
|
|
}
|
|
}
|
|
|
|
static void tag_component_sharpness_changed(void *owner)
|
|
{
|
|
if (Mesh *mesh = static_cast<Mesh *>(owner)) {
|
|
mesh->tag_sharpness_changed();
|
|
}
|
|
}
|
|
|
|
static void tag_material_index_changed(void *owner)
|
|
{
|
|
if (Mesh *mesh = static_cast<Mesh *>(owner)) {
|
|
mesh->tag_material_index_changed();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This provider makes vertex groups available as float attributes.
|
|
*/
|
|
class MeshVertexGroupsAttributeProvider final : public DynamicAttributesProvider {
|
|
public:
|
|
GAttributeReader try_get_for_read(const void *owner, const StringRef attribute_id) const final
|
|
{
|
|
const Mesh *mesh = static_cast<const Mesh *>(owner);
|
|
if (mesh == nullptr) {
|
|
return {};
|
|
}
|
|
const int vertex_group_index = BKE_defgroup_name_index(&mesh->vertex_group_names,
|
|
attribute_id);
|
|
if (vertex_group_index < 0) {
|
|
return {};
|
|
}
|
|
const Span<MDeformVert> dverts = mesh->deform_verts();
|
|
return this->get_for_vertex_group_index(*mesh, dverts, vertex_group_index);
|
|
}
|
|
|
|
GAttributeReader get_for_vertex_group_index(const Mesh &mesh,
|
|
const Span<MDeformVert> dverts,
|
|
const int vertex_group_index) const
|
|
{
|
|
BLI_assert(vertex_group_index >= 0);
|
|
if (dverts.is_empty()) {
|
|
return {VArray<float>::ForSingle(0.0f, mesh.verts_num), AttrDomain::Point};
|
|
}
|
|
return {varray_for_deform_verts(dverts, vertex_group_index), AttrDomain::Point};
|
|
}
|
|
|
|
GAttributeWriter try_get_for_write(void *owner, const StringRef attribute_id) const final
|
|
{
|
|
Mesh *mesh = static_cast<Mesh *>(owner);
|
|
if (mesh == nullptr) {
|
|
return {};
|
|
}
|
|
|
|
const int vertex_group_index = BKE_defgroup_name_index(&mesh->vertex_group_names,
|
|
attribute_id);
|
|
if (vertex_group_index < 0) {
|
|
return {};
|
|
}
|
|
MutableSpan<MDeformVert> dverts = mesh->deform_verts_for_write();
|
|
return {varray_for_mutable_deform_verts(dverts, vertex_group_index), AttrDomain::Point};
|
|
}
|
|
|
|
bool try_delete(void *owner, const StringRef name) const final
|
|
{
|
|
Mesh *mesh = static_cast<Mesh *>(owner);
|
|
if (mesh == nullptr) {
|
|
return true;
|
|
}
|
|
|
|
int index;
|
|
bDeformGroup *group;
|
|
if (!BKE_id_defgroup_name_find(&mesh->id, name, &index, &group)) {
|
|
return false;
|
|
}
|
|
BLI_remlink(&mesh->vertex_group_names, group);
|
|
MEM_freeN(group);
|
|
if (mesh->deform_verts().is_empty()) {
|
|
return true;
|
|
}
|
|
|
|
MutableSpan<MDeformVert> dverts = mesh->deform_verts_for_write();
|
|
remove_defgroup_index(dverts, index);
|
|
return true;
|
|
}
|
|
|
|
bool foreach_attribute(const void *owner,
|
|
const FunctionRef<void(const AttributeIter &)> fn) const final
|
|
{
|
|
const Mesh *mesh = static_cast<const Mesh *>(owner);
|
|
if (mesh == nullptr) {
|
|
return true;
|
|
}
|
|
|
|
const Span<MDeformVert> dverts = mesh->deform_verts();
|
|
|
|
int group_index = 0;
|
|
LISTBASE_FOREACH_INDEX (const bDeformGroup *, group, &mesh->vertex_group_names, group_index) {
|
|
const auto get_fn = [&]() {
|
|
return this->get_for_vertex_group_index(*mesh, dverts, group_index);
|
|
};
|
|
|
|
AttributeIter iter{group->name, AttrDomain::Point, CD_PROP_FLOAT, get_fn};
|
|
fn(iter);
|
|
if (iter.is_stopped()) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void foreach_domain(const FunctionRef<void(AttrDomain)> callback) const final
|
|
{
|
|
callback(AttrDomain::Point);
|
|
}
|
|
};
|
|
|
|
static std::function<void()> get_tag_modified_function(void *owner, const StringRef name)
|
|
{
|
|
if (name.startswith(".hide")) {
|
|
return [owner]() { (static_cast<Mesh *>(owner))->tag_visibility_changed(); };
|
|
}
|
|
if (name == "custom_normal") {
|
|
return [owner]() { (static_cast<Mesh *>(owner))->tag_custom_normals_changed(); };
|
|
}
|
|
return {};
|
|
}
|
|
|
|
/**
|
|
* In this function all the attribute providers for a mesh component are created. Most data in this
|
|
* function is statically allocated, because it does not change over time.
|
|
*/
|
|
static GeometryAttributeProviders create_attribute_providers_for_mesh()
|
|
{
|
|
#define MAKE_MUTABLE_CUSTOM_DATA_GETTER(NAME) \
|
|
[](void *owner) -> CustomData * { \
|
|
Mesh *mesh = static_cast<Mesh *>(owner); \
|
|
return &mesh->NAME; \
|
|
}
|
|
#define MAKE_CONST_CUSTOM_DATA_GETTER(NAME) \
|
|
[](const void *owner) -> const CustomData * { \
|
|
const Mesh *mesh = static_cast<const Mesh *>(owner); \
|
|
return &mesh->NAME; \
|
|
}
|
|
#define MAKE_GET_ELEMENT_NUM_GETTER(NAME) \
|
|
[](const void *owner) -> int { \
|
|
const Mesh *mesh = static_cast<const Mesh *>(owner); \
|
|
return mesh->NAME; \
|
|
}
|
|
|
|
static CustomDataAccessInfo corner_access = {MAKE_MUTABLE_CUSTOM_DATA_GETTER(corner_data),
|
|
MAKE_CONST_CUSTOM_DATA_GETTER(corner_data),
|
|
MAKE_GET_ELEMENT_NUM_GETTER(corners_num),
|
|
get_tag_modified_function};
|
|
static CustomDataAccessInfo point_access = {MAKE_MUTABLE_CUSTOM_DATA_GETTER(vert_data),
|
|
MAKE_CONST_CUSTOM_DATA_GETTER(vert_data),
|
|
MAKE_GET_ELEMENT_NUM_GETTER(verts_num),
|
|
get_tag_modified_function};
|
|
static CustomDataAccessInfo edge_access = {MAKE_MUTABLE_CUSTOM_DATA_GETTER(edge_data),
|
|
MAKE_CONST_CUSTOM_DATA_GETTER(edge_data),
|
|
MAKE_GET_ELEMENT_NUM_GETTER(edges_num),
|
|
get_tag_modified_function};
|
|
static CustomDataAccessInfo face_access = {MAKE_MUTABLE_CUSTOM_DATA_GETTER(face_data),
|
|
MAKE_CONST_CUSTOM_DATA_GETTER(face_data),
|
|
MAKE_GET_ELEMENT_NUM_GETTER(faces_num),
|
|
get_tag_modified_function};
|
|
|
|
#undef MAKE_CONST_CUSTOM_DATA_GETTER
|
|
#undef MAKE_MUTABLE_CUSTOM_DATA_GETTER
|
|
|
|
static BuiltinCustomDataLayerProvider position("position",
|
|
AttrDomain::Point,
|
|
CD_PROP_FLOAT3,
|
|
BuiltinAttributeProvider::NonDeletable,
|
|
point_access,
|
|
tag_component_positions_changed);
|
|
|
|
static BuiltinCustomDataLayerProvider id("id",
|
|
AttrDomain::Point,
|
|
CD_PROP_INT32,
|
|
BuiltinAttributeProvider::Deletable,
|
|
point_access,
|
|
nullptr);
|
|
|
|
static const auto material_index_clamp = mf::build::SI1_SO<int, int>(
|
|
"Material Index Validate",
|
|
[](int value) {
|
|
/* Use #short for the maximum since many areas still use that type for indices. */
|
|
return std::clamp<int>(value, 0, std::numeric_limits<short>::max());
|
|
},
|
|
mf::build::exec_presets::AllSpanOrSingle());
|
|
static BuiltinCustomDataLayerProvider material_index("material_index",
|
|
AttrDomain::Face,
|
|
CD_PROP_INT32,
|
|
BuiltinAttributeProvider::Deletable,
|
|
face_access,
|
|
tag_material_index_changed,
|
|
AttributeValidator{&material_index_clamp});
|
|
|
|
static const auto int2_index_clamp = mf::build::SI1_SO<int2, int2>(
|
|
"Index Validate",
|
|
[](int2 value) { return math::max(value, int2(0)); },
|
|
mf::build::exec_presets::AllSpanOrSingle());
|
|
static BuiltinCustomDataLayerProvider edge_verts(".edge_verts",
|
|
AttrDomain::Edge,
|
|
CD_PROP_INT32_2D,
|
|
BuiltinAttributeProvider::NonDeletable,
|
|
edge_access,
|
|
nullptr,
|
|
AttributeValidator{&int2_index_clamp});
|
|
|
|
/* NOTE: This clamping is more of a last resort, since it's quite easy to make an
|
|
* invalid mesh that will crash Blender by arbitrarily editing this attribute. */
|
|
static const auto int_index_clamp = mf::build::SI1_SO<int, int>(
|
|
"Index Validate",
|
|
[](int value) { return std::max(value, 0); },
|
|
mf::build::exec_presets::AllSpanOrSingle());
|
|
static BuiltinCustomDataLayerProvider corner_vert(".corner_vert",
|
|
AttrDomain::Corner,
|
|
CD_PROP_INT32,
|
|
BuiltinAttributeProvider::NonDeletable,
|
|
corner_access,
|
|
nullptr,
|
|
AttributeValidator{&int_index_clamp});
|
|
static BuiltinCustomDataLayerProvider corner_edge(".corner_edge",
|
|
AttrDomain::Corner,
|
|
CD_PROP_INT32,
|
|
BuiltinAttributeProvider::NonDeletable,
|
|
corner_access,
|
|
nullptr,
|
|
AttributeValidator{&int_index_clamp});
|
|
|
|
static BuiltinCustomDataLayerProvider sharp_face("sharp_face",
|
|
AttrDomain::Face,
|
|
CD_PROP_BOOL,
|
|
BuiltinAttributeProvider::Deletable,
|
|
face_access,
|
|
tag_component_sharpness_changed);
|
|
|
|
static BuiltinCustomDataLayerProvider sharp_edge("sharp_edge",
|
|
AttrDomain::Edge,
|
|
CD_PROP_BOOL,
|
|
BuiltinAttributeProvider::Deletable,
|
|
edge_access,
|
|
tag_component_sharpness_changed);
|
|
|
|
static MeshVertexGroupsAttributeProvider vertex_groups;
|
|
static CustomDataAttributeProvider corner_custom_data(AttrDomain::Corner, corner_access);
|
|
static CustomDataAttributeProvider point_custom_data(AttrDomain::Point, point_access);
|
|
static CustomDataAttributeProvider edge_custom_data(AttrDomain::Edge, edge_access);
|
|
static CustomDataAttributeProvider face_custom_data(AttrDomain::Face, face_access);
|
|
|
|
return GeometryAttributeProviders({&position,
|
|
&edge_verts,
|
|
&corner_vert,
|
|
&corner_edge,
|
|
&id,
|
|
&material_index,
|
|
&sharp_face,
|
|
&sharp_edge},
|
|
{&corner_custom_data,
|
|
&vertex_groups,
|
|
&point_custom_data,
|
|
&edge_custom_data,
|
|
&face_custom_data});
|
|
}
|
|
|
|
static AttributeAccessorFunctions get_mesh_accessor_functions()
|
|
{
|
|
static const GeometryAttributeProviders providers = create_attribute_providers_for_mesh();
|
|
AttributeAccessorFunctions fn =
|
|
attribute_accessor_functions::accessor_functions_for_providers<providers>();
|
|
fn.domain_size = [](const void *owner, const AttrDomain domain) {
|
|
if (owner == nullptr) {
|
|
return 0;
|
|
}
|
|
const Mesh &mesh = *static_cast<const Mesh *>(owner);
|
|
switch (domain) {
|
|
case AttrDomain::Point:
|
|
return mesh.verts_num;
|
|
case AttrDomain::Edge:
|
|
return mesh.edges_num;
|
|
case AttrDomain::Face:
|
|
return mesh.faces_num;
|
|
case AttrDomain::Corner:
|
|
return mesh.corners_num;
|
|
default:
|
|
return 0;
|
|
}
|
|
};
|
|
fn.domain_supported = [](const void * /*owner*/, const AttrDomain domain) {
|
|
return ELEM(domain, AttrDomain::Point, AttrDomain::Edge, AttrDomain::Face, AttrDomain::Corner);
|
|
};
|
|
fn.adapt_domain = [](const void *owner,
|
|
const GVArray &varray,
|
|
const AttrDomain from_domain,
|
|
const AttrDomain to_domain) -> GVArray {
|
|
if (owner == nullptr) {
|
|
return {};
|
|
}
|
|
const Mesh &mesh = *static_cast<const Mesh *>(owner);
|
|
return adapt_mesh_attribute_domain(mesh, varray, from_domain, to_domain);
|
|
};
|
|
return fn;
|
|
}
|
|
|
|
const AttributeAccessorFunctions &mesh_attribute_accessor_functions()
|
|
{
|
|
static const AttributeAccessorFunctions fn = get_mesh_accessor_functions();
|
|
return fn;
|
|
}
|
|
|
|
} // namespace blender::bke
|