Refactor: Attributes: split attribute accessors to separate files
Previously, the attribute accessor were defined in the `geometry_component_*.cc` files. This made sense back in the day, because this attribute API was only used through `GeometryComponent`. However, nowadays this attribute API is independent of `GeometryComponent`. E.g. one can use `mesh.attributes()` without ever creating a component. The refactor contains the following changes: * Move attribute accessors to separate files for each geometry type. E.g. from `geometry_component_mesh.cc` to `mesh_attributes.cc`. * Move implementations of e.g. `Mesh::attributes()` to `mesh.cc`. * Provide access to the `AttributeAccessorFunctions` without actually having a geometry. This will be useful to e.g. implement `attribute_is_builtin_on_component_type` without dummy components. Pull Request: https://projects.blender.org/blender/blender/pulls/130516
This commit is contained in:
@@ -32,6 +32,7 @@ namespace blender::bke {
|
||||
class AttributeAccessor;
|
||||
class MutableAttributeAccessor;
|
||||
enum class AttrDomain : int8_t;
|
||||
struct AttributeAccessorFunctions;
|
||||
} // namespace blender::bke
|
||||
namespace blender::bke::bake {
|
||||
struct BakeMaterialsList;
|
||||
@@ -1022,6 +1023,8 @@ inline float3 calculate_vector_handle(const float3 &point, const float3 &next_po
|
||||
|
||||
/** \} */
|
||||
|
||||
const AttributeAccessorFunctions &get_attribute_accessor_functions();
|
||||
|
||||
} // namespace curves
|
||||
|
||||
struct CurvesSurfaceTransforms {
|
||||
|
||||
@@ -31,7 +31,6 @@ namespace blender::bke {
|
||||
struct AttributeKind;
|
||||
class AttributeAccessor;
|
||||
struct AttributeMetaData;
|
||||
class GeometryAttributeProviders;
|
||||
class CurvesEditHints;
|
||||
class Instances;
|
||||
class GeometryComponent;
|
||||
|
||||
@@ -36,6 +36,7 @@ struct BakeMaterialsList;
|
||||
}
|
||||
|
||||
namespace blender::bke {
|
||||
struct AttributeAccessorFunctions;
|
||||
|
||||
namespace greasepencil {
|
||||
|
||||
@@ -882,6 +883,8 @@ inline LayerGroup &Layer::parent_group()
|
||||
|
||||
TREENODE_COMMON_METHODS_FORWARD_IMPL(LayerGroup);
|
||||
|
||||
const AttributeAccessorFunctions &get_attribute_accessor_functions();
|
||||
|
||||
} // namespace greasepencil
|
||||
|
||||
class GreasePencilRuntime {
|
||||
|
||||
@@ -45,6 +45,7 @@ class MutableAttributeAccessor;
|
||||
namespace blender::bke {
|
||||
|
||||
struct GeometrySet;
|
||||
struct AttributeAccessorFunctions;
|
||||
|
||||
/**
|
||||
* Holds a reference to conceptually unique geometry or a pointer to object/collection data
|
||||
@@ -229,6 +230,7 @@ class Instances {
|
||||
|
||||
VArray<float3> instance_position_varray(const Instances &instances);
|
||||
VMutableArray<float3> instance_position_varray_for_write(Instances &instances);
|
||||
const AttributeAccessorFunctions &instance_attribute_accessor_functions();
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name #InstanceReference Inline Methods
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
namespace blender::bke {
|
||||
|
||||
enum class AttrDomain : int8_t;
|
||||
struct AttributeAccessorFunctions;
|
||||
|
||||
namespace mesh {
|
||||
/* -------------------------------------------------------------------- */
|
||||
@@ -370,4 +371,6 @@ void mesh_data_update(Depsgraph &depsgraph,
|
||||
Object &ob,
|
||||
const CustomData_MeshMasks &dataMask);
|
||||
|
||||
const AttributeAccessorFunctions &mesh_attribute_accessor_functions();
|
||||
|
||||
} // namespace blender::bke
|
||||
|
||||
@@ -74,3 +74,8 @@ void BKE_pointcloud_batch_cache_free(PointCloud *pointcloud);
|
||||
|
||||
extern void (*BKE_pointcloud_batch_cache_dirty_tag_cb)(PointCloud *pointcloud, int mode);
|
||||
extern void (*BKE_pointcloud_batch_cache_free_cb)(PointCloud *pointcloud);
|
||||
|
||||
namespace blender::bke {
|
||||
struct AttributeAccessorFunctions;
|
||||
const AttributeAccessorFunctions &pointcloud_attribute_accessor_functions();
|
||||
} // namespace blender::bke
|
||||
|
||||
@@ -113,6 +113,7 @@ set(SRC
|
||||
intern/curve_to_mesh_convert.cc
|
||||
intern/curveprofile.cc
|
||||
intern/curves.cc
|
||||
intern/curves_attributes.cc
|
||||
intern/curves_geometry.cc
|
||||
intern/curves_utils.cc
|
||||
intern/customdata.cc
|
||||
@@ -148,6 +149,7 @@ set(SRC
|
||||
intern/gpencil_legacy.cc
|
||||
intern/gpencil_modifier_legacy.cc
|
||||
intern/grease_pencil.cc
|
||||
intern/grease_pencil_attributes.cc
|
||||
intern/grease_pencil_convert_legacy.cc
|
||||
intern/grease_pencil_vertex_groups.cc
|
||||
intern/icons.cc
|
||||
@@ -164,6 +166,7 @@ set(SRC
|
||||
intern/image_partial_update.cc
|
||||
intern/image_save.cc
|
||||
intern/instances.cc
|
||||
intern/instances_attributes.cc
|
||||
intern/ipo.cc
|
||||
intern/kelvinlet.cc
|
||||
intern/key.cc
|
||||
@@ -195,6 +198,7 @@ set(SRC
|
||||
intern/mball.cc
|
||||
intern/mball_tessellate.cc
|
||||
intern/mesh.cc
|
||||
intern/mesh_attributes.cc
|
||||
intern/mesh_calc_edges.cc
|
||||
intern/mesh_compare.cc
|
||||
intern/mesh_convert.cc
|
||||
@@ -263,6 +267,7 @@ set(SRC
|
||||
intern/pbvh_uv_islands.cc
|
||||
intern/pointcache.cc
|
||||
intern/pointcloud.cc
|
||||
intern/pointcloud_attributes.cc
|
||||
intern/pose_backup.cc
|
||||
intern/preferences.cc
|
||||
intern/preview_image.cc
|
||||
|
||||
410
source/blender/blenkernel/intern/curves_attributes.cc
Normal file
410
source/blender/blenkernel/intern/curves_attributes.cc
Normal file
@@ -0,0 +1,410 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BKE_curves.hh"
|
||||
#include "BKE_deform.hh"
|
||||
|
||||
#include "FN_multi_function_builder.hh"
|
||||
|
||||
#include "attribute_access_intern.hh"
|
||||
|
||||
namespace blender::bke::curves {
|
||||
|
||||
static void tag_component_topology_changed(void *owner)
|
||||
{
|
||||
CurvesGeometry &curves = *static_cast<CurvesGeometry *>(owner);
|
||||
curves.tag_topology_changed();
|
||||
}
|
||||
|
||||
static void tag_component_curve_types_changed(void *owner)
|
||||
{
|
||||
CurvesGeometry &curves = *static_cast<CurvesGeometry *>(owner);
|
||||
curves.update_curve_types();
|
||||
curves.tag_topology_changed();
|
||||
}
|
||||
|
||||
static void tag_component_positions_changed(void *owner)
|
||||
{
|
||||
CurvesGeometry &curves = *static_cast<CurvesGeometry *>(owner);
|
||||
curves.tag_positions_changed();
|
||||
}
|
||||
|
||||
static void tag_component_radii_changed(void *owner)
|
||||
{
|
||||
CurvesGeometry &curves = *static_cast<CurvesGeometry *>(owner);
|
||||
curves.tag_radii_changed();
|
||||
}
|
||||
|
||||
static void tag_component_normals_changed(void *owner)
|
||||
{
|
||||
CurvesGeometry &curves = *static_cast<CurvesGeometry *>(owner);
|
||||
curves.tag_normals_changed();
|
||||
}
|
||||
|
||||
/**
|
||||
* This provider makes vertex groups available as float attributes.
|
||||
*/
|
||||
class CurvesVertexGroupsAttributeProvider final : public DynamicAttributesProvider {
|
||||
public:
|
||||
GAttributeReader try_get_for_read(const void *owner, const StringRef attribute_id) const final
|
||||
{
|
||||
if (bke::attribute_name_is_anonymous(attribute_id)) {
|
||||
return {};
|
||||
}
|
||||
const CurvesGeometry *curves = static_cast<const CurvesGeometry *>(owner);
|
||||
if (curves == nullptr) {
|
||||
return {};
|
||||
}
|
||||
const int vertex_group_index = BKE_defgroup_name_index(&curves->vertex_group_names,
|
||||
attribute_id);
|
||||
if (vertex_group_index < 0) {
|
||||
return {};
|
||||
}
|
||||
const Span<MDeformVert> dverts = curves->deform_verts();
|
||||
return this->get_for_vertex_group_index(*curves, dverts, vertex_group_index);
|
||||
}
|
||||
|
||||
GAttributeReader get_for_vertex_group_index(const CurvesGeometry &curves,
|
||||
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, curves.points_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
|
||||
{
|
||||
if (bke::attribute_name_is_anonymous(attribute_id)) {
|
||||
return {};
|
||||
}
|
||||
CurvesGeometry *curves = static_cast<CurvesGeometry *>(owner);
|
||||
if (curves == nullptr) {
|
||||
return {};
|
||||
}
|
||||
const int vertex_group_index = BKE_defgroup_name_index(&curves->vertex_group_names,
|
||||
attribute_id);
|
||||
if (vertex_group_index < 0) {
|
||||
return {};
|
||||
}
|
||||
MutableSpan<MDeformVert> dverts = curves->deform_verts_for_write();
|
||||
return {varray_for_mutable_deform_verts(dverts, vertex_group_index), AttrDomain::Point};
|
||||
}
|
||||
|
||||
bool try_delete(void *owner, const StringRef attribute_id) const final
|
||||
{
|
||||
if (bke::attribute_name_is_anonymous(attribute_id)) {
|
||||
return false;
|
||||
}
|
||||
CurvesGeometry *curves = static_cast<CurvesGeometry *>(owner);
|
||||
if (curves == nullptr) {
|
||||
return true;
|
||||
}
|
||||
const std::string name = attribute_id;
|
||||
|
||||
int index;
|
||||
bDeformGroup *group;
|
||||
if (!BKE_defgroup_listbase_name_find(
|
||||
&curves->vertex_group_names, name.c_str(), &index, &group))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
BLI_remlink(&curves->vertex_group_names, group);
|
||||
MEM_freeN(group);
|
||||
if (curves->deform_verts().is_empty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
MutableSpan<MDeformVert> dverts = curves->deform_verts_for_write();
|
||||
remove_defgroup_index(dverts, index);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool foreach_attribute(const void *owner,
|
||||
FunctionRef<void(const AttributeIter &)> fn) const final
|
||||
{
|
||||
const CurvesGeometry *curves = static_cast<const CurvesGeometry *>(owner);
|
||||
if (curves == nullptr) {
|
||||
return true;
|
||||
}
|
||||
const Span<MDeformVert> dverts = curves->deform_verts();
|
||||
|
||||
int group_index = 0;
|
||||
LISTBASE_FOREACH_INDEX (const bDeformGroup *, group, &curves->vertex_group_names, group_index)
|
||||
{
|
||||
const auto get_fn = [&]() {
|
||||
return this->get_for_vertex_group_index(*curves, 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);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* In this function all the attribute providers for a curves component are created.
|
||||
* Most data in this function is statically allocated, because it does not change over time.
|
||||
*/
|
||||
static GeometryAttributeProviders create_attribute_providers_for_curve()
|
||||
{
|
||||
static CustomDataAccessInfo curve_access = {
|
||||
[](void *owner) -> CustomData * {
|
||||
CurvesGeometry &curves = *static_cast<CurvesGeometry *>(owner);
|
||||
return &curves.curve_data;
|
||||
},
|
||||
[](const void *owner) -> const CustomData * {
|
||||
const CurvesGeometry &curves = *static_cast<const CurvesGeometry *>(owner);
|
||||
return &curves.curve_data;
|
||||
},
|
||||
[](const void *owner) -> int {
|
||||
const CurvesGeometry &curves = *static_cast<const CurvesGeometry *>(owner);
|
||||
return curves.curves_num();
|
||||
}};
|
||||
static CustomDataAccessInfo point_access = {
|
||||
[](void *owner) -> CustomData * {
|
||||
CurvesGeometry &curves = *static_cast<CurvesGeometry *>(owner);
|
||||
return &curves.point_data;
|
||||
},
|
||||
[](const void *owner) -> const CustomData * {
|
||||
const CurvesGeometry &curves = *static_cast<const CurvesGeometry *>(owner);
|
||||
return &curves.point_data;
|
||||
},
|
||||
[](const void *owner) -> int {
|
||||
const CurvesGeometry &curves = *static_cast<const CurvesGeometry *>(owner);
|
||||
return curves.points_num();
|
||||
}};
|
||||
|
||||
static BuiltinCustomDataLayerProvider position("position",
|
||||
AttrDomain::Point,
|
||||
CD_PROP_FLOAT3,
|
||||
BuiltinAttributeProvider::NonDeletable,
|
||||
point_access,
|
||||
tag_component_positions_changed);
|
||||
|
||||
static BuiltinCustomDataLayerProvider radius("radius",
|
||||
AttrDomain::Point,
|
||||
CD_PROP_FLOAT,
|
||||
BuiltinAttributeProvider::Deletable,
|
||||
point_access,
|
||||
tag_component_radii_changed);
|
||||
|
||||
static BuiltinCustomDataLayerProvider id("id",
|
||||
AttrDomain::Point,
|
||||
CD_PROP_INT32,
|
||||
BuiltinAttributeProvider::Deletable,
|
||||
point_access,
|
||||
nullptr);
|
||||
|
||||
static BuiltinCustomDataLayerProvider tilt("tilt",
|
||||
AttrDomain::Point,
|
||||
CD_PROP_FLOAT,
|
||||
BuiltinAttributeProvider::Deletable,
|
||||
point_access,
|
||||
tag_component_normals_changed);
|
||||
|
||||
static BuiltinCustomDataLayerProvider handle_right("handle_right",
|
||||
AttrDomain::Point,
|
||||
CD_PROP_FLOAT3,
|
||||
BuiltinAttributeProvider::Deletable,
|
||||
point_access,
|
||||
tag_component_positions_changed);
|
||||
|
||||
static BuiltinCustomDataLayerProvider handle_left("handle_left",
|
||||
AttrDomain::Point,
|
||||
CD_PROP_FLOAT3,
|
||||
BuiltinAttributeProvider::Deletable,
|
||||
point_access,
|
||||
tag_component_positions_changed);
|
||||
|
||||
static auto handle_type_clamp = mf::build::SI1_SO<int8_t, int8_t>(
|
||||
"Handle Type Validate",
|
||||
[](int8_t value) {
|
||||
return std::clamp<int8_t>(value, BEZIER_HANDLE_FREE, BEZIER_HANDLE_ALIGN);
|
||||
},
|
||||
mf::build::exec_presets::AllSpanOrSingle());
|
||||
static BuiltinCustomDataLayerProvider handle_type_right("handle_type_right",
|
||||
AttrDomain::Point,
|
||||
CD_PROP_INT8,
|
||||
BuiltinAttributeProvider::Deletable,
|
||||
point_access,
|
||||
tag_component_topology_changed,
|
||||
AttributeValidator{&handle_type_clamp});
|
||||
|
||||
static BuiltinCustomDataLayerProvider handle_type_left("handle_type_left",
|
||||
AttrDomain::Point,
|
||||
CD_PROP_INT8,
|
||||
BuiltinAttributeProvider::Deletable,
|
||||
point_access,
|
||||
tag_component_topology_changed,
|
||||
AttributeValidator{&handle_type_clamp});
|
||||
|
||||
static BuiltinCustomDataLayerProvider nurbs_weight("nurbs_weight",
|
||||
AttrDomain::Point,
|
||||
CD_PROP_FLOAT,
|
||||
BuiltinAttributeProvider::Deletable,
|
||||
point_access,
|
||||
tag_component_positions_changed);
|
||||
|
||||
static const auto nurbs_order_clamp = mf::build::SI1_SO<int8_t, int8_t>(
|
||||
"NURBS Order Validate",
|
||||
[](int8_t value) { return std::max<int8_t>(value, 1); },
|
||||
mf::build::exec_presets::AllSpanOrSingle());
|
||||
static int nurbs_order_default = 4;
|
||||
static BuiltinCustomDataLayerProvider nurbs_order("nurbs_order",
|
||||
AttrDomain::Curve,
|
||||
CD_PROP_INT8,
|
||||
BuiltinAttributeProvider::Deletable,
|
||||
curve_access,
|
||||
tag_component_topology_changed,
|
||||
AttributeValidator{&nurbs_order_clamp},
|
||||
&nurbs_order_default);
|
||||
|
||||
static const auto normal_mode_clamp = mf::build::SI1_SO<int8_t, int8_t>(
|
||||
"Normal Mode Validate",
|
||||
[](int8_t value) {
|
||||
return std::clamp<int8_t>(value, NORMAL_MODE_MINIMUM_TWIST, NORMAL_MODE_FREE);
|
||||
},
|
||||
mf::build::exec_presets::AllSpanOrSingle());
|
||||
static BuiltinCustomDataLayerProvider normal_mode("normal_mode",
|
||||
AttrDomain::Curve,
|
||||
CD_PROP_INT8,
|
||||
BuiltinAttributeProvider::Deletable,
|
||||
curve_access,
|
||||
tag_component_normals_changed,
|
||||
AttributeValidator{&normal_mode_clamp});
|
||||
|
||||
static BuiltinCustomDataLayerProvider custom_normal("custom_normal",
|
||||
AttrDomain::Point,
|
||||
CD_PROP_FLOAT3,
|
||||
BuiltinAttributeProvider::Deletable,
|
||||
point_access,
|
||||
tag_component_normals_changed);
|
||||
|
||||
static const auto knots_mode_clamp = mf::build::SI1_SO<int8_t, int8_t>(
|
||||
"Knots Mode Validate",
|
||||
[](int8_t value) {
|
||||
return std::clamp<int8_t>(value, NURBS_KNOT_MODE_NORMAL, NURBS_KNOT_MODE_ENDPOINT_BEZIER);
|
||||
},
|
||||
mf::build::exec_presets::AllSpanOrSingle());
|
||||
static BuiltinCustomDataLayerProvider nurbs_knots_mode("knots_mode",
|
||||
AttrDomain::Curve,
|
||||
CD_PROP_INT8,
|
||||
BuiltinAttributeProvider::Deletable,
|
||||
curve_access,
|
||||
tag_component_topology_changed,
|
||||
AttributeValidator{&knots_mode_clamp});
|
||||
|
||||
static const auto curve_type_clamp = mf::build::SI1_SO<int8_t, int8_t>(
|
||||
"Curve Type Validate",
|
||||
[](int8_t value) {
|
||||
return std::clamp<int8_t>(value, CURVE_TYPE_CATMULL_ROM, CURVE_TYPES_NUM);
|
||||
},
|
||||
mf::build::exec_presets::AllSpanOrSingle());
|
||||
static BuiltinCustomDataLayerProvider curve_type("curve_type",
|
||||
AttrDomain::Curve,
|
||||
CD_PROP_INT8,
|
||||
BuiltinAttributeProvider::Deletable,
|
||||
curve_access,
|
||||
tag_component_curve_types_changed,
|
||||
AttributeValidator{&curve_type_clamp});
|
||||
|
||||
static const auto resolution_clamp = mf::build::SI1_SO<int, int>(
|
||||
"Resolution Validate",
|
||||
[](int value) { return std::max<int>(value, 1); },
|
||||
mf::build::exec_presets::AllSpanOrSingle());
|
||||
static int resolution_default = 12;
|
||||
static BuiltinCustomDataLayerProvider resolution("resolution",
|
||||
AttrDomain::Curve,
|
||||
CD_PROP_INT32,
|
||||
BuiltinAttributeProvider::Deletable,
|
||||
curve_access,
|
||||
tag_component_topology_changed,
|
||||
AttributeValidator{&resolution_clamp},
|
||||
&resolution_default);
|
||||
|
||||
static BuiltinCustomDataLayerProvider cyclic("cyclic",
|
||||
AttrDomain::Curve,
|
||||
CD_PROP_BOOL,
|
||||
BuiltinAttributeProvider::Deletable,
|
||||
curve_access,
|
||||
tag_component_topology_changed);
|
||||
|
||||
static CurvesVertexGroupsAttributeProvider vertex_groups;
|
||||
static CustomDataAttributeProvider curve_custom_data(AttrDomain::Curve, curve_access);
|
||||
static CustomDataAttributeProvider point_custom_data(AttrDomain::Point, point_access);
|
||||
|
||||
return GeometryAttributeProviders({&position,
|
||||
&radius,
|
||||
&id,
|
||||
&tilt,
|
||||
&handle_right,
|
||||
&handle_left,
|
||||
&handle_type_right,
|
||||
&handle_type_left,
|
||||
&normal_mode,
|
||||
&custom_normal,
|
||||
&nurbs_order,
|
||||
&nurbs_knots_mode,
|
||||
&nurbs_weight,
|
||||
&curve_type,
|
||||
&resolution,
|
||||
&cyclic},
|
||||
{&vertex_groups, &curve_custom_data, &point_custom_data});
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
static AttributeAccessorFunctions get_curves_accessor_functions()
|
||||
{
|
||||
static const GeometryAttributeProviders providers = create_attribute_providers_for_curve();
|
||||
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 CurvesGeometry &curves = *static_cast<const CurvesGeometry *>(owner);
|
||||
switch (domain) {
|
||||
case AttrDomain::Point:
|
||||
return curves.points_num();
|
||||
case AttrDomain::Curve:
|
||||
return curves.curves_num();
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
fn.domain_supported = [](const void * /*owner*/, const AttrDomain domain) {
|
||||
return ELEM(domain, AttrDomain::Point, AttrDomain::Curve);
|
||||
};
|
||||
fn.adapt_domain = [](const void *owner,
|
||||
const GVArray &varray,
|
||||
const AttrDomain from_domain,
|
||||
const AttrDomain to_domain) -> GVArray {
|
||||
if (owner == nullptr) {
|
||||
return {};
|
||||
}
|
||||
const CurvesGeometry &curves = *static_cast<const CurvesGeometry *>(owner);
|
||||
return curves.adapt_domain(varray, from_domain, to_domain);
|
||||
};
|
||||
return fn;
|
||||
}
|
||||
|
||||
const AttributeAccessorFunctions &get_attribute_accessor_functions()
|
||||
{
|
||||
static const AttributeAccessorFunctions fn = get_curves_accessor_functions();
|
||||
return fn;
|
||||
}
|
||||
|
||||
} // namespace blender::bke::curves
|
||||
@@ -1569,6 +1569,16 @@ GVArray CurvesGeometry::adapt_domain(const GVArray &varray,
|
||||
return {};
|
||||
}
|
||||
|
||||
AttributeAccessor CurvesGeometry::attributes() const
|
||||
{
|
||||
return AttributeAccessor(this, curves::get_attribute_accessor_functions());
|
||||
}
|
||||
|
||||
MutableAttributeAccessor CurvesGeometry::attributes_for_write()
|
||||
{
|
||||
return MutableAttributeAccessor(this, curves::get_attribute_accessor_functions());
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
|
||||
@@ -311,432 +311,20 @@ std::optional<AttrDomain> CurveLengthFieldInput::preferred_domain(
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Attribute Access Helper Functions
|
||||
/** \name Attribute Access
|
||||
* \{ */
|
||||
|
||||
static void tag_component_topology_changed(void *owner)
|
||||
{
|
||||
CurvesGeometry &curves = *static_cast<CurvesGeometry *>(owner);
|
||||
curves.tag_topology_changed();
|
||||
}
|
||||
|
||||
static void tag_component_curve_types_changed(void *owner)
|
||||
{
|
||||
CurvesGeometry &curves = *static_cast<CurvesGeometry *>(owner);
|
||||
curves.update_curve_types();
|
||||
curves.tag_topology_changed();
|
||||
}
|
||||
|
||||
static void tag_component_positions_changed(void *owner)
|
||||
{
|
||||
CurvesGeometry &curves = *static_cast<CurvesGeometry *>(owner);
|
||||
curves.tag_positions_changed();
|
||||
}
|
||||
|
||||
static void tag_component_radii_changed(void *owner)
|
||||
{
|
||||
CurvesGeometry &curves = *static_cast<CurvesGeometry *>(owner);
|
||||
curves.tag_radii_changed();
|
||||
}
|
||||
|
||||
static void tag_component_normals_changed(void *owner)
|
||||
{
|
||||
CurvesGeometry &curves = *static_cast<CurvesGeometry *>(owner);
|
||||
curves.tag_normals_changed();
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Attribute Provider Declaration
|
||||
* \{ */
|
||||
|
||||
/**
|
||||
* This provider makes vertex groups available as float attributes.
|
||||
*/
|
||||
class CurvesVertexGroupsAttributeProvider final : public DynamicAttributesProvider {
|
||||
public:
|
||||
GAttributeReader try_get_for_read(const void *owner, const StringRef attribute_id) const final
|
||||
{
|
||||
if (bke::attribute_name_is_anonymous(attribute_id)) {
|
||||
return {};
|
||||
}
|
||||
const CurvesGeometry *curves = static_cast<const CurvesGeometry *>(owner);
|
||||
if (curves == nullptr) {
|
||||
return {};
|
||||
}
|
||||
const int vertex_group_index = BKE_defgroup_name_index(&curves->vertex_group_names,
|
||||
attribute_id);
|
||||
if (vertex_group_index < 0) {
|
||||
return {};
|
||||
}
|
||||
const Span<MDeformVert> dverts = curves->deform_verts();
|
||||
return this->get_for_vertex_group_index(*curves, dverts, vertex_group_index);
|
||||
}
|
||||
|
||||
GAttributeReader get_for_vertex_group_index(const CurvesGeometry &curves,
|
||||
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, curves.points_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
|
||||
{
|
||||
if (bke::attribute_name_is_anonymous(attribute_id)) {
|
||||
return {};
|
||||
}
|
||||
CurvesGeometry *curves = static_cast<CurvesGeometry *>(owner);
|
||||
if (curves == nullptr) {
|
||||
return {};
|
||||
}
|
||||
const int vertex_group_index = BKE_defgroup_name_index(&curves->vertex_group_names,
|
||||
attribute_id);
|
||||
if (vertex_group_index < 0) {
|
||||
return {};
|
||||
}
|
||||
MutableSpan<MDeformVert> dverts = curves->deform_verts_for_write();
|
||||
return {varray_for_mutable_deform_verts(dverts, vertex_group_index), AttrDomain::Point};
|
||||
}
|
||||
|
||||
bool try_delete(void *owner, const StringRef attribute_id) const final
|
||||
{
|
||||
if (bke::attribute_name_is_anonymous(attribute_id)) {
|
||||
return false;
|
||||
}
|
||||
CurvesGeometry *curves = static_cast<CurvesGeometry *>(owner);
|
||||
if (curves == nullptr) {
|
||||
return true;
|
||||
}
|
||||
const std::string name = attribute_id;
|
||||
|
||||
int index;
|
||||
bDeformGroup *group;
|
||||
if (!BKE_defgroup_listbase_name_find(
|
||||
&curves->vertex_group_names, name.c_str(), &index, &group))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
BLI_remlink(&curves->vertex_group_names, group);
|
||||
MEM_freeN(group);
|
||||
if (curves->deform_verts().is_empty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
MutableSpan<MDeformVert> dverts = curves->deform_verts_for_write();
|
||||
remove_defgroup_index(dverts, index);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool foreach_attribute(const void *owner,
|
||||
FunctionRef<void(const AttributeIter &)> fn) const final
|
||||
{
|
||||
const CurvesGeometry *curves = static_cast<const CurvesGeometry *>(owner);
|
||||
if (curves == nullptr) {
|
||||
return true;
|
||||
}
|
||||
const Span<MDeformVert> dverts = curves->deform_verts();
|
||||
|
||||
int group_index = 0;
|
||||
LISTBASE_FOREACH_INDEX (const bDeformGroup *, group, &curves->vertex_group_names, group_index)
|
||||
{
|
||||
const auto get_fn = [&]() {
|
||||
return this->get_for_vertex_group_index(*curves, 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);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* In this function all the attribute providers for a curves component are created.
|
||||
* Most data in this function is statically allocated, because it does not change over time.
|
||||
*/
|
||||
static GeometryAttributeProviders create_attribute_providers_for_curve()
|
||||
{
|
||||
static CustomDataAccessInfo curve_access = {
|
||||
[](void *owner) -> CustomData * {
|
||||
CurvesGeometry &curves = *static_cast<CurvesGeometry *>(owner);
|
||||
return &curves.curve_data;
|
||||
},
|
||||
[](const void *owner) -> const CustomData * {
|
||||
const CurvesGeometry &curves = *static_cast<const CurvesGeometry *>(owner);
|
||||
return &curves.curve_data;
|
||||
},
|
||||
[](const void *owner) -> int {
|
||||
const CurvesGeometry &curves = *static_cast<const CurvesGeometry *>(owner);
|
||||
return curves.curves_num();
|
||||
}};
|
||||
static CustomDataAccessInfo point_access = {
|
||||
[](void *owner) -> CustomData * {
|
||||
CurvesGeometry &curves = *static_cast<CurvesGeometry *>(owner);
|
||||
return &curves.point_data;
|
||||
},
|
||||
[](const void *owner) -> const CustomData * {
|
||||
const CurvesGeometry &curves = *static_cast<const CurvesGeometry *>(owner);
|
||||
return &curves.point_data;
|
||||
},
|
||||
[](const void *owner) -> int {
|
||||
const CurvesGeometry &curves = *static_cast<const CurvesGeometry *>(owner);
|
||||
return curves.points_num();
|
||||
}};
|
||||
|
||||
static BuiltinCustomDataLayerProvider position("position",
|
||||
AttrDomain::Point,
|
||||
CD_PROP_FLOAT3,
|
||||
BuiltinAttributeProvider::NonDeletable,
|
||||
point_access,
|
||||
tag_component_positions_changed);
|
||||
|
||||
static BuiltinCustomDataLayerProvider radius("radius",
|
||||
AttrDomain::Point,
|
||||
CD_PROP_FLOAT,
|
||||
BuiltinAttributeProvider::Deletable,
|
||||
point_access,
|
||||
tag_component_radii_changed);
|
||||
|
||||
static BuiltinCustomDataLayerProvider id("id",
|
||||
AttrDomain::Point,
|
||||
CD_PROP_INT32,
|
||||
BuiltinAttributeProvider::Deletable,
|
||||
point_access,
|
||||
nullptr);
|
||||
|
||||
static BuiltinCustomDataLayerProvider tilt("tilt",
|
||||
AttrDomain::Point,
|
||||
CD_PROP_FLOAT,
|
||||
BuiltinAttributeProvider::Deletable,
|
||||
point_access,
|
||||
tag_component_normals_changed);
|
||||
|
||||
static BuiltinCustomDataLayerProvider handle_right("handle_right",
|
||||
AttrDomain::Point,
|
||||
CD_PROP_FLOAT3,
|
||||
BuiltinAttributeProvider::Deletable,
|
||||
point_access,
|
||||
tag_component_positions_changed);
|
||||
|
||||
static BuiltinCustomDataLayerProvider handle_left("handle_left",
|
||||
AttrDomain::Point,
|
||||
CD_PROP_FLOAT3,
|
||||
BuiltinAttributeProvider::Deletable,
|
||||
point_access,
|
||||
tag_component_positions_changed);
|
||||
|
||||
static auto handle_type_clamp = mf::build::SI1_SO<int8_t, int8_t>(
|
||||
"Handle Type Validate",
|
||||
[](int8_t value) {
|
||||
return std::clamp<int8_t>(value, BEZIER_HANDLE_FREE, BEZIER_HANDLE_ALIGN);
|
||||
},
|
||||
mf::build::exec_presets::AllSpanOrSingle());
|
||||
static BuiltinCustomDataLayerProvider handle_type_right("handle_type_right",
|
||||
AttrDomain::Point,
|
||||
CD_PROP_INT8,
|
||||
BuiltinAttributeProvider::Deletable,
|
||||
point_access,
|
||||
tag_component_topology_changed,
|
||||
AttributeValidator{&handle_type_clamp});
|
||||
|
||||
static BuiltinCustomDataLayerProvider handle_type_left("handle_type_left",
|
||||
AttrDomain::Point,
|
||||
CD_PROP_INT8,
|
||||
BuiltinAttributeProvider::Deletable,
|
||||
point_access,
|
||||
tag_component_topology_changed,
|
||||
AttributeValidator{&handle_type_clamp});
|
||||
|
||||
static BuiltinCustomDataLayerProvider nurbs_weight("nurbs_weight",
|
||||
AttrDomain::Point,
|
||||
CD_PROP_FLOAT,
|
||||
BuiltinAttributeProvider::Deletable,
|
||||
point_access,
|
||||
tag_component_positions_changed);
|
||||
|
||||
static const auto nurbs_order_clamp = mf::build::SI1_SO<int8_t, int8_t>(
|
||||
"NURBS Order Validate",
|
||||
[](int8_t value) { return std::max<int8_t>(value, 1); },
|
||||
mf::build::exec_presets::AllSpanOrSingle());
|
||||
static int nurbs_order_default = 4;
|
||||
static BuiltinCustomDataLayerProvider nurbs_order("nurbs_order",
|
||||
AttrDomain::Curve,
|
||||
CD_PROP_INT8,
|
||||
BuiltinAttributeProvider::Deletable,
|
||||
curve_access,
|
||||
tag_component_topology_changed,
|
||||
AttributeValidator{&nurbs_order_clamp},
|
||||
&nurbs_order_default);
|
||||
|
||||
static const auto normal_mode_clamp = mf::build::SI1_SO<int8_t, int8_t>(
|
||||
"Normal Mode Validate",
|
||||
[](int8_t value) {
|
||||
return std::clamp<int8_t>(value, NORMAL_MODE_MINIMUM_TWIST, NORMAL_MODE_FREE);
|
||||
},
|
||||
mf::build::exec_presets::AllSpanOrSingle());
|
||||
static BuiltinCustomDataLayerProvider normal_mode("normal_mode",
|
||||
AttrDomain::Curve,
|
||||
CD_PROP_INT8,
|
||||
BuiltinAttributeProvider::Deletable,
|
||||
curve_access,
|
||||
tag_component_normals_changed,
|
||||
AttributeValidator{&normal_mode_clamp});
|
||||
|
||||
static BuiltinCustomDataLayerProvider custom_normal("custom_normal",
|
||||
AttrDomain::Point,
|
||||
CD_PROP_FLOAT3,
|
||||
BuiltinAttributeProvider::Deletable,
|
||||
point_access,
|
||||
tag_component_normals_changed);
|
||||
|
||||
static const auto knots_mode_clamp = mf::build::SI1_SO<int8_t, int8_t>(
|
||||
"Knots Mode Validate",
|
||||
[](int8_t value) {
|
||||
return std::clamp<int8_t>(value, NURBS_KNOT_MODE_NORMAL, NURBS_KNOT_MODE_ENDPOINT_BEZIER);
|
||||
},
|
||||
mf::build::exec_presets::AllSpanOrSingle());
|
||||
static BuiltinCustomDataLayerProvider nurbs_knots_mode("knots_mode",
|
||||
AttrDomain::Curve,
|
||||
CD_PROP_INT8,
|
||||
BuiltinAttributeProvider::Deletable,
|
||||
curve_access,
|
||||
tag_component_topology_changed,
|
||||
AttributeValidator{&knots_mode_clamp});
|
||||
|
||||
static const auto curve_type_clamp = mf::build::SI1_SO<int8_t, int8_t>(
|
||||
"Curve Type Validate",
|
||||
[](int8_t value) {
|
||||
return std::clamp<int8_t>(value, CURVE_TYPE_CATMULL_ROM, CURVE_TYPES_NUM);
|
||||
},
|
||||
mf::build::exec_presets::AllSpanOrSingle());
|
||||
static BuiltinCustomDataLayerProvider curve_type("curve_type",
|
||||
AttrDomain::Curve,
|
||||
CD_PROP_INT8,
|
||||
BuiltinAttributeProvider::Deletable,
|
||||
curve_access,
|
||||
tag_component_curve_types_changed,
|
||||
AttributeValidator{&curve_type_clamp});
|
||||
|
||||
static const auto resolution_clamp = mf::build::SI1_SO<int, int>(
|
||||
"Resolution Validate",
|
||||
[](int value) { return std::max<int>(value, 1); },
|
||||
mf::build::exec_presets::AllSpanOrSingle());
|
||||
static int resolution_default = 12;
|
||||
static BuiltinCustomDataLayerProvider resolution("resolution",
|
||||
AttrDomain::Curve,
|
||||
CD_PROP_INT32,
|
||||
BuiltinAttributeProvider::Deletable,
|
||||
curve_access,
|
||||
tag_component_topology_changed,
|
||||
AttributeValidator{&resolution_clamp},
|
||||
&resolution_default);
|
||||
|
||||
static BuiltinCustomDataLayerProvider cyclic("cyclic",
|
||||
AttrDomain::Curve,
|
||||
CD_PROP_BOOL,
|
||||
BuiltinAttributeProvider::Deletable,
|
||||
curve_access,
|
||||
tag_component_topology_changed);
|
||||
|
||||
static CurvesVertexGroupsAttributeProvider vertex_groups;
|
||||
static CustomDataAttributeProvider curve_custom_data(AttrDomain::Curve, curve_access);
|
||||
static CustomDataAttributeProvider point_custom_data(AttrDomain::Point, point_access);
|
||||
|
||||
return GeometryAttributeProviders({&position,
|
||||
&radius,
|
||||
&id,
|
||||
&tilt,
|
||||
&handle_right,
|
||||
&handle_left,
|
||||
&handle_type_right,
|
||||
&handle_type_left,
|
||||
&normal_mode,
|
||||
&custom_normal,
|
||||
&nurbs_order,
|
||||
&nurbs_knots_mode,
|
||||
&nurbs_weight,
|
||||
&curve_type,
|
||||
&resolution,
|
||||
&cyclic},
|
||||
{&vertex_groups, &curve_custom_data, &point_custom_data});
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
static AttributeAccessorFunctions get_curves_accessor_functions()
|
||||
{
|
||||
static const GeometryAttributeProviders providers = create_attribute_providers_for_curve();
|
||||
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 CurvesGeometry &curves = *static_cast<const CurvesGeometry *>(owner);
|
||||
switch (domain) {
|
||||
case AttrDomain::Point:
|
||||
return curves.points_num();
|
||||
case AttrDomain::Curve:
|
||||
return curves.curves_num();
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
fn.domain_supported = [](const void * /*owner*/, const AttrDomain domain) {
|
||||
return ELEM(domain, AttrDomain::Point, AttrDomain::Curve);
|
||||
};
|
||||
fn.adapt_domain = [](const void *owner,
|
||||
const GVArray &varray,
|
||||
const AttrDomain from_domain,
|
||||
const AttrDomain to_domain) -> GVArray {
|
||||
if (owner == nullptr) {
|
||||
return {};
|
||||
}
|
||||
const CurvesGeometry &curves = *static_cast<const CurvesGeometry *>(owner);
|
||||
return curves.adapt_domain(varray, from_domain, to_domain);
|
||||
};
|
||||
return fn;
|
||||
}
|
||||
|
||||
static const AttributeAccessorFunctions &get_curves_accessor_functions_ref()
|
||||
{
|
||||
static const AttributeAccessorFunctions fn = get_curves_accessor_functions();
|
||||
return fn;
|
||||
}
|
||||
|
||||
AttributeAccessor CurvesGeometry::attributes() const
|
||||
{
|
||||
return AttributeAccessor(this, get_curves_accessor_functions_ref());
|
||||
}
|
||||
|
||||
MutableAttributeAccessor CurvesGeometry::attributes_for_write()
|
||||
{
|
||||
return MutableAttributeAccessor(this, get_curves_accessor_functions_ref());
|
||||
}
|
||||
|
||||
std::optional<AttributeAccessor> CurveComponent::attributes() const
|
||||
{
|
||||
return AttributeAccessor(curves_ ? &curves_->geometry : nullptr,
|
||||
get_curves_accessor_functions_ref());
|
||||
curves::get_attribute_accessor_functions());
|
||||
}
|
||||
|
||||
std::optional<MutableAttributeAccessor> CurveComponent::attributes_for_write()
|
||||
{
|
||||
Curves *curves = this->get_for_write();
|
||||
return MutableAttributeAccessor(curves ? &curves->geometry : nullptr,
|
||||
get_curves_accessor_functions_ref());
|
||||
curves::get_attribute_accessor_functions());
|
||||
}
|
||||
|
||||
} // namespace blender::bke
|
||||
|
||||
@@ -101,103 +101,19 @@ void GreasePencilComponent::ensure_owns_direct_data()
|
||||
}
|
||||
}
|
||||
|
||||
static GeometryAttributeProviders create_attribute_providers_for_grease_pencil()
|
||||
{
|
||||
static CustomDataAccessInfo layers_access = {
|
||||
[](void *owner) -> CustomData * {
|
||||
GreasePencil &grease_pencil = *static_cast<GreasePencil *>(owner);
|
||||
return &grease_pencil.layers_data;
|
||||
},
|
||||
[](const void *owner) -> const CustomData * {
|
||||
const GreasePencil &grease_pencil = *static_cast<const GreasePencil *>(owner);
|
||||
return &grease_pencil.layers_data;
|
||||
},
|
||||
[](const void *owner) -> int {
|
||||
const GreasePencil &grease_pencil = *static_cast<const GreasePencil *>(owner);
|
||||
return grease_pencil.layers().size();
|
||||
}};
|
||||
|
||||
static CustomDataAttributeProvider layer_custom_data(AttrDomain::Layer, layers_access);
|
||||
|
||||
return GeometryAttributeProviders({}, {&layer_custom_data});
|
||||
}
|
||||
|
||||
static GVArray adapt_grease_pencil_attribute_domain(const GreasePencil & /*grease_pencil*/,
|
||||
const GVArray &varray,
|
||||
const AttrDomain from,
|
||||
const AttrDomain to)
|
||||
{
|
||||
if (from == to) {
|
||||
return varray;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
static AttributeAccessorFunctions get_grease_pencil_accessor_functions()
|
||||
{
|
||||
static const GeometryAttributeProviders providers =
|
||||
create_attribute_providers_for_grease_pencil();
|
||||
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 GreasePencil &grease_pencil = *static_cast<const GreasePencil *>(owner);
|
||||
switch (domain) {
|
||||
case AttrDomain::Layer:
|
||||
return int(grease_pencil.layers().size());
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
fn.domain_supported = [](const void * /*owner*/, const AttrDomain domain) {
|
||||
return domain == AttrDomain::Layer;
|
||||
};
|
||||
fn.adapt_domain = [](const void *owner,
|
||||
const GVArray &varray,
|
||||
const AttrDomain from_domain,
|
||||
const AttrDomain to_domain) -> GVArray {
|
||||
if (owner == nullptr) {
|
||||
return {};
|
||||
}
|
||||
const GreasePencil &grease_pencil = *static_cast<const GreasePencil *>(owner);
|
||||
return adapt_grease_pencil_attribute_domain(grease_pencil, varray, from_domain, to_domain);
|
||||
};
|
||||
return fn;
|
||||
}
|
||||
|
||||
static const AttributeAccessorFunctions &get_grease_pencil_accessor_functions_ref()
|
||||
{
|
||||
static const AttributeAccessorFunctions fn = get_grease_pencil_accessor_functions();
|
||||
return fn;
|
||||
}
|
||||
|
||||
} // namespace blender::bke
|
||||
|
||||
blender::bke::AttributeAccessor GreasePencil::attributes() const
|
||||
{
|
||||
return blender::bke::AttributeAccessor(this,
|
||||
blender::bke::get_grease_pencil_accessor_functions_ref());
|
||||
}
|
||||
|
||||
blender::bke::MutableAttributeAccessor GreasePencil::attributes_for_write()
|
||||
{
|
||||
return blender::bke::MutableAttributeAccessor(
|
||||
this, blender::bke::get_grease_pencil_accessor_functions_ref());
|
||||
}
|
||||
|
||||
namespace blender::bke {
|
||||
|
||||
std::optional<AttributeAccessor> GreasePencilComponent::attributes() const
|
||||
{
|
||||
return AttributeAccessor(grease_pencil_, get_grease_pencil_accessor_functions_ref());
|
||||
return AttributeAccessor(grease_pencil_, greasepencil::get_attribute_accessor_functions());
|
||||
}
|
||||
|
||||
std::optional<MutableAttributeAccessor> GreasePencilComponent::attributes_for_write()
|
||||
{
|
||||
GreasePencil *grease_pencil = this->get_for_write();
|
||||
return MutableAttributeAccessor(grease_pencil, get_grease_pencil_accessor_functions_ref());
|
||||
return MutableAttributeAccessor(grease_pencil, greasepencil::get_attribute_accessor_functions());
|
||||
}
|
||||
|
||||
} // namespace blender::bke
|
||||
|
||||
@@ -117,119 +117,14 @@ void InstancesComponent::count_memory(MemoryCounter &memory) const
|
||||
}
|
||||
}
|
||||
|
||||
static void tag_component_reference_index_changed(void *owner)
|
||||
{
|
||||
Instances &instances = *static_cast<Instances *>(owner);
|
||||
instances.tag_reference_handles_changed();
|
||||
}
|
||||
|
||||
static GeometryAttributeProviders create_attribute_providers_for_instances()
|
||||
{
|
||||
static CustomDataAccessInfo instance_custom_data_access = {
|
||||
[](void *owner) -> CustomData * {
|
||||
Instances *instances = static_cast<Instances *>(owner);
|
||||
return &instances->custom_data_attributes();
|
||||
},
|
||||
[](const void *owner) -> const CustomData * {
|
||||
const Instances *instances = static_cast<const Instances *>(owner);
|
||||
return &instances->custom_data_attributes();
|
||||
},
|
||||
[](const void *owner) -> int {
|
||||
const Instances *instances = static_cast<const Instances *>(owner);
|
||||
return instances->instances_num();
|
||||
}};
|
||||
|
||||
/**
|
||||
* IDs of the instances. They are used for consistency over multiple frames for things like
|
||||
* motion blur. Proper stable ID data that actually helps when rendering can only be generated
|
||||
* in some situations, so this vector is allowed to be empty, in which case the index of each
|
||||
* instance will be used for the final ID.
|
||||
*/
|
||||
static BuiltinCustomDataLayerProvider id("id",
|
||||
AttrDomain::Instance,
|
||||
CD_PROP_INT32,
|
||||
BuiltinAttributeProvider::Deletable,
|
||||
instance_custom_data_access,
|
||||
nullptr);
|
||||
|
||||
static BuiltinCustomDataLayerProvider instance_transform("instance_transform",
|
||||
AttrDomain::Instance,
|
||||
CD_PROP_FLOAT4X4,
|
||||
BuiltinAttributeProvider::NonDeletable,
|
||||
instance_custom_data_access,
|
||||
nullptr);
|
||||
|
||||
/** Indices into `Instances::references_`. Determines what data is instanced. */
|
||||
static BuiltinCustomDataLayerProvider reference_index(".reference_index",
|
||||
AttrDomain::Instance,
|
||||
CD_PROP_INT32,
|
||||
BuiltinAttributeProvider::NonDeletable,
|
||||
instance_custom_data_access,
|
||||
tag_component_reference_index_changed);
|
||||
|
||||
static CustomDataAttributeProvider instance_custom_data(AttrDomain::Instance,
|
||||
instance_custom_data_access);
|
||||
|
||||
return GeometryAttributeProviders({&instance_transform, &id, &reference_index},
|
||||
{&instance_custom_data});
|
||||
}
|
||||
|
||||
static AttributeAccessorFunctions get_instances_accessor_functions()
|
||||
{
|
||||
static const GeometryAttributeProviders providers = create_attribute_providers_for_instances();
|
||||
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 Instances *instances = static_cast<const Instances *>(owner);
|
||||
switch (domain) {
|
||||
case AttrDomain::Instance:
|
||||
return instances->instances_num();
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
fn.domain_supported = [](const void * /*owner*/, const AttrDomain domain) {
|
||||
return domain == AttrDomain::Instance;
|
||||
};
|
||||
fn.adapt_domain = [](const void * /*owner*/,
|
||||
const GVArray &varray,
|
||||
const AttrDomain from_domain,
|
||||
const AttrDomain to_domain) {
|
||||
if (from_domain == to_domain && from_domain == AttrDomain::Instance) {
|
||||
return varray;
|
||||
}
|
||||
return GVArray{};
|
||||
};
|
||||
return fn;
|
||||
}
|
||||
|
||||
static const AttributeAccessorFunctions &get_instances_accessor_functions_ref()
|
||||
{
|
||||
static const AttributeAccessorFunctions fn = get_instances_accessor_functions();
|
||||
return fn;
|
||||
}
|
||||
|
||||
AttributeAccessor Instances::attributes() const
|
||||
{
|
||||
return AttributeAccessor(this, get_instances_accessor_functions_ref());
|
||||
}
|
||||
|
||||
MutableAttributeAccessor Instances::attributes_for_write()
|
||||
{
|
||||
return MutableAttributeAccessor(this, get_instances_accessor_functions_ref());
|
||||
}
|
||||
|
||||
std::optional<AttributeAccessor> InstancesComponent::attributes() const
|
||||
{
|
||||
return AttributeAccessor(instances_, get_instances_accessor_functions_ref());
|
||||
return AttributeAccessor(instances_, instance_attribute_accessor_functions());
|
||||
}
|
||||
|
||||
std::optional<MutableAttributeAccessor> InstancesComponent::attributes_for_write()
|
||||
{
|
||||
return MutableAttributeAccessor(instances_, get_instances_accessor_functions_ref());
|
||||
return MutableAttributeAccessor(instances_, instance_attribute_accessor_functions());
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
@@ -171,1029 +171,23 @@ VArray<float3> mesh_normals_varray(const Mesh &mesh,
|
||||
|
||||
/** \} */
|
||||
|
||||
} // namespace blender::bke
|
||||
|
||||
namespace blender::bke {
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Attribute Access
|
||||
* \{ */
|
||||
|
||||
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) {
|
||||
T return_value;
|
||||
attribute_math::DefaultMixer<T> mixer({&return_value, 1});
|
||||
const int2 &edge = edges[edge_index];
|
||||
mixer.mix_in(0, varray[edge[0]]);
|
||||
mixer.mix_in(0, varray[edge[1]]);
|
||||
mixer.finalize();
|
||||
return return_value;
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
{
|
||||
if (bke::attribute_name_is_anonymous(attribute_id)) {
|
||||
return {};
|
||||
}
|
||||
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
|
||||
{
|
||||
if (bke::attribute_name_is_anonymous(attribute_id)) {
|
||||
return {};
|
||||
}
|
||||
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 attribute_id) const final
|
||||
{
|
||||
if (bke::attribute_name_is_anonymous(attribute_id)) {
|
||||
return false;
|
||||
}
|
||||
Mesh *mesh = static_cast<Mesh *>(owner);
|
||||
if (mesh == nullptr) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const std::string name = attribute_id;
|
||||
|
||||
int index;
|
||||
bDeformGroup *group;
|
||||
if (!BKE_id_defgroup_name_find(&mesh->id, name.c_str(), &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);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 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)};
|
||||
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)};
|
||||
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)};
|
||||
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)};
|
||||
|
||||
#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,
|
||||
nullptr,
|
||||
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;
|
||||
}
|
||||
|
||||
static const AttributeAccessorFunctions &get_mesh_accessor_functions_ref()
|
||||
{
|
||||
static const AttributeAccessorFunctions fn = get_mesh_accessor_functions();
|
||||
return fn;
|
||||
}
|
||||
|
||||
} // namespace blender::bke
|
||||
|
||||
blender::bke::AttributeAccessor Mesh::attributes() const
|
||||
{
|
||||
return blender::bke::AttributeAccessor(this, blender::bke::get_mesh_accessor_functions_ref());
|
||||
}
|
||||
|
||||
blender::bke::MutableAttributeAccessor Mesh::attributes_for_write()
|
||||
{
|
||||
return blender::bke::MutableAttributeAccessor(this,
|
||||
blender::bke::get_mesh_accessor_functions_ref());
|
||||
}
|
||||
|
||||
namespace blender::bke {
|
||||
|
||||
std::optional<AttributeAccessor> MeshComponent::attributes() const
|
||||
{
|
||||
return AttributeAccessor(mesh_, get_mesh_accessor_functions_ref());
|
||||
return AttributeAccessor(mesh_, mesh_attribute_accessor_functions());
|
||||
}
|
||||
|
||||
std::optional<MutableAttributeAccessor> MeshComponent::attributes_for_write()
|
||||
{
|
||||
Mesh *mesh = this->get_for_write();
|
||||
return MutableAttributeAccessor(mesh, get_mesh_accessor_functions_ref());
|
||||
return MutableAttributeAccessor(mesh, mesh_attribute_accessor_functions());
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
@@ -115,127 +115,23 @@ void PointCloudComponent::count_memory(MemoryCounter &memory) const
|
||||
|
||||
/** \} */
|
||||
|
||||
} // namespace blender::bke
|
||||
|
||||
namespace blender::bke {
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Attribute Access
|
||||
* \{ */
|
||||
|
||||
static void tag_component_positions_changed(void *owner)
|
||||
{
|
||||
PointCloud &points = *static_cast<PointCloud *>(owner);
|
||||
points.tag_positions_changed();
|
||||
}
|
||||
|
||||
static void tag_component_radius_changed(void *owner)
|
||||
{
|
||||
PointCloud &points = *static_cast<PointCloud *>(owner);
|
||||
points.tag_radii_changed();
|
||||
}
|
||||
|
||||
/**
|
||||
* In this function all the attribute providers for a point cloud component are created. Most data
|
||||
* in this function is statically allocated, because it does not change over time.
|
||||
*/
|
||||
static GeometryAttributeProviders create_attribute_providers_for_point_cloud()
|
||||
{
|
||||
static CustomDataAccessInfo point_access = {
|
||||
[](void *owner) -> CustomData * {
|
||||
PointCloud *pointcloud = static_cast<PointCloud *>(owner);
|
||||
return &pointcloud->pdata;
|
||||
},
|
||||
[](const void *owner) -> const CustomData * {
|
||||
const PointCloud *pointcloud = static_cast<const PointCloud *>(owner);
|
||||
return &pointcloud->pdata;
|
||||
},
|
||||
[](const void *owner) -> int {
|
||||
const PointCloud *pointcloud = static_cast<const PointCloud *>(owner);
|
||||
return pointcloud->totpoint;
|
||||
}};
|
||||
|
||||
static BuiltinCustomDataLayerProvider position("position",
|
||||
AttrDomain::Point,
|
||||
CD_PROP_FLOAT3,
|
||||
BuiltinAttributeProvider::NonDeletable,
|
||||
point_access,
|
||||
tag_component_positions_changed);
|
||||
static BuiltinCustomDataLayerProvider radius("radius",
|
||||
AttrDomain::Point,
|
||||
CD_PROP_FLOAT,
|
||||
BuiltinAttributeProvider::Deletable,
|
||||
point_access,
|
||||
tag_component_radius_changed);
|
||||
static BuiltinCustomDataLayerProvider id("id",
|
||||
AttrDomain::Point,
|
||||
CD_PROP_INT32,
|
||||
BuiltinAttributeProvider::Deletable,
|
||||
point_access,
|
||||
nullptr);
|
||||
static CustomDataAttributeProvider point_custom_data(AttrDomain::Point, point_access);
|
||||
return GeometryAttributeProviders({&position, &radius, &id}, {&point_custom_data});
|
||||
}
|
||||
|
||||
static AttributeAccessorFunctions get_pointcloud_accessor_functions()
|
||||
{
|
||||
static const GeometryAttributeProviders providers = create_attribute_providers_for_point_cloud();
|
||||
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 PointCloud &pointcloud = *static_cast<const PointCloud *>(owner);
|
||||
switch (domain) {
|
||||
case AttrDomain::Point:
|
||||
return pointcloud.totpoint;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
fn.domain_supported = [](const void * /*owner*/, const AttrDomain domain) {
|
||||
return domain == AttrDomain::Point;
|
||||
};
|
||||
fn.adapt_domain = [](const void * /*owner*/,
|
||||
const GVArray &varray,
|
||||
const AttrDomain from_domain,
|
||||
const AttrDomain to_domain) {
|
||||
if (from_domain == to_domain && from_domain == AttrDomain::Point) {
|
||||
return varray;
|
||||
}
|
||||
return GVArray{};
|
||||
};
|
||||
return fn;
|
||||
}
|
||||
|
||||
static const AttributeAccessorFunctions &get_pointcloud_accessor_functions_ref()
|
||||
{
|
||||
static const AttributeAccessorFunctions fn = get_pointcloud_accessor_functions();
|
||||
return fn;
|
||||
}
|
||||
|
||||
} // namespace blender::bke
|
||||
|
||||
blender::bke::AttributeAccessor PointCloud::attributes() const
|
||||
{
|
||||
return blender::bke::AttributeAccessor(this,
|
||||
blender::bke::get_pointcloud_accessor_functions_ref());
|
||||
}
|
||||
|
||||
blender::bke::MutableAttributeAccessor PointCloud::attributes_for_write()
|
||||
{
|
||||
return blender::bke::MutableAttributeAccessor(
|
||||
this, blender::bke::get_pointcloud_accessor_functions_ref());
|
||||
}
|
||||
|
||||
namespace blender::bke {
|
||||
|
||||
std::optional<AttributeAccessor> PointCloudComponent::attributes() const
|
||||
{
|
||||
return AttributeAccessor(pointcloud_, get_pointcloud_accessor_functions_ref());
|
||||
return AttributeAccessor(pointcloud_, pointcloud_attribute_accessor_functions());
|
||||
}
|
||||
|
||||
std::optional<MutableAttributeAccessor> PointCloudComponent::attributes_for_write()
|
||||
{
|
||||
PointCloud *pointcloud = this->get_for_write();
|
||||
return MutableAttributeAccessor(pointcloud, get_pointcloud_accessor_functions_ref());
|
||||
return MutableAttributeAccessor(pointcloud, pointcloud_attribute_accessor_functions());
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
@@ -3941,6 +3941,18 @@ void GreasePencil::print_layer_tree()
|
||||
this->root_group().print_nodes("Layer Tree:");
|
||||
}
|
||||
|
||||
blender::bke::AttributeAccessor GreasePencil::attributes() const
|
||||
{
|
||||
return blender::bke::AttributeAccessor(
|
||||
this, blender::bke::greasepencil::get_attribute_accessor_functions());
|
||||
}
|
||||
|
||||
blender::bke::MutableAttributeAccessor GreasePencil::attributes_for_write()
|
||||
{
|
||||
return blender::bke::MutableAttributeAccessor(
|
||||
this, blender::bke::greasepencil::get_attribute_accessor_functions());
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* ------------------------------------------------------------------- */
|
||||
|
||||
85
source/blender/blenkernel/intern/grease_pencil_attributes.cc
Normal file
85
source/blender/blenkernel/intern/grease_pencil_attributes.cc
Normal file
@@ -0,0 +1,85 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BKE_grease_pencil.hh"
|
||||
|
||||
#include "DNA_grease_pencil_types.h"
|
||||
|
||||
#include "attribute_access_intern.hh"
|
||||
|
||||
namespace blender ::bke::greasepencil {
|
||||
|
||||
static GeometryAttributeProviders create_attribute_providers_for_grease_pencil()
|
||||
{
|
||||
static CustomDataAccessInfo layers_access = {
|
||||
[](void *owner) -> CustomData * {
|
||||
GreasePencil &grease_pencil = *static_cast<GreasePencil *>(owner);
|
||||
return &grease_pencil.layers_data;
|
||||
},
|
||||
[](const void *owner) -> const CustomData * {
|
||||
const GreasePencil &grease_pencil = *static_cast<const GreasePencil *>(owner);
|
||||
return &grease_pencil.layers_data;
|
||||
},
|
||||
[](const void *owner) -> int {
|
||||
const GreasePencil &grease_pencil = *static_cast<const GreasePencil *>(owner);
|
||||
return grease_pencil.layers().size();
|
||||
}};
|
||||
|
||||
static CustomDataAttributeProvider layer_custom_data(AttrDomain::Layer, layers_access);
|
||||
|
||||
return GeometryAttributeProviders({}, {&layer_custom_data});
|
||||
}
|
||||
|
||||
static GVArray adapt_grease_pencil_attribute_domain(const GreasePencil & /*grease_pencil*/,
|
||||
const GVArray &varray,
|
||||
const AttrDomain from,
|
||||
const AttrDomain to)
|
||||
{
|
||||
if (from == to) {
|
||||
return varray;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
static AttributeAccessorFunctions get_grease_pencil_accessor_functions()
|
||||
{
|
||||
static const GeometryAttributeProviders providers =
|
||||
create_attribute_providers_for_grease_pencil();
|
||||
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 GreasePencil &grease_pencil = *static_cast<const GreasePencil *>(owner);
|
||||
switch (domain) {
|
||||
case AttrDomain::Layer:
|
||||
return int(grease_pencil.layers().size());
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
fn.domain_supported = [](const void * /*owner*/, const AttrDomain domain) {
|
||||
return domain == AttrDomain::Layer;
|
||||
};
|
||||
fn.adapt_domain = [](const void *owner,
|
||||
const GVArray &varray,
|
||||
const AttrDomain from_domain,
|
||||
const AttrDomain to_domain) -> GVArray {
|
||||
if (owner == nullptr) {
|
||||
return {};
|
||||
}
|
||||
const GreasePencil &grease_pencil = *static_cast<const GreasePencil *>(owner);
|
||||
return adapt_grease_pencil_attribute_domain(grease_pencil, varray, from_domain, to_domain);
|
||||
};
|
||||
return fn;
|
||||
}
|
||||
|
||||
const AttributeAccessorFunctions &get_attribute_accessor_functions()
|
||||
{
|
||||
static const AttributeAccessorFunctions fn = get_grease_pencil_accessor_functions();
|
||||
return fn;
|
||||
}
|
||||
|
||||
} // namespace blender::bke::greasepencil
|
||||
@@ -62,6 +62,16 @@ void InstanceReference::count_memory(MemoryCounter &memory) const
|
||||
}
|
||||
}
|
||||
|
||||
AttributeAccessor Instances::attributes() const
|
||||
{
|
||||
return AttributeAccessor(this, instance_attribute_accessor_functions());
|
||||
}
|
||||
|
||||
MutableAttributeAccessor Instances::attributes_for_write()
|
||||
{
|
||||
return MutableAttributeAccessor(this, instance_attribute_accessor_functions());
|
||||
}
|
||||
|
||||
static void convert_collection_to_instances(const Collection &collection,
|
||||
bke::Instances &instances)
|
||||
{
|
||||
|
||||
108
source/blender/blenkernel/intern/instances_attributes.cc
Normal file
108
source/blender/blenkernel/intern/instances_attributes.cc
Normal file
@@ -0,0 +1,108 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_math_matrix_types.hh"
|
||||
|
||||
#include "BKE_instances.hh"
|
||||
|
||||
#include "attribute_access_intern.hh"
|
||||
|
||||
namespace blender::bke {
|
||||
|
||||
static void tag_component_reference_index_changed(void *owner)
|
||||
{
|
||||
Instances &instances = *static_cast<Instances *>(owner);
|
||||
instances.tag_reference_handles_changed();
|
||||
}
|
||||
|
||||
static GeometryAttributeProviders create_attribute_providers_for_instances()
|
||||
{
|
||||
static CustomDataAccessInfo instance_custom_data_access = {
|
||||
[](void *owner) -> CustomData * {
|
||||
Instances *instances = static_cast<Instances *>(owner);
|
||||
return &instances->custom_data_attributes();
|
||||
},
|
||||
[](const void *owner) -> const CustomData * {
|
||||
const Instances *instances = static_cast<const Instances *>(owner);
|
||||
return &instances->custom_data_attributes();
|
||||
},
|
||||
[](const void *owner) -> int {
|
||||
const Instances *instances = static_cast<const Instances *>(owner);
|
||||
return instances->instances_num();
|
||||
}};
|
||||
|
||||
/**
|
||||
* IDs of the instances. They are used for consistency over multiple frames for things like
|
||||
* motion blur. Proper stable ID data that actually helps when rendering can only be generated
|
||||
* in some situations, so this vector is allowed to be empty, in which case the index of each
|
||||
* instance will be used for the final ID.
|
||||
*/
|
||||
static BuiltinCustomDataLayerProvider id("id",
|
||||
AttrDomain::Instance,
|
||||
CD_PROP_INT32,
|
||||
BuiltinAttributeProvider::Deletable,
|
||||
instance_custom_data_access,
|
||||
nullptr);
|
||||
|
||||
static BuiltinCustomDataLayerProvider instance_transform("instance_transform",
|
||||
AttrDomain::Instance,
|
||||
CD_PROP_FLOAT4X4,
|
||||
BuiltinAttributeProvider::NonDeletable,
|
||||
instance_custom_data_access,
|
||||
nullptr);
|
||||
|
||||
/** Indices into `Instances::references_`. Determines what data is instanced. */
|
||||
static BuiltinCustomDataLayerProvider reference_index(".reference_index",
|
||||
AttrDomain::Instance,
|
||||
CD_PROP_INT32,
|
||||
BuiltinAttributeProvider::NonDeletable,
|
||||
instance_custom_data_access,
|
||||
tag_component_reference_index_changed);
|
||||
|
||||
static CustomDataAttributeProvider instance_custom_data(AttrDomain::Instance,
|
||||
instance_custom_data_access);
|
||||
|
||||
return GeometryAttributeProviders({&instance_transform, &id, &reference_index},
|
||||
{&instance_custom_data});
|
||||
}
|
||||
|
||||
static AttributeAccessorFunctions get_instances_accessor_functions()
|
||||
{
|
||||
static const GeometryAttributeProviders providers = create_attribute_providers_for_instances();
|
||||
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 Instances *instances = static_cast<const Instances *>(owner);
|
||||
switch (domain) {
|
||||
case AttrDomain::Instance:
|
||||
return instances->instances_num();
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
fn.domain_supported = [](const void * /*owner*/, const AttrDomain domain) {
|
||||
return domain == AttrDomain::Instance;
|
||||
};
|
||||
fn.adapt_domain = [](const void * /*owner*/,
|
||||
const GVArray &varray,
|
||||
const AttrDomain from_domain,
|
||||
const AttrDomain to_domain) {
|
||||
if (from_domain == to_domain && from_domain == AttrDomain::Instance) {
|
||||
return varray;
|
||||
}
|
||||
return GVArray{};
|
||||
};
|
||||
return fn;
|
||||
}
|
||||
|
||||
const AttributeAccessorFunctions &instance_attribute_accessor_functions()
|
||||
{
|
||||
static const AttributeAccessorFunctions fn = get_instances_accessor_functions();
|
||||
return fn;
|
||||
}
|
||||
|
||||
} // namespace blender::bke
|
||||
@@ -701,6 +701,17 @@ void Mesh::count_memory(blender::MemoryCounter &memory) const
|
||||
CustomData_count_memory(this->corner_data, this->corners_num, memory);
|
||||
}
|
||||
|
||||
blender::bke::AttributeAccessor Mesh::attributes() const
|
||||
{
|
||||
return blender::bke::AttributeAccessor(this, blender::bke::mesh_attribute_accessor_functions());
|
||||
}
|
||||
|
||||
blender::bke::MutableAttributeAccessor Mesh::attributes_for_write()
|
||||
{
|
||||
return blender::bke::MutableAttributeAccessor(this,
|
||||
blender::bke::mesh_attribute_accessor_functions());
|
||||
}
|
||||
|
||||
Mesh *BKE_mesh_new_nomain(const int verts_num,
|
||||
const int edges_num,
|
||||
const int faces_num,
|
||||
|
||||
1015
source/blender/blenkernel/intern/mesh_attributes.cc
Normal file
1015
source/blender/blenkernel/intern/mesh_attributes.cc
Normal file
@@ -0,0 +1,1015 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#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) {
|
||||
T return_value;
|
||||
attribute_math::DefaultMixer<T> mixer({&return_value, 1});
|
||||
const int2 &edge = edges[edge_index];
|
||||
mixer.mix_in(0, varray[edge[0]]);
|
||||
mixer.mix_in(0, varray[edge[1]]);
|
||||
mixer.finalize();
|
||||
return return_value;
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
{
|
||||
if (bke::attribute_name_is_anonymous(attribute_id)) {
|
||||
return {};
|
||||
}
|
||||
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
|
||||
{
|
||||
if (bke::attribute_name_is_anonymous(attribute_id)) {
|
||||
return {};
|
||||
}
|
||||
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 attribute_id) const final
|
||||
{
|
||||
if (bke::attribute_name_is_anonymous(attribute_id)) {
|
||||
return false;
|
||||
}
|
||||
Mesh *mesh = static_cast<Mesh *>(owner);
|
||||
if (mesh == nullptr) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const std::string name = attribute_id;
|
||||
|
||||
int index;
|
||||
bDeformGroup *group;
|
||||
if (!BKE_id_defgroup_name_find(&mesh->id, name.c_str(), &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);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 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)};
|
||||
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)};
|
||||
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)};
|
||||
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)};
|
||||
|
||||
#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,
|
||||
nullptr,
|
||||
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
|
||||
@@ -284,6 +284,18 @@ void PointCloud::count_memory(blender::MemoryCounter &memory) const
|
||||
CustomData_count_memory(this->pdata, this->totpoint, memory);
|
||||
}
|
||||
|
||||
blender::bke::AttributeAccessor PointCloud::attributes() const
|
||||
{
|
||||
return blender::bke::AttributeAccessor(this,
|
||||
blender::bke::pointcloud_attribute_accessor_functions());
|
||||
}
|
||||
|
||||
blender::bke::MutableAttributeAccessor PointCloud::attributes_for_write()
|
||||
{
|
||||
return blender::bke::MutableAttributeAccessor(
|
||||
this, blender::bke::pointcloud_attribute_accessor_functions());
|
||||
}
|
||||
|
||||
bool BKE_pointcloud_attribute_required(const PointCloud * /*pointcloud*/, const char *name)
|
||||
{
|
||||
return STREQ(name, POINTCLOUD_ATTR_POSITION);
|
||||
|
||||
105
source/blender/blenkernel/intern/pointcloud_attributes.cc
Normal file
105
source/blender/blenkernel/intern/pointcloud_attributes.cc
Normal file
@@ -0,0 +1,105 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "DNA_pointcloud_types.h"
|
||||
|
||||
#include "BKE_pointcloud.hh"
|
||||
|
||||
#include "attribute_access_intern.hh"
|
||||
|
||||
namespace blender::bke {
|
||||
|
||||
static void tag_component_positions_changed(void *owner)
|
||||
{
|
||||
PointCloud &points = *static_cast<PointCloud *>(owner);
|
||||
points.tag_positions_changed();
|
||||
}
|
||||
|
||||
static void tag_component_radius_changed(void *owner)
|
||||
{
|
||||
PointCloud &points = *static_cast<PointCloud *>(owner);
|
||||
points.tag_radii_changed();
|
||||
}
|
||||
|
||||
/**
|
||||
* In this function all the attribute providers for a point cloud component are created. Most data
|
||||
* in this function is statically allocated, because it does not change over time.
|
||||
*/
|
||||
static GeometryAttributeProviders create_attribute_providers_for_point_cloud()
|
||||
{
|
||||
static CustomDataAccessInfo point_access = {
|
||||
[](void *owner) -> CustomData * {
|
||||
PointCloud *pointcloud = static_cast<PointCloud *>(owner);
|
||||
return &pointcloud->pdata;
|
||||
},
|
||||
[](const void *owner) -> const CustomData * {
|
||||
const PointCloud *pointcloud = static_cast<const PointCloud *>(owner);
|
||||
return &pointcloud->pdata;
|
||||
},
|
||||
[](const void *owner) -> int {
|
||||
const PointCloud *pointcloud = static_cast<const PointCloud *>(owner);
|
||||
return pointcloud->totpoint;
|
||||
}};
|
||||
|
||||
static BuiltinCustomDataLayerProvider position("position",
|
||||
AttrDomain::Point,
|
||||
CD_PROP_FLOAT3,
|
||||
BuiltinAttributeProvider::NonDeletable,
|
||||
point_access,
|
||||
tag_component_positions_changed);
|
||||
static BuiltinCustomDataLayerProvider radius("radius",
|
||||
AttrDomain::Point,
|
||||
CD_PROP_FLOAT,
|
||||
BuiltinAttributeProvider::Deletable,
|
||||
point_access,
|
||||
tag_component_radius_changed);
|
||||
static BuiltinCustomDataLayerProvider id("id",
|
||||
AttrDomain::Point,
|
||||
CD_PROP_INT32,
|
||||
BuiltinAttributeProvider::Deletable,
|
||||
point_access,
|
||||
nullptr);
|
||||
static CustomDataAttributeProvider point_custom_data(AttrDomain::Point, point_access);
|
||||
return GeometryAttributeProviders({&position, &radius, &id}, {&point_custom_data});
|
||||
}
|
||||
|
||||
static AttributeAccessorFunctions get_pointcloud_accessor_functions()
|
||||
{
|
||||
static const GeometryAttributeProviders providers = create_attribute_providers_for_point_cloud();
|
||||
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 PointCloud &pointcloud = *static_cast<const PointCloud *>(owner);
|
||||
switch (domain) {
|
||||
case AttrDomain::Point:
|
||||
return pointcloud.totpoint;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
fn.domain_supported = [](const void * /*owner*/, const AttrDomain domain) {
|
||||
return domain == AttrDomain::Point;
|
||||
};
|
||||
fn.adapt_domain = [](const void * /*owner*/,
|
||||
const GVArray &varray,
|
||||
const AttrDomain from_domain,
|
||||
const AttrDomain to_domain) {
|
||||
if (from_domain == to_domain && from_domain == AttrDomain::Point) {
|
||||
return varray;
|
||||
}
|
||||
return GVArray{};
|
||||
};
|
||||
return fn;
|
||||
}
|
||||
|
||||
const AttributeAccessorFunctions &pointcloud_attribute_accessor_functions()
|
||||
{
|
||||
static const AttributeAccessorFunctions fn = get_pointcloud_accessor_functions();
|
||||
return fn;
|
||||
}
|
||||
|
||||
} // namespace blender::bke
|
||||
Reference in New Issue
Block a user