Geometry Nodes: Add "Scale" input to "Curve to Mesh" node
This replaces the implicit use of the `radius` attribute on the input curves with an input field `Scale` that gets evaluated on the point domain of the input curves to scale the profile. It wasn't super intuitive that the `radius` would actually act as a scale of the profile. E.g. if the radius of the input curve was `1 meter` the resulting profile was unscaled (scaled by 1), but wouldn't necessarily have a size of `1 meter` (only if the profile also had a size of 1m)! If imperial units were used, `3.28084 ft` would correspond to a scale of 1. This change makes this behavior a lot more clear and potentially removes the need for the assumption that the default curve radius is `1.0f` (Ideally, the default curve radius should be `0.01f`). While we did consider making the `Scale` input use the `radius` field implicitly by default, we decided against it, because it again "hides" the dependency on the radius and the fact that the radius is used as a scale. Letting the user make this decision seems better. Pull Request: https://projects.blender.org/blender/blender/pulls/134187
This commit is contained in:
@@ -27,7 +27,7 @@
|
||||
|
||||
/* Blender file format version. */
|
||||
#define BLENDER_FILE_VERSION BLENDER_VERSION
|
||||
#define BLENDER_FILE_SUBVERSION 3
|
||||
#define BLENDER_FILE_SUBVERSION 4
|
||||
|
||||
/* Minimum Blender version that supports reading file written with the current
|
||||
* version. Older Blender versions will test this and cancel loading the file, showing a warning to
|
||||
|
||||
@@ -10,6 +10,8 @@ struct Mesh;
|
||||
* \ingroup bke
|
||||
*/
|
||||
|
||||
#include "BLI_virtual_array_fwd.hh"
|
||||
|
||||
#include "BKE_attribute_filter.hh"
|
||||
|
||||
namespace blender::bke {
|
||||
@@ -28,6 +30,7 @@ class CurvesGeometry;
|
||||
*/
|
||||
Mesh *curve_to_mesh_sweep(const CurvesGeometry &main,
|
||||
const CurvesGeometry &profile,
|
||||
const VArray<float> &scales,
|
||||
bool fill_caps,
|
||||
const bke::AttributeFilter &attribute_filter = {});
|
||||
/**
|
||||
|
||||
@@ -189,15 +189,15 @@ static void fill_mesh_positions(const int main_point_num,
|
||||
const Span<float3> profile_positions,
|
||||
const Span<float3> tangents,
|
||||
const Span<float3> normals,
|
||||
const Span<float> radii,
|
||||
const Span<float> scales,
|
||||
MutableSpan<float3> mesh_positions)
|
||||
{
|
||||
if (profile_point_num == 1) {
|
||||
for (const int i_ring : IndexRange(main_point_num)) {
|
||||
float4x4 point_matrix = build_point_matrix(
|
||||
main_positions[i_ring], normals[i_ring], tangents[i_ring]);
|
||||
if (!radii.is_empty()) {
|
||||
point_matrix = math::scale(point_matrix, float3(radii[i_ring]));
|
||||
if (!scales.is_empty()) {
|
||||
point_matrix = math::scale(point_matrix, float3(scales[i_ring]));
|
||||
}
|
||||
mesh_positions[i_ring] = math::transform_point(point_matrix, profile_positions.first());
|
||||
}
|
||||
@@ -206,8 +206,8 @@ static void fill_mesh_positions(const int main_point_num,
|
||||
for (const int i_ring : IndexRange(main_point_num)) {
|
||||
float4x4 point_matrix = build_point_matrix(
|
||||
main_positions[i_ring], normals[i_ring], tangents[i_ring]);
|
||||
if (!radii.is_empty()) {
|
||||
point_matrix = math::scale(point_matrix, float3(radii[i_ring]));
|
||||
if (!scales.is_empty()) {
|
||||
point_matrix = math::scale(point_matrix, float3(scales[i_ring]));
|
||||
}
|
||||
|
||||
const int ring_vert_start = i_ring * profile_point_num;
|
||||
@@ -472,6 +472,7 @@ static void foreach_curve_combination(const CurvesInfo &info,
|
||||
|
||||
static void build_mesh_positions(const CurvesInfo &curves_info,
|
||||
const ResultOffsets &offsets,
|
||||
const VArray<float> &scales,
|
||||
Vector<std::byte> &eval_buffer,
|
||||
Mesh &mesh)
|
||||
{
|
||||
@@ -499,9 +500,9 @@ static void build_mesh_positions(const CurvesInfo &curves_info,
|
||||
}
|
||||
const Span<float3> tangents = curves_info.main.evaluated_tangents();
|
||||
const Span<float3> normals = curves_info.main.evaluated_normals();
|
||||
Span<float> radii_eval;
|
||||
if (const GVArray radii = *curves_info.main.attributes().lookup("radius", AttrDomain::Point)) {
|
||||
radii_eval = evaluate_attribute(radii, curves_info.main, eval_buffer).typed<float>();
|
||||
Span<float> eval_scales;
|
||||
if (!scales.is_empty() && scales.get_if_single() != 1.0f) {
|
||||
eval_scales = evaluate_attribute(scales, curves_info.main, eval_buffer).typed<float>();
|
||||
}
|
||||
foreach_curve_combination(curves_info, offsets, [&](const CombinationInfo &info) {
|
||||
fill_mesh_positions(info.main_points.size(),
|
||||
@@ -510,7 +511,7 @@ static void build_mesh_positions(const CurvesInfo &curves_info,
|
||||
profile_positions.slice(info.profile_points),
|
||||
tangents.slice(info.main_points),
|
||||
normals.slice(info.main_points),
|
||||
radii_eval.is_empty() ? radii_eval : radii_eval.slice(info.main_points),
|
||||
eval_scales.is_empty() ? eval_scales : eval_scales.slice(info.main_points),
|
||||
positions.slice(info.vert_range));
|
||||
});
|
||||
}
|
||||
@@ -811,6 +812,7 @@ static void write_sharp_bezier_edges(const CurvesInfo &curves_info,
|
||||
|
||||
Mesh *curve_to_mesh_sweep(const CurvesGeometry &main,
|
||||
const CurvesGeometry &profile,
|
||||
const VArray<float> &scales,
|
||||
const bool fill_caps,
|
||||
const AttributeFilter &attribute_filter)
|
||||
{
|
||||
@@ -871,7 +873,7 @@ Mesh *curve_to_mesh_sweep(const CurvesGeometry &main,
|
||||
/* Make sure curve attributes can be interpolated. */
|
||||
main.ensure_can_interpolate_to_evaluated();
|
||||
|
||||
build_mesh_positions(curves_info, offsets, eval_buffer, *mesh);
|
||||
build_mesh_positions(curves_info, offsets, scales, eval_buffer, *mesh);
|
||||
|
||||
mesh->tag_overlapping_none();
|
||||
if (!offsets.any_single_point_main) {
|
||||
@@ -996,7 +998,7 @@ static CurvesGeometry get_curve_single_vert()
|
||||
Mesh *curve_to_wire_mesh(const CurvesGeometry &curve, const AttributeFilter &attribute_filter)
|
||||
{
|
||||
static const CurvesGeometry vert_curve = get_curve_single_vert();
|
||||
return curve_to_mesh_sweep(curve, vert_curve, false, attribute_filter);
|
||||
return curve_to_mesh_sweep(curve, vert_curve, {}, false, attribute_filter);
|
||||
}
|
||||
|
||||
} // namespace blender::bke
|
||||
|
||||
@@ -3766,6 +3766,83 @@ static void version_geometry_normal_input_node(bNodeTree &ntree)
|
||||
}
|
||||
}
|
||||
|
||||
static void do_version_node_curve_to_mesh_scale_input(bNodeTree *tree)
|
||||
{
|
||||
using namespace blender;
|
||||
Set<bNode *> curve_to_mesh_nodes;
|
||||
LISTBASE_FOREACH (bNode *, node, &tree->nodes) {
|
||||
if (STREQ(node->idname, "GeometryNodeCurveToMesh")) {
|
||||
curve_to_mesh_nodes.add(node);
|
||||
}
|
||||
}
|
||||
|
||||
for (bNode *curve_to_mesh : curve_to_mesh_nodes) {
|
||||
if (bke::node_find_socket(*curve_to_mesh, SOCK_IN, "Scale")) {
|
||||
/* Make versioning idempotent. */
|
||||
continue;
|
||||
}
|
||||
version_node_add_socket_if_not_exist(
|
||||
tree, curve_to_mesh, SOCK_IN, SOCK_FLOAT, PROP_NONE, "Scale", "Scale");
|
||||
|
||||
bNode &named_attribute = version_node_add_empty(*tree, "GeometryNodeInputNamedAttribute");
|
||||
NodeGeometryInputNamedAttribute *named_attribute_storage =
|
||||
MEM_callocN<NodeGeometryInputNamedAttribute>(__func__);
|
||||
named_attribute_storage->data_type = CD_PROP_FLOAT;
|
||||
named_attribute.storage = named_attribute_storage;
|
||||
named_attribute.parent = curve_to_mesh->parent;
|
||||
named_attribute.location[0] = curve_to_mesh->location[0] - 25;
|
||||
named_attribute.location[1] = curve_to_mesh->location[1];
|
||||
named_attribute.flag &= ~NODE_SELECT;
|
||||
|
||||
bNodeSocket *name_input = version_node_add_socket_if_not_exist(
|
||||
tree, &named_attribute, SOCK_IN, SOCK_STRING, PROP_NONE, "Name", "Name");
|
||||
STRNCPY(name_input->default_value_typed<bNodeSocketValueString>()->value, "radius");
|
||||
|
||||
version_node_add_socket_if_not_exist(
|
||||
tree, &named_attribute, SOCK_OUT, SOCK_BOOLEAN, PROP_NONE, "Exists", "Exists");
|
||||
version_node_add_socket_if_not_exist(
|
||||
tree, &named_attribute, SOCK_OUT, SOCK_FLOAT, PROP_NONE, "Attribute", "Attribute");
|
||||
|
||||
bNode &switch_node = version_node_add_empty(*tree, "GeometryNodeSwitch");
|
||||
NodeSwitch *switch_storage = MEM_callocN<NodeSwitch>(__func__);
|
||||
switch_storage->input_type = SOCK_FLOAT;
|
||||
switch_node.storage = switch_storage;
|
||||
switch_node.parent = curve_to_mesh->parent;
|
||||
switch_node.location[0] = curve_to_mesh->location[0] - 25;
|
||||
switch_node.location[1] = curve_to_mesh->location[1];
|
||||
switch_node.flag &= ~NODE_SELECT;
|
||||
|
||||
version_node_add_socket_if_not_exist(
|
||||
tree, &switch_node, SOCK_IN, SOCK_BOOLEAN, PROP_NONE, "Switch", "Switch");
|
||||
bNodeSocket *false_input = version_node_add_socket_if_not_exist(
|
||||
tree, &switch_node, SOCK_IN, SOCK_FLOAT, PROP_NONE, "False", "False");
|
||||
false_input->default_value_typed<bNodeSocketValueFloat>()->value = 1.0f;
|
||||
|
||||
version_node_add_socket_if_not_exist(
|
||||
tree, &switch_node, SOCK_IN, SOCK_FLOAT, PROP_NONE, "True", "True");
|
||||
|
||||
version_node_add_link(*tree,
|
||||
named_attribute,
|
||||
*bke::node_find_socket(named_attribute, SOCK_OUT, "Exists"),
|
||||
switch_node,
|
||||
*bke::node_find_socket(switch_node, SOCK_IN, "Switch"));
|
||||
version_node_add_link(*tree,
|
||||
named_attribute,
|
||||
*bke::node_find_socket(named_attribute, SOCK_OUT, "Attribute"),
|
||||
switch_node,
|
||||
*bke::node_find_socket(switch_node, SOCK_IN, "True"));
|
||||
|
||||
version_node_add_socket_if_not_exist(
|
||||
tree, &switch_node, SOCK_OUT, SOCK_FLOAT, PROP_NONE, "Output", "Output");
|
||||
|
||||
version_node_add_link(*tree,
|
||||
switch_node,
|
||||
*bke::node_find_socket(switch_node, SOCK_OUT, "Output"),
|
||||
*curve_to_mesh,
|
||||
*bke::node_find_socket(*curve_to_mesh, SOCK_IN, "Scale"));
|
||||
}
|
||||
}
|
||||
|
||||
static bool strip_effect_overdrop_to_alphaover(Strip *strip, void * /*user_data*/)
|
||||
{
|
||||
if (strip->type == STRIP_TYPE_OVERDROP_REMOVED) {
|
||||
@@ -5899,6 +5976,15 @@ void blo_do_versions_400(FileData *fd, Library * /*lib*/, Main *bmain)
|
||||
version_sequencer_update_overdrop(bmain);
|
||||
}
|
||||
|
||||
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 405, 4)) {
|
||||
FOREACH_NODETREE_BEGIN (bmain, ntree, id) {
|
||||
if (ntree->type == NTREE_GEOMETRY) {
|
||||
do_version_node_curve_to_mesh_scale_input(ntree);
|
||||
}
|
||||
}
|
||||
FOREACH_NODETREE_END;
|
||||
}
|
||||
|
||||
/* Always run this versioning; meshes are written with the legacy format which always needs to
|
||||
* be converted to the new format on file load. Can be moved to a subversion check in a larger
|
||||
* breaking release. */
|
||||
|
||||
@@ -24,6 +24,8 @@ static void node_declare(NodeDeclarationBuilder &b)
|
||||
b.add_input<decl::Geometry>("Profile Curve")
|
||||
.only_realized_data()
|
||||
.supported_type(GeometryComponent::Type::Curve);
|
||||
b.add_input<decl::Float>("Scale").default_value(1.0f).min(0.0f).field_on({0}).description(
|
||||
"Scale of the profile at each point");
|
||||
b.add_input<decl::Bool>("Fill Caps")
|
||||
.description(
|
||||
"If the profile spline is cyclic, fill the ends of the generated mesh with N-gons");
|
||||
@@ -32,14 +34,22 @@ static void node_declare(NodeDeclarationBuilder &b)
|
||||
|
||||
static Mesh *curve_to_mesh(const bke::CurvesGeometry &curves,
|
||||
const GeometrySet &profile_set,
|
||||
const fn::FieldContext &context,
|
||||
const Field<float> &scale_field,
|
||||
const bool fill_caps,
|
||||
const AttributeFilter &attribute_filter)
|
||||
{
|
||||
Mesh *mesh;
|
||||
if (profile_set.has_curves()) {
|
||||
const Curves *profile_curves = profile_set.get_curves();
|
||||
|
||||
FieldEvaluator evaluator{context, curves.points_num()};
|
||||
evaluator.add(scale_field);
|
||||
evaluator.evaluate();
|
||||
|
||||
const VArray<float> profile_scales = evaluator.get_evaluated<float>(0);
|
||||
mesh = bke::curve_to_mesh_sweep(
|
||||
curves, profile_curves->geometry.wrap(), fill_caps, attribute_filter);
|
||||
curves, profile_curves->geometry.wrap(), profile_scales, fill_caps, attribute_filter);
|
||||
}
|
||||
else {
|
||||
mesh = bke::curve_to_wire_mesh(curves, attribute_filter);
|
||||
@@ -50,6 +60,7 @@ static Mesh *curve_to_mesh(const bke::CurvesGeometry &curves,
|
||||
|
||||
static void grease_pencil_to_mesh(GeometrySet &geometry_set,
|
||||
const GeometrySet &profile_set,
|
||||
const Field<float> &scale_field,
|
||||
const bool fill_caps,
|
||||
const AttributeFilter &attribute_filter)
|
||||
{
|
||||
@@ -64,7 +75,10 @@ static void grease_pencil_to_mesh(GeometrySet &geometry_set,
|
||||
continue;
|
||||
}
|
||||
const bke::CurvesGeometry &curves = drawing->strokes();
|
||||
mesh_by_layer[layer_index] = curve_to_mesh(curves, profile_set, fill_caps, attribute_filter);
|
||||
const bke::GreasePencilLayerFieldContext context{
|
||||
grease_pencil, bke::AttrDomain::Point, layer_index};
|
||||
mesh_by_layer[layer_index] = curve_to_mesh(
|
||||
curves, profile_set, context, scale_field, fill_caps, attribute_filter);
|
||||
}
|
||||
|
||||
if (mesh_by_layer.is_empty()) {
|
||||
@@ -104,6 +118,7 @@ static void node_geo_exec(GeoNodeExecParams params)
|
||||
{
|
||||
GeometrySet curve_set = params.extract_input<GeometrySet>("Curve");
|
||||
GeometrySet profile_set = params.extract_input<GeometrySet>("Profile Curve");
|
||||
const Field<float> scale_field = params.extract_input<Field<float>>("Scale");
|
||||
const bool fill_caps = params.extract_input<bool>("Fill Caps");
|
||||
|
||||
bke::GeometryComponentEditData::remember_deformed_positions_if_necessary(curve_set);
|
||||
@@ -112,7 +127,10 @@ static void node_geo_exec(GeoNodeExecParams params)
|
||||
curve_set.modify_geometry_sets([&](GeometrySet &geometry_set) {
|
||||
if (geometry_set.has_curves()) {
|
||||
const Curves &curves = *geometry_set.get_curves();
|
||||
Mesh *mesh = curve_to_mesh(curves.geometry.wrap(), profile_set, fill_caps, attribute_filter);
|
||||
|
||||
const bke::CurvesFieldContext context{curves, bke::AttrDomain::Point};
|
||||
Mesh *mesh = curve_to_mesh(
|
||||
curves.geometry.wrap(), profile_set, context, scale_field, fill_caps, attribute_filter);
|
||||
if (mesh != nullptr) {
|
||||
mesh->mat = static_cast<Material **>(MEM_dupallocN(curves.mat));
|
||||
mesh->totcol = curves.totcol;
|
||||
@@ -120,7 +138,7 @@ static void node_geo_exec(GeoNodeExecParams params)
|
||||
geometry_set.replace_mesh(mesh);
|
||||
}
|
||||
if (geometry_set.has_grease_pencil()) {
|
||||
grease_pencil_to_mesh(geometry_set, profile_set, fill_caps, attribute_filter);
|
||||
grease_pencil_to_mesh(geometry_set, profile_set, scale_field, fill_caps, attribute_filter);
|
||||
}
|
||||
geometry_set.keep_only_during_modify({GeometryComponent::Type::Mesh});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user