Geometry Nodes: Add Shape Method parameter to Pack UV Islands node

Adds a Shape Method parameter to the UV Pack Islands node, enabling
artists to choose between faster packing and more efficient space
utilization.

Those are the three shape method options:
* Bounding Box: Fastest, less efficient space usage
* Convex Hull: Balanced performance and efficiency
* Exact Shape: Optimal packing, higher computational cost

This change consolidates arguments in `uv_parametrizer_pack()`. Now it
accept ` UVPackIsland_Params` instead of many different separate options.
This also makes it easier to expose more options in the future.

Pull Request: https://projects.blender.org/blender/blender/pulls/139110
This commit is contained in:
Arye Ramaty 2025-06-12 16:41:17 +02:00 committed by Hans Goudey
parent 30644d20a8
commit aced349e3d
4 changed files with 105 additions and 14 deletions

View File

@ -10,6 +10,10 @@ namespace slim {
struct MatrixTransfer;
}
namespace blender::geometry {
class UVPackIsland_Params;
}
/** \file
* \ingroup geo
*/
@ -166,7 +170,7 @@ void uv_parametrizer_stretch_end(ParamHandle *handle);
/** \name Packing
* \{ */
void uv_parametrizer_pack(ParamHandle *handle, float margin, bool do_rotate, bool ignore_pinned);
void uv_parametrizer_pack(ParamHandle *handle, const UVPackIsland_Params &params);
/** \} */

View File

@ -4176,7 +4176,7 @@ void uv_parametrizer_stretch_end(ParamHandle *phandle)
phandle->state = PHANDLE_STATE_CONSTRUCTED;
}
void uv_parametrizer_pack(ParamHandle *handle, float margin, bool do_rotate, bool ignore_pinned)
void uv_parametrizer_pack(ParamHandle *handle, const UVPackIsland_Params &params)
{
if (handle->ncharts == 0) {
return;
@ -4186,14 +4186,9 @@ void uv_parametrizer_pack(ParamHandle *handle, float margin, bool do_rotate, boo
Vector<PackIsland *> pack_island_vector;
UVPackIsland_Params params;
params.rotate_method = do_rotate ? ED_UVPACK_ROTATION_ANY : ED_UVPACK_ROTATION_NONE;
params.margin = margin;
params.margin_method = ED_UVPACK_MARGIN_SCALED;
for (int i = 0; i < handle->ncharts; i++) {
PChart *chart = handle->charts[i];
if (ignore_pinned && chart->has_pins) {
if (params.pin_method == ED_UVPACK_PIN_NONE && chart->has_pins) {
continue;
}

View File

@ -2,16 +2,45 @@
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "GEO_uv_pack.hh"
#include "GEO_uv_parametrizer.hh"
#include "DNA_mesh_types.h"
#include "node_geometry_util.hh"
#include "NOD_rna_define.hh"
#include "UI_interface.hh"
namespace blender::nodes::node_geo_uv_pack_islands_cc {
/** Local node enum that maps to eUVPackIsland_ShapeMethod in GEO_uv_pack.hh. */
enum class ShapeMethod : int16_t {
Aabb = 0,
Convex = 1,
Concave = 2,
};
static eUVPackIsland_ShapeMethod convert_shape_method(const ShapeMethod method)
{
switch (method) {
case ShapeMethod::Aabb:
return ED_UVPACK_SHAPE_AABB;
case ShapeMethod::Convex:
return ED_UVPACK_SHAPE_CONVEX;
case ShapeMethod::Concave:
return ED_UVPACK_SHAPE_CONCAVE;
}
BLI_assert_unreachable();
return ED_UVPACK_SHAPE_AABB;
}
static void node_declare(NodeDeclarationBuilder &b)
{
b.use_custom_socket_order();
b.allow_any_socket_order();
b.add_default_layout();
b.add_input<decl::Vector>("UV").hide_value().supports_field();
b.add_input<decl::Bool>("Selection")
.default_value(true)
@ -24,11 +53,22 @@ static void node_declare(NodeDeclarationBuilder &b)
b.add_output<decl::Vector>("UV").field_source_reference_all();
}
static void node_layout(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr)
{
layout->prop(ptr, "shape_method", UI_ITEM_NONE, "", ICON_NONE);
}
static void node_init(bNodeTree * /*tree*/, bNode *node)
{
node->custom1 = int16_t(ShapeMethod::Aabb);
}
static VArray<float3> construct_uv_gvarray(const Mesh &mesh,
const Field<bool> selection_field,
const Field<float3> uv_field,
const bool rotate,
const float margin,
const eUVPackIsland_ShapeMethod shape_method,
const AttrDomain domain)
{
const Span<float3> positions = mesh.vert_positions();
@ -79,7 +119,12 @@ static VArray<float3> construct_uv_gvarray(const Mesh &mesh,
});
geometry::uv_parametrizer_construct_end(handle, true, true, nullptr);
geometry::uv_parametrizer_pack(handle, margin, rotate, true);
blender::geometry::UVPackIsland_Params params;
params.shape_method = shape_method;
params.rotate_method = rotate ? ED_UVPACK_ROTATION_ANY : ED_UVPACK_ROTATION_NONE;
params.margin = margin;
geometry::uv_parametrizer_pack(handle, params);
geometry::uv_parametrizer_flush(handle);
delete (handle);
@ -93,17 +138,20 @@ class PackIslandsFieldInput final : public bke::MeshFieldInput {
const Field<float3> uv_field_;
const bool rotate_;
const float margin_;
const eUVPackIsland_ShapeMethod shape_method_;
public:
PackIslandsFieldInput(const Field<bool> selection_field,
const Field<float3> uv_field,
const bool rotate,
const float margin)
const float margin,
const eUVPackIsland_ShapeMethod shape_method)
: bke::MeshFieldInput(CPPType::get<float3>(), "Pack UV Islands Field"),
selection_field_(selection_field),
uv_field_(uv_field),
rotate_(rotate),
margin_(margin)
margin_(margin),
shape_method_(shape_method)
{
category_ = Category::Generated;
}
@ -112,7 +160,8 @@ class PackIslandsFieldInput final : public bke::MeshFieldInput {
const AttrDomain domain,
const IndexMask & /*mask*/) const final
{
return construct_uv_gvarray(mesh, selection_field_, uv_field_, rotate_, margin_, domain);
return construct_uv_gvarray(
mesh, selection_field_, uv_field_, rotate_, margin_, shape_method_, domain);
}
void for_each_field_input_recursive(FunctionRef<void(const FieldInput &)> fn) const override
@ -129,13 +178,47 @@ class PackIslandsFieldInput final : public bke::MeshFieldInput {
static void node_geo_exec(GeoNodeExecParams params)
{
const bNode &node = params.node();
const ShapeMethod local_shape_method = ShapeMethod(node.custom1);
const eUVPackIsland_ShapeMethod shape_method = convert_shape_method(local_shape_method);
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)));
selection_field, uv_field, rotate, margin, shape_method)));
}
static void node_rna(StructRNA *srna)
{
static const EnumPropertyItem shape_method_items[] = {
{int(ShapeMethod::Aabb),
"AABB",
0,
"Bounding Box",
"Use axis-aligned bounding boxes for packing (fastest, least space efficient)"},
{int(ShapeMethod::Convex),
"CONVEX",
0,
"Convex Hull",
"Use convex hull approximation of islands (good balance of speed and space efficiency)"},
{int(ShapeMethod::Concave),
"CONCAVE",
0,
"Exact Shape",
"Use exact geometry for most efficient packing (slowest)"},
{0, nullptr, 0, nullptr, nullptr},
};
RNA_def_node_enum(srna,
"shape_method",
"Shape Method",
"Method used for packing UV islands",
shape_method_items,
NOD_inline_enum_accessors(custom1),
int(ShapeMethod::Aabb));
}
static void node_register()
@ -148,9 +231,13 @@ static void node_register()
"Scale islands of a UV map and move them so they fill the UV space as much as possible";
ntype.enum_name_legacy = "UV_PACK_ISLANDS";
ntype.nclass = NODE_CLASS_CONVERTER;
ntype.initfunc = node_init;
ntype.declare = node_declare;
ntype.geometry_node_execute = node_geo_exec;
ntype.draw_buttons = node_layout;
blender::bke::node_register_type(ntype);
node_rna(ntype.rna_ext.srna);
}
NOD_REGISTER_NODE(node_register)

View File

@ -4,6 +4,7 @@
#include "DNA_mesh_types.h"
#include "GEO_uv_pack.hh"
#include "GEO_uv_parametrizer.hh"
#include "UI_interface.hh"
@ -114,6 +115,10 @@ static VArray<float3> construct_uv_gvarray(const Mesh &mesh,
geometry::uv_parametrizer_edge_set_seam(handle, vkeys);
});
blender::geometry::UVPackIsland_Params params;
params.margin = margin;
params.rotate_method = ED_UVPACK_ROTATION_ANY;
/* TODO: once field input nodes are able to emit warnings (#94039), emit a
* warning if we fail to solve an island. */
geometry::uv_parametrizer_construct_end(handle, fill_holes, false, nullptr);
@ -123,7 +128,7 @@ static VArray<float3> construct_uv_gvarray(const Mesh &mesh,
geometry::uv_parametrizer_lscm_solve(handle, nullptr, nullptr);
geometry::uv_parametrizer_lscm_end(handle);
geometry::uv_parametrizer_average(handle, true, false, false);
geometry::uv_parametrizer_pack(handle, margin, true, true);
geometry::uv_parametrizer_pack(handle, params);
geometry::uv_parametrizer_flush(handle);
delete (handle);