diff options
Diffstat (limited to 'source')
-rw-r--r-- | source/blender/blenkernel/BKE_node.h | 2 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/node.cc | 2 | ||||
-rw-r--r-- | source/blender/makesdna/DNA_node_types.h | 10 | ||||
-rw-r--r-- | source/blender/makesrna/intern/rna_nodetree.c | 27 | ||||
-rw-r--r-- | source/blender/nodes/NOD_geometry.h | 2 | ||||
-rw-r--r-- | source/blender/nodes/NOD_static_types.h | 2 | ||||
-rw-r--r-- | source/blender/nodes/geometry/CMakeLists.txt | 2 | ||||
-rw-r--r-- | source/blender/nodes/geometry/nodes/node_geo_uv_pack_islands.cc | 152 | ||||
-rw-r--r-- | source/blender/nodes/geometry/nodes/node_geo_uv_unwrap.cc | 199 |
9 files changed, 398 insertions, 0 deletions
diff --git a/source/blender/blenkernel/BKE_node.h b/source/blender/blenkernel/BKE_node.h index bad3e0005b8..e13ac3180ec 100644 --- a/source/blender/blenkernel/BKE_node.h +++ b/source/blender/blenkernel/BKE_node.h @@ -1499,6 +1499,8 @@ struct TexResult; #define GEO_NODE_POINTS 1162 #define GEO_NODE_FIELD_ON_DOMAIN 1163 #define GEO_NODE_MESH_TO_VOLUME 1164 +#define GEO_NODE_UV_UNWRAP 1165 +#define GEO_NODE_UV_PACK_ISLANDS 1166 /** \} */ diff --git a/source/blender/blenkernel/intern/node.cc b/source/blender/blenkernel/intern/node.cc index 244c9ccd048..5be912ffb2b 100644 --- a/source/blender/blenkernel/intern/node.cc +++ b/source/blender/blenkernel/intern/node.cc @@ -4841,6 +4841,8 @@ static void registerGeometryNodes() register_node_type_geo_viewer(); register_node_type_geo_volume_cube(); register_node_type_geo_volume_to_mesh(); + register_node_type_geo_uv_pack_islands(); + register_node_type_geo_uv_unwrap(); } static void registerFunctionNodes() diff --git a/source/blender/makesdna/DNA_node_types.h b/source/blender/makesdna/DNA_node_types.h index 3f3eb6e0571..76d8207eead 100644 --- a/source/blender/makesdna/DNA_node_types.h +++ b/source/blender/makesdna/DNA_node_types.h @@ -1455,6 +1455,11 @@ typedef struct NodeGeometryViewer { int8_t data_type; } NodeGeometryViewer; +typedef struct NodeGeometryUVUnwrap { + /* GeometryNodeUVUnwrapMethod. */ + uint8_t method; +} NodeGeometryUVUnwrap; + typedef struct NodeFunctionCompare { /* NodeCompareOperation */ int8_t operation; @@ -2005,6 +2010,11 @@ typedef enum GeometryNodeMergeByDistanceMode { GEO_NODE_MERGE_BY_DISTANCE_MODE_CONNECTED = 1, } GeometryNodeMergeByDistanceMode; +typedef enum GeometryNodeUVUnwrapMethod { + GEO_NODE_UV_UNWRAP_METHOD_ANGLE_BASED = 0, + GEO_NODE_UV_UNWRAP_METHOD_CONFORMAL = 1, +} GeometryNodeUVUnwrapMethod; + typedef enum GeometryNodeMeshLineMode { GEO_NODE_MESH_LINE_MODE_END_POINTS = 0, GEO_NODE_MESH_LINE_MODE_OFFSET = 1, diff --git a/source/blender/makesrna/intern/rna_nodetree.c b/source/blender/makesrna/intern/rna_nodetree.c index 0ad109e711f..386ef3f74a3 100644 --- a/source/blender/makesrna/intern/rna_nodetree.c +++ b/source/blender/makesrna/intern/rna_nodetree.c @@ -9879,6 +9879,33 @@ static void def_geo_points_to_volume(StructRNA *srna) RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update"); } +static void def_geo_uv_unwrap(StructRNA *srna) +{ + PropertyRNA *prop; + + static EnumPropertyItem rna_node_geometry_uv_unwrap_method_items[] = { + {GEO_NODE_UV_UNWRAP_METHOD_ANGLE_BASED, + "ANGLE_BASED", + 0, + "Angle Based", + "This method gives a good 2D representation of a mesh"}, + {GEO_NODE_UV_UNWRAP_METHOD_CONFORMAL, + "CONFORMAL", + 0, + "Conformal", + "Uses LSCM (Least Squares Conformal Mapping). This usually gives a less accurate UV " + "mapping than Angle Based, but works better for simpler objects"}, + {0, NULL, 0, NULL, NULL}, + }; + + RNA_def_struct_sdna_from(srna, "NodeGeometryUVUnwrap", "storage"); + + prop = RNA_def_property(srna, "method", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_items(prop, rna_node_geometry_uv_unwrap_method_items); + RNA_def_property_ui_text(prop, "Method", ""); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); +} + static void def_geo_collection_info(StructRNA *srna) { PropertyRNA *prop; diff --git a/source/blender/nodes/NOD_geometry.h b/source/blender/nodes/NOD_geometry.h index 889ab2a81e4..8f15add33fd 100644 --- a/source/blender/nodes/NOD_geometry.h +++ b/source/blender/nodes/NOD_geometry.h @@ -140,6 +140,8 @@ void register_node_type_geo_triangulate(void); void register_node_type_geo_viewer(void); void register_node_type_geo_volume_cube(void); void register_node_type_geo_volume_to_mesh(void); +void register_node_type_geo_uv_pack_islands(void); +void register_node_type_geo_uv_unwrap(void); #ifdef __cplusplus } diff --git a/source/blender/nodes/NOD_static_types.h b/source/blender/nodes/NOD_static_types.h index f9925924260..609791ad091 100644 --- a/source/blender/nodes/NOD_static_types.h +++ b/source/blender/nodes/NOD_static_types.h @@ -402,6 +402,8 @@ DefNode(GeometryNode, GEO_NODE_TRIM_CURVE, def_geo_curve_trim, "TRIM_CURVE", Tri DefNode(GeometryNode, GEO_NODE_VIEWER, def_geo_viewer, "VIEWER", Viewer, "Viewer", "") DefNode(GeometryNode, GEO_NODE_VOLUME_CUBE, 0, "VOLUME_CUBE", VolumeCube, "Volume Cube", "") DefNode(GeometryNode, GEO_NODE_VOLUME_TO_MESH, def_geo_volume_to_mesh, "VOLUME_TO_MESH", VolumeToMesh, "Volume to Mesh", "") +DefNode(GeometryNode, GEO_NODE_UV_PACK_ISLANDS, 0, "UV_PACK_ISLANDS", UVPackIslands, "Pack UV Islands", "") +DefNode(GeometryNode, GEO_NODE_UV_UNWRAP, def_geo_uv_unwrap, "UV_UNWRAP", UVUnwrap, "UV Unwrap", "") /* undefine macros */ #undef DefNode diff --git a/source/blender/nodes/geometry/CMakeLists.txt b/source/blender/nodes/geometry/CMakeLists.txt index 9ef3151ddab..950124f75d0 100644 --- a/source/blender/nodes/geometry/CMakeLists.txt +++ b/source/blender/nodes/geometry/CMakeLists.txt @@ -146,6 +146,8 @@ set(SRC nodes/node_geo_transform.cc nodes/node_geo_translate_instances.cc nodes/node_geo_triangulate.cc + nodes/node_geo_uv_pack_islands.cc + nodes/node_geo_uv_unwrap.cc nodes/node_geo_viewer.cc nodes/node_geo_volume_cube.cc nodes/node_geo_volume_to_mesh.cc diff --git a/source/blender/nodes/geometry/nodes/node_geo_uv_pack_islands.cc b/source/blender/nodes/geometry/nodes/node_geo_uv_pack_islands.cc new file mode 100644 index 00000000000..feff6efc3f8 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_uv_pack_islands.cc @@ -0,0 +1,152 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "GEO_uv_parametrizer.h" + +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" + +#include "node_geometry_util.hh" + +namespace blender::nodes::node_geo_uv_pack_islands_cc { + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Vector>(N_("UV")).hide_value().supports_field(); + b.add_input<decl::Bool>(N_("Selection")) + .default_value(true) + .hide_value() + .supports_field() + .description(N_("Faces to consider when packing islands")); + b.add_input<decl::Float>(N_("Margin")) + .default_value(0.001f) + .min(0.0f) + .max(1.0f) + .description(N_("Space between islands")); + b.add_input<decl::Bool>(N_("Rotate")) + .default_value(true) + .description(N_("Rotate islands for best fit")); + b.add_output<decl::Vector>(N_("UV")).field_source(); +} + +static VArray<float3> construct_uv_gvarray(const MeshComponent &component, + const Field<bool> selection_field, + const Field<float3> uv_field, + const bool rotate, + const float margin, + const eAttrDomain domain) +{ + const Mesh *mesh = component.get_for_read(); + if (mesh == nullptr) { + return {}; + } + + const int face_num = component.attribute_domain_num(ATTR_DOMAIN_FACE); + GeometryComponentFieldContext face_context{component, ATTR_DOMAIN_FACE}; + FieldEvaluator face_evaluator{face_context, face_num}; + face_evaluator.add(selection_field); + face_evaluator.evaluate(); + const IndexMask selection = face_evaluator.get_evaluated_as_mask(0); + if (selection.is_empty()) { + return {}; + } + + const int corner_num = component.attribute_domain_num(ATTR_DOMAIN_CORNER); + GeometryComponentFieldContext corner_context{component, ATTR_DOMAIN_CORNER}; + FieldEvaluator evaluator{corner_context, corner_num}; + Array<float3> uv(corner_num); + evaluator.add_with_destination(uv_field, uv.as_mutable_span()); + evaluator.evaluate(); + + ParamHandle *handle = GEO_uv_parametrizer_construct_begin(); + for (const int mp_index : selection) { + const MPoly &mp = mesh->mpoly[mp_index]; + Array<ParamKey, 16> mp_vkeys(mp.totloop); + Array<bool, 16> mp_pin(mp.totloop); + Array<bool, 16> mp_select(mp.totloop); + Array<const float *, 16> mp_co(mp.totloop); + Array<float *, 16> mp_uv(mp.totloop); + for (const int i : IndexRange(mp.totloop)) { + const MLoop &ml = mesh->mloop[mp.loopstart + i]; + mp_vkeys[i] = ml.v; + mp_co[i] = mesh->mvert[ml.v].co; + mp_uv[i] = uv[mp.loopstart + i]; + mp_pin[i] = false; + mp_select[i] = false; + } + GEO_uv_parametrizer_face_add(handle, + mp_index, + mp.totloop, + mp_vkeys.data(), + mp_co.data(), + mp_uv.data(), + mp_pin.data(), + mp_select.data()); + } + GEO_uv_parametrizer_construct_end(handle, true, true, nullptr); + + GEO_uv_parametrizer_pack(handle, margin, rotate, true); + GEO_uv_parametrizer_flush(handle); + GEO_uv_parametrizer_delete(handle); + + return component.attribute_try_adapt_domain<float3>( + VArray<float3>::ForContainer(std::move(uv)), ATTR_DOMAIN_CORNER, domain); +} + +class PackIslandsFieldInput final : public GeometryFieldInput { + private: + const Field<bool> selection_field; + const Field<float3> uv_field; + const bool rotate; + const float margin; + + public: + PackIslandsFieldInput(const Field<bool> selection_field, + const Field<float3> uv_field, + const bool rotate, + const float margin) + : GeometryFieldInput(CPPType::get<float3>(), "Pack UV Islands Field"), + selection_field(selection_field), + uv_field(uv_field), + rotate(rotate), + margin(margin) + { + category_ = Category::Generated; + } + + GVArray get_varray_for_context(const GeometryComponent &component, + const eAttrDomain domain, + IndexMask UNUSED(mask)) const final + { + if (component.type() == GEO_COMPONENT_TYPE_MESH) { + const MeshComponent &mesh_component = static_cast<const MeshComponent &>(component); + return construct_uv_gvarray( + mesh_component, selection_field, uv_field, rotate, margin, domain); + } + return {}; + } +}; + +static void node_geo_exec(GeoNodeExecParams params) +{ + const Field<bool> selection_field = params.extract_input<Field<bool>>("Selection"); + const Field<float3> uv_field = params.extract_input<Field<float3>>("UV"); + const bool rotate = params.extract_input<bool>("Rotate"); + const float margin = params.extract_input<float>("Margin"); + params.set_output("UV", + Field<float3>(std::make_shared<PackIslandsFieldInput>( + selection_field, uv_field, rotate, margin))); +} + +} // namespace blender::nodes::node_geo_uv_pack_islands_cc + +void register_node_type_geo_uv_pack_islands() +{ + namespace file_ns = blender::nodes::node_geo_uv_pack_islands_cc; + + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_UV_PACK_ISLANDS, "Pack UV Islands", NODE_CLASS_CONVERTER); + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_uv_unwrap.cc b/source/blender/nodes/geometry/nodes/node_geo_uv_unwrap.cc new file mode 100644 index 00000000000..bf960c5c809 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_uv_unwrap.cc @@ -0,0 +1,199 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "GEO_uv_parametrizer.h" + +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "node_geometry_util.hh" + +namespace blender::nodes::node_geo_uv_unwrap_cc { + +NODE_STORAGE_FUNCS(NodeGeometryUVUnwrap) + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Bool>(N_("Selection")) + .default_value(true) + .hide_value() + .supports_field() + .description(N_("Faces to participate in the unwrap operation")); + b.add_input<decl::Bool>(N_("Seam")) + .hide_value() + .supports_field() + .description(N_("Edges to mark where the mesh is \"cut\" for the purposes of unwrapping")); + b.add_input<decl::Float>(N_("Margin")) + .default_value(0.001f) + .min(0.0f) + .max(1.0f) + .description(N_("Space between islands")); + b.add_input<decl::Bool>(N_("Fill Holes")) + .default_value(true) + .description(N_("Virtually fill holes in mesh before unwrapping, to better avoid overlaps " + "and preserve symmetry")); + b.add_output<decl::Vector>(N_("UV")).field_source().description( + N_("UV coordinates between 0 and 1 for each face corner in the selected faces")); +} + +static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +{ + uiLayoutSetPropSep(layout, true); + uiLayoutSetPropDecorate(layout, false); + uiItemR(layout, ptr, "method", 0, "", ICON_NONE); +} + +static void node_init(bNodeTree *UNUSED(tree), bNode *node) +{ + NodeGeometryUVUnwrap *data = MEM_cnew<NodeGeometryUVUnwrap>(__func__); + data->method = GEO_NODE_UV_UNWRAP_METHOD_ANGLE_BASED; + node->storage = data; +} + +static VArray<float3> construct_uv_gvarray(const MeshComponent &component, + const Field<bool> selection_field, + const Field<bool> seam_field, + const bool fill_holes, + const float margin, + const GeometryNodeUVUnwrapMethod method, + const eAttrDomain domain) +{ + const Mesh *mesh = component.get_for_read(); + if (mesh == nullptr) { + return {}; + } + + const int face_num = component.attribute_domain_num(ATTR_DOMAIN_FACE); + GeometryComponentFieldContext face_context{component, ATTR_DOMAIN_FACE}; + FieldEvaluator face_evaluator{face_context, face_num}; + face_evaluator.add(selection_field); + face_evaluator.evaluate(); + const IndexMask selection = face_evaluator.get_evaluated_as_mask(0); + if (selection.is_empty()) { + return {}; + } + + const int edge_num = component.attribute_domain_num(ATTR_DOMAIN_EDGE); + GeometryComponentFieldContext edge_context{component, ATTR_DOMAIN_EDGE}; + FieldEvaluator edge_evaluator{edge_context, edge_num}; + edge_evaluator.add(seam_field); + edge_evaluator.evaluate(); + const IndexMask seam = edge_evaluator.get_evaluated_as_mask(0); + + Array<float3> uv(mesh->totloop); + + ParamHandle *handle = GEO_uv_parametrizer_construct_begin(); + for (const int mp_index : selection) { + const MPoly &mp = mesh->mpoly[mp_index]; + Array<ParamKey, 16> mp_vkeys(mp.totloop); + Array<bool, 16> mp_pin(mp.totloop); + Array<bool, 16> mp_select(mp.totloop); + Array<const float *, 16> mp_co(mp.totloop); + Array<float *, 16> mp_uv(mp.totloop); + for (const int i : IndexRange(mp.totloop)) { + const MLoop &ml = mesh->mloop[mp.loopstart + i]; + mp_vkeys[i] = ml.v; + mp_co[i] = mesh->mvert[ml.v].co; + mp_uv[i] = uv[mp.loopstart + i]; + mp_pin[i] = false; + mp_select[i] = false; + } + GEO_uv_parametrizer_face_add(handle, + mp_index, + mp.totloop, + mp_vkeys.data(), + mp_co.data(), + mp_uv.data(), + mp_pin.data(), + mp_select.data()); + } + for (const int i : seam) { + const MEdge &edge = mesh->medge[i]; + ParamKey vkeys[2]{edge.v1, edge.v2}; + GEO_uv_parametrizer_edge_set_seam(handle, vkeys); + } + /* TODO: once field input nodes are able to emit warnings (T94039), emit a + * warning if we fail to solve an island. */ + GEO_uv_parametrizer_construct_end(handle, fill_holes, false, nullptr); + + GEO_uv_parametrizer_lscm_begin(handle, false, method == GEO_NODE_UV_UNWRAP_METHOD_ANGLE_BASED); + GEO_uv_parametrizer_lscm_solve(handle, nullptr, nullptr); + GEO_uv_parametrizer_lscm_end(handle); + GEO_uv_parametrizer_average(handle, true); + GEO_uv_parametrizer_pack(handle, margin, true, true); + GEO_uv_parametrizer_flush(handle); + GEO_uv_parametrizer_delete(handle); + + return component.attribute_try_adapt_domain<float3>( + VArray<float3>::ForContainer(std::move(uv)), ATTR_DOMAIN_CORNER, domain); +} + +class UnwrapFieldInput final : public GeometryFieldInput { + private: + const Field<bool> selection; + const Field<bool> seam; + const bool fill_holes; + const float margin; + const GeometryNodeUVUnwrapMethod method; + + public: + UnwrapFieldInput(const Field<bool> selection, + const Field<bool> seam, + const bool fill_holes, + const float margin, + const GeometryNodeUVUnwrapMethod method) + : GeometryFieldInput(CPPType::get<float3>(), "UV Unwrap Field"), + selection(selection), + seam(seam), + fill_holes(fill_holes), + margin(margin), + method(method) + { + category_ = Category::Generated; + } + + GVArray get_varray_for_context(const GeometryComponent &component, + const eAttrDomain domain, + IndexMask UNUSED(mask)) const final + { + if (component.type() == GEO_COMPONENT_TYPE_MESH) { + const MeshComponent &mesh_component = static_cast<const MeshComponent &>(component); + return construct_uv_gvarray( + mesh_component, selection, seam, fill_holes, margin, method, domain); + } + return {}; + } +}; + +static void node_geo_exec(GeoNodeExecParams params) +{ + const NodeGeometryUVUnwrap &storage = node_storage(params.node()); + const GeometryNodeUVUnwrapMethod method = (GeometryNodeUVUnwrapMethod)storage.method; + const Field<bool> selection_field = params.extract_input<Field<bool>>("Selection"); + const Field<bool> seam_field = params.extract_input<Field<bool>>("Seam"); + const bool fill_holes = params.extract_input<bool>("Fill Holes"); + const float margin = params.extract_input<float>("Margin"); + params.set_output("UV", + Field<float3>(std::make_shared<UnwrapFieldInput>( + selection_field, seam_field, fill_holes, margin, method))); +} + +} // namespace blender::nodes::node_geo_uv_unwrap_cc + +void register_node_type_geo_uv_unwrap() +{ + namespace file_ns = blender::nodes::node_geo_uv_unwrap_cc; + + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_UV_UNWRAP, "UV Unwrap", NODE_CLASS_CONVERTER); + node_type_init(&ntype, file_ns::node_init); + node_type_storage( + &ntype, "NodeGeometryUVUnwrap", node_free_standard_storage, node_copy_standard_storage); + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.draw_buttons = file_ns::node_layout; + nodeRegisterType(&ntype); +} |