WIP: Geometry Nodes: Curve to mesh "Angle Scale" option #120757

Draft
Hans Goudey wants to merge 7 commits from HooglyBoogly/blender:curve-to-mesh-angle-scale into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
3 changed files with 80 additions and 29 deletions

View File

@ -28,6 +28,7 @@ class AnonymousAttributePropagationInfo;
Mesh *curve_to_mesh_sweep(const CurvesGeometry &main,
const CurvesGeometry &profile,
bool fill_caps,
bool angle_scale,
const AnonymousAttributePropagationInfo &propagation_info);
/**
* Create a loose-edge mesh based on the evaluated path of the curve's splines.

View File

@ -2,10 +2,14 @@
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include <iostream>
#include "BLI_array.hh"
#include "BLI_array_utils.hh"
#include "BLI_math_geom.h"
#include "BLI_math_matrix.hh"
#include "BLI_math_rotation.h"
#include "BLI_math_rotation.hh"
#include "BLI_set.hh"
#include "BLI_task.hh"
@ -173,37 +177,74 @@ static void mark_bezier_vector_edges_sharp(const int profile_point_num,
}
}
static void fill_mesh_positions(const int main_point_num,
const int profile_point_num,
const Span<float3> main_positions,
static float4x4 calc_profile_matrix(const Span<float3> positions,
const Span<float3> tangents,
const Span<float3> normals,
const Span<float> radii,
const bool angle_scale,
const int i)
{
float3x3 matrix = math::from_orthonormal_axes<float3x3>(normals[i], tangents[i]);
if (angle_scale && !ELEM(i, 0, positions.index_range().last())) {
const float3 dir_a = math::normalize(positions[i] - positions[i - 1]);
std::cout << "dir_a: " << dir_a << '\n';
const float3 dir_b = math::normalize(positions[i] - positions[i + 1]);
std::cout << "dir_b: " << dir_b << '\n';
// const float factor = shell_v3v3_normalized_to_dist(dir_a, dir_b);
// const float angle = angle_v3v3v3(positions[i - 1], positions[i], positions[i + 1]);
// std::cout << "angle: " << angle << '\n';
// const float factor = shell_angle_to_dist(angle);
const float dot = math::dot(dir_a, dir_b);
std::cout << "dot: " << dot << '\n';
const float factor = 1.0f / math::sqrt((1.0f - dot) * 0.5f);
std::cout << "factor: " << factor << '\n';
// if (factor != 1.0f) {
const float3 tri_normal = math::normal_tri(positions[i - 1], positions[i], positions[i + 1]);
const float3 normal = math::is_zero(tri_normal) ? normals[i] : tri_normal;
std::cout << "tri_normal: " << tri_normal << '\n';
const float3x3 base = math::from_orthonormal_axes<float3x3>(tangents[i], normal);
std::cout << "base: " << base << '\n';
const float3x3 scale = math::scale(base, float3(1.0f, factor, 1.0f));
std::cout << "scale: " << scale << '\n';
matrix = scale * matrix;
// }
}
const float radius = radii.is_empty() ? 1.0f : radii[i];
if (radius != 1.0f) {
matrix = math::scale(matrix, float3(radius));
}
float4x4 final(matrix);
final.location() = positions[i];
return final;
}
static void fill_mesh_positions(const Span<float3> main_positions,
const Span<float3> profile_positions,
const Span<float3> tangents,
const Span<float3> normals,
const Span<float> radii,
const bool angle_scale,
MutableSpan<float3> mesh_positions)
{
if (profile_point_num == 1) {
for (const int i_ring : IndexRange(main_point_num)) {
float4x4 point_matrix = math::from_orthonormal_axes<float4x4>(
main_positions[i_ring], normals[i_ring], tangents[i_ring]);
if (!radii.is_empty()) {
point_matrix = math::scale(point_matrix, float3(radii[i_ring]));
}
mesh_positions[i_ring] = math::transform_point(point_matrix, profile_positions.first());
if (profile_positions.size() == 1) {
for (const int i_ring : main_positions.index_range()) {
const float4x4 matrix = calc_profile_matrix(
main_positions, tangents, normals, radii, angle_scale, i_ring);
mesh_positions[i_ring] = math::transform_point(matrix, profile_positions.first());
}
}
else {
for (const int i_ring : IndexRange(main_point_num)) {
float4x4 point_matrix = math::from_orthonormal_axes<float4x4>(
main_positions[i_ring], normals[i_ring], tangents[i_ring]);
if (!radii.is_empty()) {
point_matrix = math::scale(point_matrix, float3(radii[i_ring]));
}
for (const int i_ring : main_positions.index_range()) {
const float4x4 matrix = calc_profile_matrix(
main_positions, tangents, normals, radii, angle_scale, i_ring);
const int ring_vert_start = i_ring * profile_point_num;
for (const int i_profile : IndexRange(profile_point_num)) {
const int ring_vert_start = i_ring * profile_positions.size();
for (const int i_profile : profile_positions.index_range()) {
mesh_positions[ring_vert_start + i_profile] = math::transform_point(
point_matrix, profile_positions[i_profile]);
matrix, profile_positions[i_profile]);
}
}
}
@ -462,6 +503,7 @@ static void foreach_curve_combination(const CurvesInfo &info,
static void build_mesh_positions(const CurvesInfo &curves_info,
const ResultOffsets &offsets,
const bool angle_scale,
Vector<std::byte> &eval_buffer,
Mesh &mesh)
{
@ -494,13 +536,12 @@ static void build_mesh_positions(const CurvesInfo &curves_info,
radii_eval = evaluate_attribute(radii, curves_info.main, eval_buffer).typed<float>();
}
foreach_curve_combination(curves_info, offsets, [&](const CombinationInfo &info) {
fill_mesh_positions(info.main_points.size(),
info.profile_points.size(),
main_positions.slice(info.main_points),
fill_mesh_positions(main_positions.slice(info.main_points),
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),
angle_scale,
positions.slice(info.vert_range));
});
}
@ -802,6 +843,7 @@ static void write_sharp_bezier_edges(const CurvesInfo &curves_info,
Mesh *curve_to_mesh_sweep(const CurvesGeometry &main,
const CurvesGeometry &profile,
const bool fill_caps,
const bool angle_scale,
const AnonymousAttributePropagationInfo &propagation_info)
{
const CurvesInfo curves_info = get_curves_info(main, profile);
@ -861,7 +903,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, angle_scale, eval_buffer, *mesh);
mesh->tag_overlapping_none();
if (!offsets.any_single_point_main) {
@ -984,7 +1026,7 @@ Mesh *curve_to_wire_mesh(const CurvesGeometry &curve,
const AnonymousAttributePropagationInfo &propagation_info)
{
static const CurvesGeometry vert_curve = get_curve_single_vert();
return curve_to_mesh_sweep(curve, vert_curve, false, propagation_info);
return curve_to_mesh_sweep(curve, vert_curve, false, false, propagation_info);
}
} // namespace blender::bke

View File

@ -26,19 +26,23 @@ static void node_declare(NodeDeclarationBuilder &b)
b.add_input<decl::Bool>("Fill Caps")
.description(
"If the profile spline is cyclic, fill the ends of the generated mesh with N-gons");
b.add_input<decl::Bool>("Angle Scale")
.description(
"Scale each profile based on the of the neighboring edges to give an even thickness");
b.add_output<decl::Geometry>("Mesh").propagate_all();
}
static Mesh *curve_to_mesh(const bke::CurvesGeometry &curves,
const GeometrySet &profile_set,
const bool fill_caps,
const bool angle_scale,
const AnonymousAttributePropagationInfo &propagation_info)
{
Mesh *mesh;
if (profile_set.has_curves()) {
const Curves *profile_curves = profile_set.get_curves();
mesh = bke::curve_to_mesh_sweep(
curves, profile_curves->geometry.wrap(), fill_caps, propagation_info);
curves, profile_curves->geometry.wrap(), fill_caps, angle_scale, propagation_info);
}
else {
mesh = bke::curve_to_wire_mesh(curves, propagation_info);
@ -50,6 +54,7 @@ static Mesh *curve_to_mesh(const bke::CurvesGeometry &curves,
static void grease_pencil_to_mesh(GeometrySet &geometry_set,
const GeometrySet &profile_set,
const bool fill_caps,
const bool angle_scale,
const AnonymousAttributePropagationInfo &propagation_info)
{
using namespace blender::bke::greasepencil;
@ -63,7 +68,8 @@ 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, propagation_info);
mesh_by_layer[layer_index] = curve_to_mesh(
curves, profile_set, fill_caps, angle_scale, propagation_info);
}
if (mesh_by_layer.is_empty()) {
@ -102,6 +108,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 bool fill_caps = params.extract_input<bool>("Fill Caps");
const bool angle_scale = params.extract_input<bool>("Angle Scale");
bke::GeometryComponentEditData::remember_deformed_positions_if_necessary(curve_set);
const AnonymousAttributePropagationInfo &propagation_info = params.get_output_propagation_info(
@ -110,11 +117,12 @@ 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, propagation_info);
Mesh *mesh = curve_to_mesh(
curves.geometry.wrap(), profile_set, fill_caps, angle_scale, propagation_info);
geometry_set.replace_mesh(mesh);
}
if (geometry_set.has_grease_pencil()) {
grease_pencil_to_mesh(geometry_set, profile_set, fill_caps, propagation_info);
grease_pencil_to_mesh(geometry_set, profile_set, fill_caps, angle_scale, propagation_info);
}
geometry_set.keep_only_during_modify({GeometryComponent::Type::Mesh});
});