Compare commits
10 Commits
feature-im
...
temp-attri
Author | SHA1 | Date | |
---|---|---|---|
3447b31a31 | |||
6e4b10d1b2 | |||
81b76f8daf | |||
29d11cad54 | |||
ed04957d76 | |||
89bf629589 | |||
1703831f62 | |||
6e8cb545b3 | |||
435e9393e9 | |||
a62c1d7700 |
@@ -498,6 +498,7 @@ geometry_node_categories = [
|
||||
NodeItem("GeometryNodeAttributeSeparateXYZ"),
|
||||
NodeItem("GeometryNodeAttributeRemove"),
|
||||
NodeItem("GeometryNodeAttributeMapRange"),
|
||||
NodeItem("GeometryNodeAttributeTransfer"),
|
||||
]),
|
||||
GeometryNodeCategory("GEO_COLOR", "Color", items=[
|
||||
NodeItem("ShaderNodeValToRGB"),
|
||||
|
@@ -21,8 +21,12 @@
|
||||
|
||||
#include "DNA_customdata_types.h"
|
||||
|
||||
#include "FN_cpp_type.hh"
|
||||
|
||||
namespace blender::attribute_math {
|
||||
|
||||
using fn::CPPType;
|
||||
|
||||
/**
|
||||
* Utility function that simplifies calling a templated function based on a custom data type.
|
||||
*/
|
||||
@@ -54,6 +58,31 @@ void convert_to_static_type(const CustomDataType data_type, const Func &func)
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Func> void convert_to_static_type(const fn::CPPType &cpp_type, const Func &func)
|
||||
{
|
||||
if (cpp_type.is<float>()) {
|
||||
func(float());
|
||||
}
|
||||
else if (cpp_type.is<float2>()) {
|
||||
func(float2());
|
||||
}
|
||||
else if (cpp_type.is<float3>()) {
|
||||
func(float3());
|
||||
}
|
||||
else if (cpp_type.is<int>()) {
|
||||
func(int());
|
||||
}
|
||||
else if (cpp_type.is<bool>()) {
|
||||
func(bool());
|
||||
}
|
||||
else if (cpp_type.is<Color4f>()) {
|
||||
func(Color4f());
|
||||
}
|
||||
else {
|
||||
BLI_assert_unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Mix three values of the same type.
|
||||
*
|
||||
|
@@ -158,6 +158,9 @@ class GeometryComponent {
|
||||
std::unique_ptr<blender::fn::GVArray> attribute_try_get_for_read(
|
||||
const blender::StringRef attribute_name, const AttributeDomain domain) const;
|
||||
|
||||
blender::bke::ReadAttributeLookup attribute_try_get_for_read(
|
||||
const blender::StringRef attribute_name, const CustomDataType data_type) const;
|
||||
|
||||
/* Get a virtual array to read the data of an attribute. If that is not possible, the returned
|
||||
* virtual array will contain a default value. This never returns null. */
|
||||
std::unique_ptr<blender::fn::GVArray> attribute_get_for_read(
|
||||
|
55
source/blender/blenkernel/BKE_mesh_sample.hh
Normal file
55
source/blender/blenkernel/BKE_mesh_sample.hh
Normal file
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
/** \file
|
||||
* \ingroup bke
|
||||
*/
|
||||
|
||||
#include "FN_generic_virtual_array.hh"
|
||||
|
||||
#include "BLI_float3.hh"
|
||||
|
||||
#include "BKE_attribute.h"
|
||||
|
||||
struct Mesh;
|
||||
|
||||
namespace blender::bke::mesh_surface_sample {
|
||||
|
||||
using fn::CPPType;
|
||||
using fn::GMutableSpan;
|
||||
using fn::GSpan;
|
||||
using fn::GVArray;
|
||||
|
||||
void sample_point_attribute(const Mesh &mesh,
|
||||
Span<int> looptri_indices,
|
||||
Span<float3> bary_coords,
|
||||
const GVArray &data_in,
|
||||
GMutableSpan data_out);
|
||||
|
||||
void sample_corner_attribute(const Mesh &mesh,
|
||||
Span<int> looptri_indices,
|
||||
Span<float3> bary_coords,
|
||||
const GVArray &data_in,
|
||||
GMutableSpan data_out);
|
||||
|
||||
void sample_face_attribute(const Mesh &mesh,
|
||||
Span<int> looptri_indices,
|
||||
const GVArray &data_in,
|
||||
GMutableSpan data_out);
|
||||
|
||||
} // namespace blender::bke::mesh_surface_sample
|
@@ -1414,6 +1414,7 @@ int ntreeTexExecTree(struct bNodeTree *ntree,
|
||||
#define GEO_NODE_ATTRIBUTE_CLAMP 1041
|
||||
#define GEO_NODE_BOUNDING_BOX 1042
|
||||
#define GEO_NODE_SWITCH 1043
|
||||
#define GEO_NODE_ATTRIBUTE_TRANSFER 1044
|
||||
|
||||
/** \} */
|
||||
|
||||
|
@@ -190,6 +190,7 @@ set(SRC
|
||||
intern/mesh_remap.c
|
||||
intern/mesh_remesh_voxel.c
|
||||
intern/mesh_runtime.c
|
||||
intern/mesh_sample.cc
|
||||
intern/mesh_tangent.c
|
||||
intern/mesh_validate.c
|
||||
intern/mesh_validate.cc
|
||||
@@ -379,6 +380,7 @@ set(SRC
|
||||
BKE_mesh_remap.h
|
||||
BKE_mesh_remesh_voxel.h
|
||||
BKE_mesh_runtime.h
|
||||
BKE_mesh_sample.hh
|
||||
BKE_mesh_tangent.h
|
||||
BKE_mesh_types.h
|
||||
BKE_mesh_wrapper.h
|
||||
|
@@ -786,6 +786,23 @@ std::unique_ptr<blender::bke::GVArray> GeometryComponent::attribute_try_get_for_
|
||||
return std::move(attribute.varray);
|
||||
}
|
||||
|
||||
blender::bke::ReadAttributeLookup GeometryComponent::attribute_try_get_for_read(
|
||||
const blender::StringRef attribute_name, const CustomDataType data_type) const
|
||||
{
|
||||
blender::bke::ReadAttributeLookup attribute = this->attribute_try_get_for_read(attribute_name);
|
||||
if (!attribute) {
|
||||
return {};
|
||||
}
|
||||
const blender::fn::CPPType *type = blender::bke::custom_data_type_to_cpp_type(data_type);
|
||||
BLI_assert(type != nullptr);
|
||||
if (attribute.varray->type() == *type) {
|
||||
return attribute;
|
||||
}
|
||||
const blender::nodes::DataTypeConversions &conversions =
|
||||
blender::nodes::get_implicit_type_conversions();
|
||||
return {conversions.try_convert(std::move(attribute.varray), *type), attribute.domain};
|
||||
}
|
||||
|
||||
std::unique_ptr<blender::bke::GVArray> GeometryComponent::attribute_get_for_read(
|
||||
const StringRef attribute_name,
|
||||
const AttributeDomain domain,
|
||||
|
158
source/blender/blenkernel/intern/mesh_sample.cc
Normal file
158
source/blender/blenkernel/intern/mesh_sample.cc
Normal file
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "BKE_attribute_math.hh"
|
||||
#include "BKE_mesh_runtime.h"
|
||||
#include "BKE_mesh_sample.hh"
|
||||
|
||||
#include "DNA_mesh_types.h"
|
||||
#include "DNA_meshdata_types.h"
|
||||
|
||||
namespace blender::bke::mesh_surface_sample {
|
||||
|
||||
static Span<MLoopTri> get_mesh_looptris(const Mesh &mesh)
|
||||
{
|
||||
/* This only updates a cache and can be considered to be logically const. */
|
||||
const MLoopTri *looptris = BKE_mesh_runtime_looptri_ensure(const_cast<Mesh *>(&mesh));
|
||||
const int looptris_len = BKE_mesh_runtime_looptri_len(&mesh);
|
||||
return {looptris, looptris_len};
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
BLI_NOINLINE static void sample_point_attribute(const Mesh &mesh,
|
||||
const Span<int> looptri_indices,
|
||||
const Span<float3> bary_coords,
|
||||
const VArray<T> &data_in,
|
||||
const MutableSpan<T> data_out)
|
||||
{
|
||||
const Span<MLoopTri> looptris = get_mesh_looptris(mesh);
|
||||
|
||||
for (const int i : bary_coords.index_range()) {
|
||||
const int looptri_index = looptri_indices[i];
|
||||
const MLoopTri &looptri = looptris[looptri_index];
|
||||
const float3 &bary_coord = bary_coords[i];
|
||||
|
||||
const int v0_index = mesh.mloop[looptri.tri[0]].v;
|
||||
const int v1_index = mesh.mloop[looptri.tri[1]].v;
|
||||
const int v2_index = mesh.mloop[looptri.tri[2]].v;
|
||||
|
||||
const T v0 = data_in[v0_index];
|
||||
const T v1 = data_in[v1_index];
|
||||
const T v2 = data_in[v2_index];
|
||||
|
||||
const T interpolated_value = attribute_math::mix3(bary_coord, v0, v1, v2);
|
||||
data_out[i] = interpolated_value;
|
||||
}
|
||||
}
|
||||
|
||||
void sample_point_attribute(const Mesh &mesh,
|
||||
const Span<int> looptri_indices,
|
||||
const Span<float3> bary_coords,
|
||||
const GVArray &data_in,
|
||||
const GMutableSpan data_out)
|
||||
{
|
||||
BLI_assert(data_out.size() == looptri_indices.size());
|
||||
BLI_assert(data_out.size() == bary_coords.size());
|
||||
BLI_assert(data_in.size() == mesh.totvert);
|
||||
BLI_assert(data_in.type() == data_out.type());
|
||||
|
||||
const CPPType &type = data_in.type();
|
||||
attribute_math::convert_to_static_type(type, [&](auto dummy) {
|
||||
using T = decltype(dummy);
|
||||
sample_point_attribute<T>(
|
||||
mesh, looptri_indices, bary_coords, data_in.typed<T>(), data_out.typed<T>());
|
||||
});
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
BLI_NOINLINE static void sample_corner_attribute(const Mesh &mesh,
|
||||
const Span<int> looptri_indices,
|
||||
const Span<float3> bary_coords,
|
||||
const VArray<T> &data_in,
|
||||
const MutableSpan<T> data_out)
|
||||
{
|
||||
Span<MLoopTri> looptris = get_mesh_looptris(mesh);
|
||||
|
||||
for (const int i : bary_coords.index_range()) {
|
||||
const int looptri_index = looptri_indices[i];
|
||||
const MLoopTri &looptri = looptris[looptri_index];
|
||||
const float3 &bary_coord = bary_coords[i];
|
||||
|
||||
const int loop_index_0 = looptri.tri[0];
|
||||
const int loop_index_1 = looptri.tri[1];
|
||||
const int loop_index_2 = looptri.tri[2];
|
||||
|
||||
const T v0 = data_in[loop_index_0];
|
||||
const T v1 = data_in[loop_index_1];
|
||||
const T v2 = data_in[loop_index_2];
|
||||
|
||||
const T interpolated_value = attribute_math::mix3(bary_coord, v0, v1, v2);
|
||||
data_out[i] = interpolated_value;
|
||||
}
|
||||
}
|
||||
|
||||
void sample_corner_attribute(const Mesh &mesh,
|
||||
const Span<int> looptri_indices,
|
||||
const Span<float3> bary_coords,
|
||||
const GVArray &data_in,
|
||||
const GMutableSpan data_out)
|
||||
{
|
||||
BLI_assert(data_out.size() == looptri_indices.size());
|
||||
BLI_assert(data_out.size() == bary_coords.size());
|
||||
BLI_assert(data_in.size() == mesh.totloop);
|
||||
BLI_assert(data_in.type() == data_out.type());
|
||||
|
||||
const CPPType &type = data_in.type();
|
||||
attribute_math::convert_to_static_type(type, [&](auto dummy) {
|
||||
using T = decltype(dummy);
|
||||
sample_corner_attribute<T>(
|
||||
mesh, looptri_indices, bary_coords, data_in.typed<T>(), data_out.typed<T>());
|
||||
});
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void sample_face_attribute(const Mesh &mesh,
|
||||
const Span<int> looptri_indices,
|
||||
const VArray<T> &data_in,
|
||||
const MutableSpan<T> data_out)
|
||||
{
|
||||
Span<MLoopTri> looptris = get_mesh_looptris(mesh);
|
||||
|
||||
for (const int i : data_out.index_range()) {
|
||||
const int looptri_index = looptri_indices[i];
|
||||
const MLoopTri &looptri = looptris[looptri_index];
|
||||
const int poly_index = looptri.poly;
|
||||
data_out[i] = data_in[poly_index];
|
||||
}
|
||||
}
|
||||
|
||||
void sample_face_attribute(const Mesh &mesh,
|
||||
const Span<int> looptri_indices,
|
||||
const GVArray &data_in,
|
||||
const GMutableSpan data_out)
|
||||
{
|
||||
BLI_assert(data_out.size() == looptri_indices.size());
|
||||
BLI_assert(data_in.size() == mesh.totpoly);
|
||||
BLI_assert(data_in.type() == data_out.type());
|
||||
|
||||
const CPPType &type = data_in.type();
|
||||
attribute_math::convert_to_static_type(type, [&](auto dummy) {
|
||||
using T = decltype(dummy);
|
||||
sample_face_attribute<T>(mesh, looptri_indices, data_in.typed<T>(), data_out.typed<T>());
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace blender::bke::mesh_surface_sample
|
@@ -4941,6 +4941,7 @@ static void registerGeometryNodes()
|
||||
register_node_type_geo_attribute_proximity();
|
||||
register_node_type_geo_attribute_randomize();
|
||||
register_node_type_geo_attribute_separate_xyz();
|
||||
register_node_type_geo_attribute_transfer();
|
||||
register_node_type_geo_attribute_vector_math();
|
||||
register_node_type_geo_attribute_remove();
|
||||
register_node_type_geo_boolean();
|
||||
|
@@ -85,6 +85,14 @@ class GSpan {
|
||||
BLI_assert(type_->is<T>());
|
||||
return Span<T>(static_cast<const T *>(data_), size_);
|
||||
}
|
||||
|
||||
GSpan slice(const int64_t start, int64_t size) const
|
||||
{
|
||||
BLI_assert(start >= 0);
|
||||
BLI_assert(size >= 0);
|
||||
const int64_t new_size = std::max<int64_t>(0, std::min(size, size_ - start));
|
||||
return GSpan(*type_, POINTER_OFFSET(data_, type_->size() * start), new_size);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -153,6 +161,14 @@ class GMutableSpan {
|
||||
BLI_assert(type_->is<T>());
|
||||
return MutableSpan<T>(static_cast<T *>(data_), size_);
|
||||
}
|
||||
|
||||
GMutableSpan slice(const int64_t start, int64_t size) const
|
||||
{
|
||||
BLI_assert(start >= 0);
|
||||
BLI_assert(size >= 0);
|
||||
const int64_t new_size = std::max<int64_t>(0, std::min(size, size_ - start));
|
||||
return GMutableSpan(*type_, POINTER_OFFSET(data_, type_->size() * start), new_size);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace blender::fn
|
||||
|
@@ -1313,6 +1313,13 @@ typedef struct NodeSwitch {
|
||||
uint8_t input_type;
|
||||
} NodeSwitch;
|
||||
|
||||
typedef struct NodeGeometryAttributeTransfer {
|
||||
/* AttributeDomain. */
|
||||
uint8_t domain;
|
||||
/* GeometryNodeAttributeTransferMappingMode. */
|
||||
uint8_t mapping;
|
||||
} NodeGeometryAttributeTransfer;
|
||||
|
||||
/* script node mode */
|
||||
#define NODE_SCRIPT_INTERNAL 0
|
||||
#define NODE_SCRIPT_EXTERNAL 1
|
||||
@@ -1807,6 +1814,12 @@ typedef enum GeometryNodeMeshLineCountMode {
|
||||
GEO_NODE_MESH_LINE_COUNT_RESOLUTION = 1,
|
||||
} GeometryNodeMeshLineCountMode;
|
||||
|
||||
typedef enum GeometryNodeAttributeTransferMappingMode {
|
||||
GEO_NODE_ATTRIBUTE_TRANSFER_MAPPING_NEAREST_INTERPOLATED = 0,
|
||||
GEO_NODE_ATTRIBUTE_TRANSFER_MAPPING_NEAREST = 1,
|
||||
GEO_NODE_ATTRIBUTE_TRANSFER_MAPPING_TOPOLOGY = 2,
|
||||
} GeometryNodeAttributeTransferMappingMode;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
@@ -9680,6 +9680,43 @@ static void def_geo_switch(StructRNA *srna)
|
||||
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update");
|
||||
}
|
||||
|
||||
static void def_geo_attribute_transfer(StructRNA *srna)
|
||||
{
|
||||
static EnumPropertyItem mapping_items[] = {
|
||||
{GEO_NODE_ATTRIBUTE_TRANSFER_MAPPING_NEAREST_INTERPOLATED,
|
||||
"NEAREST_INTERPOLATED",
|
||||
0,
|
||||
"Nearest Interpolated",
|
||||
"Transfer the value from the nearest point on the surface"},
|
||||
{GEO_NODE_ATTRIBUTE_TRANSFER_MAPPING_NEAREST,
|
||||
"NEAREST",
|
||||
0,
|
||||
"Nearest",
|
||||
"Transfer the value from the nearest element on the surface, without interpolation"},
|
||||
{GEO_NODE_ATTRIBUTE_TRANSFER_MAPPING_TOPOLOGY,
|
||||
"TOPOLOGY",
|
||||
0,
|
||||
"Topology",
|
||||
"Transfer the values from a geometry with the same topology"},
|
||||
{0, NULL, 0, NULL, NULL},
|
||||
};
|
||||
|
||||
PropertyRNA *prop;
|
||||
|
||||
RNA_def_struct_sdna_from(srna, "NodeGeometryAttributeTransfer", "storage");
|
||||
|
||||
prop = RNA_def_property(srna, "domain", PROP_ENUM, PROP_NONE);
|
||||
RNA_def_property_enum_items(prop, rna_enum_attribute_domain_with_auto_items);
|
||||
RNA_def_property_enum_default(prop, ATTR_DOMAIN_AUTO);
|
||||
RNA_def_property_ui_text(prop, "Domain", "The geometry domain to save the result attribute in");
|
||||
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update");
|
||||
|
||||
prop = RNA_def_property(srna, "mapping", PROP_ENUM, PROP_NONE);
|
||||
RNA_def_property_enum_items(prop, mapping_items);
|
||||
RNA_def_property_ui_text(prop, "Mapping", "Mapping between geometries");
|
||||
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update");
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
static void rna_def_shader_node(BlenderRNA *brna)
|
||||
|
@@ -154,6 +154,7 @@ set(SRC
|
||||
geometry/nodes/node_geo_attribute_remove.cc
|
||||
geometry/nodes/node_geo_attribute_sample_texture.cc
|
||||
geometry/nodes/node_geo_attribute_separate_xyz.cc
|
||||
geometry/nodes/node_geo_attribute_transfer.cc
|
||||
geometry/nodes/node_geo_attribute_vector_math.cc
|
||||
geometry/nodes/node_geo_boolean.cc
|
||||
geometry/nodes/node_geo_bounding_box.cc
|
||||
|
@@ -42,6 +42,7 @@ void register_node_type_geo_attribute_mix(void);
|
||||
void register_node_type_geo_attribute_proximity(void);
|
||||
void register_node_type_geo_attribute_randomize(void);
|
||||
void register_node_type_geo_attribute_separate_xyz(void);
|
||||
void register_node_type_geo_attribute_transfer(void);
|
||||
void register_node_type_geo_attribute_vector_math(void);
|
||||
void register_node_type_geo_attribute_remove(void);
|
||||
void register_node_type_geo_boolean(void);
|
||||
|
@@ -311,6 +311,7 @@ DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_MAP_RANGE, def_geo_attribute_map_range,
|
||||
DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_CLAMP, def_geo_attribute_clamp, "ATTRIBUTE_CLAMP", AttributeClamp, "Attribute Clamp", "")
|
||||
DefNode(GeometryNode, GEO_NODE_BOUNDING_BOX, 0, "BOUNDING_BOX", BoundBox, "Bounding Box", "")
|
||||
DefNode(GeometryNode, GEO_NODE_SWITCH, def_geo_switch, "SWITCH", Switch, "Switch", "")
|
||||
DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_TRANSFER, def_geo_attribute_transfer, "ATTRIBUTE_TRANSFER", AttributeTransfer, "Attribute Transfer", "")
|
||||
|
||||
/* undefine macros */
|
||||
#undef DefNode
|
||||
|
@@ -0,0 +1,403 @@
|
||||
/*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "UI_interface.h"
|
||||
#include "UI_resources.h"
|
||||
|
||||
#include "BLI_kdopbvh.h"
|
||||
|
||||
#include "BKE_bvhutils.h"
|
||||
#include "BKE_mesh_runtime.h"
|
||||
#include "BKE_mesh_sample.hh"
|
||||
|
||||
#include "DNA_mesh_types.h"
|
||||
#include "DNA_meshdata_types.h"
|
||||
#include "DNA_pointcloud_types.h"
|
||||
|
||||
#include "node_geometry_util.hh"
|
||||
|
||||
static bNodeSocketTemplate geo_node_attribute_transfer_in[] = {
|
||||
{SOCK_GEOMETRY, N_("Geometry")},
|
||||
{SOCK_GEOMETRY, N_("Target")},
|
||||
{SOCK_STRING, N_("Source")},
|
||||
{SOCK_STRING, N_("Destination")},
|
||||
{-1, ""},
|
||||
};
|
||||
|
||||
static bNodeSocketTemplate geo_node_attribute_transfer_out[] = {
|
||||
{SOCK_GEOMETRY, N_("Geometry")},
|
||||
{-1, ""},
|
||||
};
|
||||
|
||||
static void geo_node_attribute_transfer_layout(uiLayout *layout,
|
||||
bContext *UNUSED(C),
|
||||
PointerRNA *ptr)
|
||||
{
|
||||
uiLayoutSetPropSep(layout, true);
|
||||
uiLayoutSetPropDecorate(layout, false);
|
||||
uiItemR(layout, ptr, "domain", 0, IFACE_("Domain"), ICON_NONE);
|
||||
uiItemR(layout, ptr, "mapping", 0, IFACE_("Mapping"), ICON_NONE);
|
||||
}
|
||||
|
||||
namespace blender::nodes {
|
||||
|
||||
static void geo_node_attribute_transfer_init(bNodeTree *UNUSED(tree), bNode *node)
|
||||
{
|
||||
NodeGeometryAttributeTransfer *data = (NodeGeometryAttributeTransfer *)MEM_callocN(
|
||||
sizeof(NodeGeometryAttributeTransfer), __func__);
|
||||
node->storage = data;
|
||||
}
|
||||
|
||||
static CustomDataType get_result_data_type(const GeometrySet &geometry,
|
||||
const StringRef attribute_name)
|
||||
{
|
||||
Vector<CustomDataType> data_types;
|
||||
|
||||
const PointCloudComponent *pointcloud_component =
|
||||
geometry.get_component_for_read<PointCloudComponent>();
|
||||
if (pointcloud_component != nullptr) {
|
||||
ReadAttributeLookup attribute = pointcloud_component->attribute_try_get_for_read(
|
||||
attribute_name);
|
||||
if (attribute) {
|
||||
data_types.append(bke::cpp_type_to_custom_data_type(attribute.varray->type()));
|
||||
}
|
||||
}
|
||||
|
||||
const MeshComponent *mesh_component = geometry.get_component_for_read<MeshComponent>();
|
||||
if (mesh_component != nullptr) {
|
||||
ReadAttributeLookup attribute = mesh_component->attribute_try_get_for_read(attribute_name);
|
||||
if (attribute) {
|
||||
data_types.append(bke::cpp_type_to_custom_data_type(attribute.varray->type()));
|
||||
}
|
||||
}
|
||||
return bke::attribute_data_type_highest_complexity(data_types);
|
||||
}
|
||||
|
||||
static void get_closest_pointcloud_point_indices(const PointCloud &pointcloud,
|
||||
const VArray<float3> &positions,
|
||||
const MutableSpan<int> r_indices,
|
||||
const MutableSpan<float> r_distances_sq)
|
||||
{
|
||||
BLI_assert(positions.size() == r_indices.size());
|
||||
BLI_assert(pointcloud.totpoint > 0);
|
||||
|
||||
BVHTreeFromPointCloud tree_data;
|
||||
BKE_bvhtree_from_pointcloud_get(&tree_data, &pointcloud, 2);
|
||||
|
||||
for (const int i : positions.index_range()) {
|
||||
BVHTreeNearest nearest;
|
||||
nearest.dist_sq = FLT_MAX;
|
||||
const float3 position = positions[i];
|
||||
BLI_bvhtree_find_nearest(
|
||||
tree_data.tree, position, &nearest, tree_data.nearest_callback, &tree_data);
|
||||
r_indices[i] = nearest.index;
|
||||
r_distances_sq[i] = nearest.dist_sq;
|
||||
}
|
||||
|
||||
free_bvhtree_from_pointcloud(&tree_data);
|
||||
}
|
||||
|
||||
static void get_closest_mesh_point_indices(const Mesh &mesh,
|
||||
const VArray<float3> &positions,
|
||||
const MutableSpan<int> r_indices,
|
||||
const MutableSpan<float> r_distances_sq)
|
||||
{
|
||||
BLI_assert(positions.size() == r_indices.size());
|
||||
BLI_assert(mesh.totvert > 0);
|
||||
|
||||
BVHTreeFromMesh tree_data;
|
||||
BKE_bvhtree_from_mesh_get(&tree_data, const_cast<Mesh *>(&mesh), BVHTREE_FROM_VERTS, 2);
|
||||
|
||||
for (const int i : positions.index_range()) {
|
||||
BVHTreeNearest nearest;
|
||||
nearest.dist_sq = FLT_MAX;
|
||||
const float3 position = positions[i];
|
||||
BLI_bvhtree_find_nearest(
|
||||
tree_data.tree, position, &nearest, tree_data.nearest_callback, &tree_data);
|
||||
r_indices[i] = nearest.index;
|
||||
r_distances_sq[i] = nearest.dist_sq;
|
||||
}
|
||||
|
||||
free_bvhtree_from_mesh(&tree_data);
|
||||
}
|
||||
|
||||
static void get_closest_mesh_surface_samples(const Mesh &mesh,
|
||||
const VArray<float3> &positions,
|
||||
const MutableSpan<int> r_looptri_indices,
|
||||
const MutableSpan<float3> r_positions,
|
||||
const MutableSpan<float> r_distances_sq)
|
||||
{
|
||||
BLI_assert(positions.size() == r_looptri_indices.size());
|
||||
BLI_assert(positions.size() == r_positions.size());
|
||||
|
||||
BVHTreeFromMesh tree_data;
|
||||
BKE_bvhtree_from_mesh_get(&tree_data, const_cast<Mesh *>(&mesh), BVHTREE_FROM_LOOPTRI, 2);
|
||||
|
||||
for (const int i : positions.index_range()) {
|
||||
BVHTreeNearest nearest;
|
||||
nearest.dist_sq = FLT_MAX;
|
||||
const float3 position = positions[i];
|
||||
BLI_bvhtree_find_nearest(
|
||||
tree_data.tree, position, &nearest, tree_data.nearest_callback, &tree_data);
|
||||
r_looptri_indices[i] = nearest.index;
|
||||
r_positions[i] = nearest.co;
|
||||
r_distances_sq[i] = nearest.dist_sq;
|
||||
}
|
||||
|
||||
free_bvhtree_from_mesh(&tree_data);
|
||||
}
|
||||
|
||||
static Span<MLoopTri> get_mesh_looptris(const Mesh &mesh)
|
||||
{
|
||||
/* This only updates a cache and can be considered to be logically const. */
|
||||
const MLoopTri *looptris = BKE_mesh_runtime_looptri_ensure(const_cast<Mesh *>(&mesh));
|
||||
const int looptris_len = BKE_mesh_runtime_looptri_len(&mesh);
|
||||
return {looptris, looptris_len};
|
||||
}
|
||||
|
||||
static void get_barycentric_coords(const Mesh &mesh,
|
||||
const Span<int> looptri_indices,
|
||||
const Span<float3> positions,
|
||||
const MutableSpan<float3> r_bary_coords)
|
||||
{
|
||||
BLI_assert(r_bary_coords.size() == positions.size());
|
||||
BLI_assert(r_bary_coords.size() == looptri_indices.size());
|
||||
|
||||
Span<MLoopTri> looptris = get_mesh_looptris(mesh);
|
||||
|
||||
for (const int i : r_bary_coords.index_range()) {
|
||||
const int looptri_index = looptri_indices[i];
|
||||
const MLoopTri &looptri = looptris[looptri_index];
|
||||
|
||||
const int v0_index = mesh.mloop[looptri.tri[0]].v;
|
||||
const int v1_index = mesh.mloop[looptri.tri[1]].v;
|
||||
const int v2_index = mesh.mloop[looptri.tri[2]].v;
|
||||
|
||||
interp_weights_tri_v3(r_bary_coords[i],
|
||||
mesh.mvert[v0_index].co,
|
||||
mesh.mvert[v1_index].co,
|
||||
mesh.mvert[v2_index].co,
|
||||
positions[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static void transfer_attribute(const GeometrySet &src_geometry,
|
||||
GeometryComponent &dst_component,
|
||||
const AttributeDomain result_domain,
|
||||
const CustomDataType data_type,
|
||||
const StringRef src_name,
|
||||
const StringRef dst_name)
|
||||
{
|
||||
const CPPType &type = *bke::custom_data_type_to_cpp_type(data_type);
|
||||
|
||||
GVArray_Typed<float3> dst_positions = dst_component.attribute_get_for_read<float3>(
|
||||
"position", result_domain, {0, 0, 0});
|
||||
const int64_t tot_dst_positions = dst_positions.size();
|
||||
|
||||
bool use_pointcloud = false;
|
||||
Array<int> pointcloud_point_indices;
|
||||
Array<float> pointcloud_point_distances_sq;
|
||||
|
||||
bool use_mesh = false;
|
||||
Array<int> mesh_looptri_indices;
|
||||
Array<float3> mesh_point_positions;
|
||||
Array<float> mesh_point_distances_sq;
|
||||
|
||||
if (src_geometry.has<PointCloudComponent>()) {
|
||||
const PointCloudComponent &component =
|
||||
*src_geometry.get_component_for_read<PointCloudComponent>();
|
||||
const PointCloud *pointcloud = component.get_for_read();
|
||||
if (pointcloud != nullptr && pointcloud->totpoint > 0) {
|
||||
pointcloud_point_indices.reinitialize(tot_dst_positions);
|
||||
pointcloud_point_distances_sq.reinitialize(tot_dst_positions);
|
||||
get_closest_pointcloud_point_indices(
|
||||
*pointcloud, dst_positions, pointcloud_point_indices, pointcloud_point_distances_sq);
|
||||
use_pointcloud = true;
|
||||
}
|
||||
}
|
||||
if (src_geometry.has<MeshComponent>()) {
|
||||
const MeshComponent &component = *src_geometry.get_component_for_read<MeshComponent>();
|
||||
const Mesh *mesh = component.get_for_read();
|
||||
if (mesh != nullptr && mesh->totpoly > 0) {
|
||||
mesh_looptri_indices.reinitialize(tot_dst_positions);
|
||||
mesh_point_positions.reinitialize(tot_dst_positions);
|
||||
mesh_point_distances_sq.reinitialize(tot_dst_positions);
|
||||
get_closest_mesh_surface_samples(*mesh,
|
||||
dst_positions,
|
||||
mesh_looptri_indices,
|
||||
mesh_point_positions,
|
||||
mesh_point_distances_sq);
|
||||
use_mesh = true;
|
||||
}
|
||||
}
|
||||
|
||||
Vector<int> pointcloud_sample_indices;
|
||||
Vector<int> mesh_sample_indices;
|
||||
|
||||
if (use_mesh && use_pointcloud) {
|
||||
for (const int i : IndexRange(tot_dst_positions)) {
|
||||
if (pointcloud_point_distances_sq[i] < mesh_point_distances_sq[i]) {
|
||||
pointcloud_sample_indices.append(i);
|
||||
}
|
||||
else {
|
||||
mesh_sample_indices.append(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (use_mesh) {
|
||||
/* TODO: Optimize. */
|
||||
mesh_sample_indices = IndexRange(tot_dst_positions).as_span();
|
||||
}
|
||||
else if (use_pointcloud) {
|
||||
pointcloud_sample_indices = IndexRange(tot_dst_positions).as_span();
|
||||
}
|
||||
else {
|
||||
return;
|
||||
}
|
||||
|
||||
OutputAttribute dst_attribute = dst_component.attribute_try_get_for_output_only(
|
||||
dst_name, result_domain, data_type);
|
||||
if (!dst_attribute) {
|
||||
return;
|
||||
}
|
||||
|
||||
BUFFER_FOR_CPP_TYPE_VALUE(type, buffer);
|
||||
|
||||
if (!pointcloud_sample_indices.is_empty()) {
|
||||
const PointCloudComponent &component =
|
||||
*src_geometry.get_component_for_read<PointCloudComponent>();
|
||||
ReadAttributeLookup src_attribute = component.attribute_try_get_for_read(src_name, data_type);
|
||||
if (src_attribute) {
|
||||
BLI_assert(src_attribute.domain == ATTR_DOMAIN_POINT);
|
||||
for (const int i : pointcloud_sample_indices) {
|
||||
const int point_index = pointcloud_point_indices[i];
|
||||
src_attribute.varray->get(point_index, buffer);
|
||||
dst_attribute->set_by_relocate(i, buffer);
|
||||
}
|
||||
}
|
||||
else {
|
||||
const void *default_value = type.default_value();
|
||||
for (const int i : pointcloud_point_indices) {
|
||||
dst_attribute->set_by_copy(i, default_value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!mesh_sample_indices.is_empty()) {
|
||||
const MeshComponent &component = *src_geometry.get_component_for_read<MeshComponent>();
|
||||
const Mesh &mesh = *component.get_for_read();
|
||||
ReadAttributeLookup src_attribute = component.attribute_try_get_for_read(src_name, data_type);
|
||||
if (src_attribute) {
|
||||
GMutableSpan dst_span = dst_attribute.as_span();
|
||||
Array<float3> bary_coords(tot_dst_positions);
|
||||
get_barycentric_coords(mesh, mesh_looptri_indices, mesh_point_positions, bary_coords);
|
||||
|
||||
/* TODO: Take mask into account. */
|
||||
switch (src_attribute.domain) {
|
||||
case ATTR_DOMAIN_POINT: {
|
||||
bke::mesh_surface_sample::sample_point_attribute(
|
||||
mesh, mesh_looptri_indices, bary_coords, *src_attribute.varray, dst_span);
|
||||
break;
|
||||
}
|
||||
case ATTR_DOMAIN_FACE: {
|
||||
bke::mesh_surface_sample::sample_face_attribute(
|
||||
mesh, mesh_looptri_indices, *src_attribute.varray, dst_span);
|
||||
break;
|
||||
}
|
||||
case ATTR_DOMAIN_CORNER: {
|
||||
bke::mesh_surface_sample::sample_corner_attribute(
|
||||
mesh, mesh_looptri_indices, bary_coords, *src_attribute.varray, dst_span);
|
||||
break;
|
||||
}
|
||||
case ATTR_DOMAIN_EDGE: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
const void *default_value = type.default_value();
|
||||
for (const int i : mesh_sample_indices) {
|
||||
dst_attribute->set_by_copy(i, default_value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dst_attribute.save();
|
||||
}
|
||||
|
||||
static void geo_node_attribute_transfer_exec(GeoNodeExecParams params)
|
||||
{
|
||||
GeometrySet dst_geometry_set = params.extract_input<GeometrySet>("Geometry");
|
||||
GeometrySet src_geometry_set = params.extract_input<GeometrySet>("Target");
|
||||
const std::string src_attribute_name = params.extract_input<std::string>("Source");
|
||||
const std::string dst_attribute_name = params.extract_input<std::string>("Destination");
|
||||
|
||||
if (src_attribute_name.empty() || dst_attribute_name.empty()) {
|
||||
params.set_output("Geometry", dst_geometry_set);
|
||||
return;
|
||||
}
|
||||
|
||||
const NodeGeometryAttributeTransfer &storage =
|
||||
*(const NodeGeometryAttributeTransfer *)params.node().storage;
|
||||
const AttributeDomain dst_domain = (AttributeDomain)storage.domain;
|
||||
const GeometryNodeAttributeTransferMappingMode mapping =
|
||||
(GeometryNodeAttributeTransferMappingMode)storage.mapping;
|
||||
|
||||
dst_geometry_set = bke::geometry_set_realize_instances(dst_geometry_set);
|
||||
src_geometry_set = bke::geometry_set_realize_instances(src_geometry_set);
|
||||
|
||||
const CustomDataType result_data_type = get_result_data_type(src_geometry_set,
|
||||
src_attribute_name);
|
||||
|
||||
if (dst_geometry_set.has<MeshComponent>()) {
|
||||
transfer_attribute(src_geometry_set,
|
||||
dst_geometry_set.get_component_for_write<MeshComponent>(),
|
||||
dst_domain,
|
||||
result_data_type,
|
||||
src_attribute_name,
|
||||
dst_attribute_name);
|
||||
}
|
||||
if (dst_geometry_set.has<PointCloudComponent>()) {
|
||||
transfer_attribute(src_geometry_set,
|
||||
dst_geometry_set.get_component_for_write<PointCloudComponent>(),
|
||||
dst_domain,
|
||||
result_data_type,
|
||||
src_attribute_name,
|
||||
dst_attribute_name);
|
||||
}
|
||||
|
||||
params.set_output("Geometry", dst_geometry_set);
|
||||
}
|
||||
|
||||
} // namespace blender::nodes
|
||||
|
||||
void register_node_type_geo_attribute_transfer()
|
||||
{
|
||||
static bNodeType ntype;
|
||||
|
||||
geo_node_type_base(
|
||||
&ntype, GEO_NODE_ATTRIBUTE_TRANSFER, "Attribute Transfer", NODE_CLASS_ATTRIBUTE, 0);
|
||||
node_type_socket_templates(
|
||||
&ntype, geo_node_attribute_transfer_in, geo_node_attribute_transfer_out);
|
||||
node_type_init(&ntype, blender::nodes::geo_node_attribute_transfer_init);
|
||||
node_type_storage(&ntype,
|
||||
"NodeGeometryAttributeTransfer",
|
||||
node_free_standard_storage,
|
||||
node_copy_standard_storage);
|
||||
ntype.geometry_node_execute = blender::nodes::geo_node_attribute_transfer_exec;
|
||||
ntype.draw_buttons = geo_node_attribute_transfer_layout;
|
||||
nodeRegisterType(&ntype);
|
||||
}
|
@@ -28,6 +28,7 @@
|
||||
#include "BKE_geometry_set_instances.hh"
|
||||
#include "BKE_mesh.h"
|
||||
#include "BKE_mesh_runtime.h"
|
||||
#include "BKE_mesh_sample.hh"
|
||||
#include "BKE_pointcloud.h"
|
||||
|
||||
#include "UI_interface.h"
|
||||
@@ -249,99 +250,27 @@ BLI_NOINLINE static void eliminate_points_based_on_mask(Span<bool> elimination_m
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
BLI_NOINLINE static void interpolate_attribute_point(const Mesh &mesh,
|
||||
const Span<float3> bary_coords,
|
||||
const Span<int> looptri_indices,
|
||||
const Span<T> data_in,
|
||||
MutableSpan<T> data_out)
|
||||
{
|
||||
BLI_assert(data_in.size() == mesh.totvert);
|
||||
Span<MLoopTri> looptris = get_mesh_looptris(mesh);
|
||||
|
||||
for (const int i : bary_coords.index_range()) {
|
||||
const int looptri_index = looptri_indices[i];
|
||||
const MLoopTri &looptri = looptris[looptri_index];
|
||||
const float3 &bary_coord = bary_coords[i];
|
||||
|
||||
const int v0_index = mesh.mloop[looptri.tri[0]].v;
|
||||
const int v1_index = mesh.mloop[looptri.tri[1]].v;
|
||||
const int v2_index = mesh.mloop[looptri.tri[2]].v;
|
||||
|
||||
const T &v0 = data_in[v0_index];
|
||||
const T &v1 = data_in[v1_index];
|
||||
const T &v2 = data_in[v2_index];
|
||||
|
||||
const T interpolated_value = attribute_math::mix3(bary_coord, v0, v1, v2);
|
||||
data_out[i] = interpolated_value;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
BLI_NOINLINE static void interpolate_attribute_corner(const Mesh &mesh,
|
||||
const Span<float3> bary_coords,
|
||||
const Span<int> looptri_indices,
|
||||
const Span<T> data_in,
|
||||
MutableSpan<T> data_out)
|
||||
{
|
||||
BLI_assert(data_in.size() == mesh.totloop);
|
||||
Span<MLoopTri> looptris = get_mesh_looptris(mesh);
|
||||
|
||||
for (const int i : bary_coords.index_range()) {
|
||||
const int looptri_index = looptri_indices[i];
|
||||
const MLoopTri &looptri = looptris[looptri_index];
|
||||
const float3 &bary_coord = bary_coords[i];
|
||||
|
||||
const int loop_index_0 = looptri.tri[0];
|
||||
const int loop_index_1 = looptri.tri[1];
|
||||
const int loop_index_2 = looptri.tri[2];
|
||||
|
||||
const T &v0 = data_in[loop_index_0];
|
||||
const T &v1 = data_in[loop_index_1];
|
||||
const T &v2 = data_in[loop_index_2];
|
||||
|
||||
const T interpolated_value = attribute_math::mix3(bary_coord, v0, v1, v2);
|
||||
data_out[i] = interpolated_value;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
BLI_NOINLINE static void interpolate_attribute_face(const Mesh &mesh,
|
||||
const Span<int> looptri_indices,
|
||||
const Span<T> data_in,
|
||||
MutableSpan<T> data_out)
|
||||
{
|
||||
BLI_assert(data_in.size() == mesh.totpoly);
|
||||
Span<MLoopTri> looptris = get_mesh_looptris(mesh);
|
||||
|
||||
for (const int i : data_out.index_range()) {
|
||||
const int looptri_index = looptri_indices[i];
|
||||
const MLoopTri &looptri = looptris[looptri_index];
|
||||
const int poly_index = looptri.poly;
|
||||
data_out[i] = data_in[poly_index];
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
BLI_NOINLINE static void interpolate_attribute(const Mesh &mesh,
|
||||
Span<float3> bary_coords,
|
||||
Span<int> looptri_indices,
|
||||
const AttributeDomain source_domain,
|
||||
Span<T> source_span,
|
||||
MutableSpan<T> output_span)
|
||||
const GVArray &source_data,
|
||||
GMutableSpan output_data)
|
||||
{
|
||||
switch (source_domain) {
|
||||
case ATTR_DOMAIN_POINT: {
|
||||
interpolate_attribute_point<T>(mesh, bary_coords, looptri_indices, source_span, output_span);
|
||||
bke::mesh_surface_sample::sample_point_attribute(
|
||||
mesh, looptri_indices, bary_coords, source_data, output_data);
|
||||
break;
|
||||
}
|
||||
case ATTR_DOMAIN_CORNER: {
|
||||
interpolate_attribute_corner<T>(
|
||||
mesh, bary_coords, looptri_indices, source_span, output_span);
|
||||
bke::mesh_surface_sample::sample_corner_attribute(
|
||||
mesh, looptri_indices, bary_coords, source_data, output_data);
|
||||
break;
|
||||
}
|
||||
case ATTR_DOMAIN_FACE: {
|
||||
interpolate_attribute_face<T>(mesh, looptri_indices, source_span, output_span);
|
||||
bke::mesh_surface_sample::sample_face_attribute(
|
||||
mesh, looptri_indices, source_data, output_data);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
@@ -395,22 +324,22 @@ BLI_NOINLINE static void interpolate_existing_attributes(
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const int UNUSED(i_set_instance) : set_group.transforms.index_range()) {
|
||||
const int offset = instance_start_offsets[i_instance];
|
||||
Span<float3> bary_coords = bary_coords_array[i_instance];
|
||||
Span<int> looptri_indices = looptri_indices_array[i_instance];
|
||||
|
||||
GMutableSpan instance_span = out_span.slice(offset, bary_coords.size());
|
||||
interpolate_attribute(
|
||||
mesh, bary_coords, looptri_indices, source_domain, *source_attribute, instance_span);
|
||||
|
||||
i_instance++;
|
||||
}
|
||||
|
||||
attribute_math::convert_to_static_type(output_data_type, [&](auto dummy) {
|
||||
using T = decltype(dummy);
|
||||
|
||||
GVArray_Span<T> source_span{*source_attribute};
|
||||
|
||||
for (const int UNUSED(i_set_instance) : set_group.transforms.index_range()) {
|
||||
const int offset = instance_start_offsets[i_instance];
|
||||
Span<float3> bary_coords = bary_coords_array[i_instance];
|
||||
Span<int> looptri_indices = looptri_indices_array[i_instance];
|
||||
|
||||
MutableSpan<T> instance_span = out_span.typed<T>().slice(offset, bary_coords.size());
|
||||
interpolate_attribute<T>(
|
||||
mesh, bary_coords, looptri_indices, source_domain, source_span, instance_span);
|
||||
|
||||
i_instance++;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user