Changeset View
Changeset View
Standalone View
Standalone View
source/blender/nodes/geometry/nodes/node_geo_curve_primitive_arc.cc
- This file was added.
| /* | |||||
| * 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_spline.hh" | |||||
| #include "BLI_math_base_safe.h" | |||||
| #include "UI_interface.h" | |||||
| #include "UI_resources.h" | |||||
| #include "node_geometry_util.hh" | |||||
| #include <numeric> | |||||
| namespace blender::nodes::node_geo_curve_primitive_arc_cc { | |||||
| NODE_STORAGE_FUNCS(NodeGeometryCurvePrimitiveArc) | |||||
| static void node_declare(NodeDeclarationBuilder &b) | |||||
| { | |||||
| b.add_input<decl::Int>(N_("Resolution")) | |||||
| .default_value(16) | |||||
| .min(2) | |||||
| .max(256) | |||||
| .subtype(PROP_UNSIGNED) | |||||
| .description(N_("The number of points on the arc")); | |||||
| b.add_input<decl::Vector>(N_("Start")) | |||||
| .default_value({-1.0f, 0.0f, 0.0f}) | |||||
| .subtype(PROP_TRANSLATION) | |||||
| .description(N_("Position of the first control point")); | |||||
| b.add_input<decl::Vector>(N_("Middle")) | |||||
| .default_value({0.0f, 2.0f, 0.0f}) | |||||
| .subtype(PROP_TRANSLATION) | |||||
| .description(N_("Position of the middle control point")); | |||||
| b.add_input<decl::Vector>(N_("End")) | |||||
| .default_value({1.0f, 0.0f, 0.0f}) | |||||
| .subtype(PROP_TRANSLATION) | |||||
| .description(N_("Position of the last control point")); | |||||
| b.add_input<decl::Float>(N_("Radius")) | |||||
| .default_value(1.0f) | |||||
| .min(0.0f) | |||||
| .subtype(PROP_DISTANCE) | |||||
| .description(N_("Distance of the points from the origin")); | |||||
| b.add_input<decl::Float>(N_("Start Angle")) | |||||
| .default_value(0.0f) | |||||
| .subtype(PROP_ANGLE) | |||||
| .description(N_("Starting angle of the arc")); | |||||
| b.add_input<decl::Float>(N_("Sweep Angle")) | |||||
| .default_value(1.75f * M_PI) | |||||
| .min(-2 * M_PI) | |||||
| .max(2 * M_PI) | |||||
| .subtype(PROP_ANGLE) | |||||
| .description(N_("Length of the arc")); | |||||
| b.add_input<decl::Float>(N_("Offset Angle")) | |||||
| .default_value(0.0f) | |||||
| .subtype(PROP_ANGLE) | |||||
| .description(N_("Offset angle of the arc")); | |||||
| b.add_input<decl::Bool>(N_("Connect Center")) | |||||
| .default_value(false) | |||||
| .description(N_("Connect the arc at the center")); | |||||
| b.add_input<decl::Bool>(N_("Invert Arc")) | |||||
| .default_value(false) | |||||
| .description(N_("Invert and draw opposite arc")); | |||||
| b.add_output<decl::Geometry>(N_("Curve")); | |||||
| b.add_output<decl::Vector>(N_("Center")) | |||||
| .description(N_("The center of the circle described by the three points")) | |||||
| .make_available( | |||||
| [](bNode &node) { node_storage(node).mode = GEO_NODE_CURVE_PRIMITIVE_ARC_TYPE_POINTS; }); | |||||
| b.add_output<decl::Vector>(N_("Normal")) | |||||
| .description(N_("The normal direction of the plane described by the three points, pointing " | |||||
| "towards the positive Z axis")) | |||||
| .make_available( | |||||
| [](bNode &node) { node_storage(node).mode = GEO_NODE_CURVE_PRIMITIVE_ARC_TYPE_POINTS; }); | |||||
| b.add_output<decl::Float>(N_("Radius")) | |||||
HooglyBoogly: Candidates for descriptions:
- `The center of the circle described by the three points`
- `The… | |||||
| .description(N_("The radius of the circle described by the three points")) | |||||
| .make_available( | |||||
| [](bNode &node) { node_storage(node).mode = GEO_NODE_CURVE_PRIMITIVE_ARC_TYPE_POINTS; }); | |||||
| } | |||||
| static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) | |||||
| { | |||||
| uiItemR(layout, ptr, "mode", UI_ITEM_R_EXPAND, nullptr, ICON_NONE); | |||||
| } | |||||
| static void node_init(bNodeTree *UNUSED(tree), bNode *node) | |||||
| { | |||||
| NodeGeometryCurvePrimitiveArc *data = MEM_cnew<NodeGeometryCurvePrimitiveArc>(__func__); | |||||
| data->mode = GEO_NODE_CURVE_PRIMITIVE_ARC_TYPE_RADIUS; | |||||
| node->storage = data; | |||||
| } | |||||
| static void node_update(bNodeTree *ntree, bNode *node) | |||||
| { | |||||
| const NodeGeometryCurvePrimitiveArc &storage = node_storage(*node); | |||||
| const GeometryNodeCurvePrimitiveArcMode mode = (GeometryNodeCurvePrimitiveArcMode)storage.mode; | |||||
| bNodeSocket *start_socket = ((bNodeSocket *)node->inputs.first)->next; | |||||
| bNodeSocket *middle_socket = start_socket->next; | |||||
| bNodeSocket *end_socket = middle_socket->next; | |||||
| bNodeSocket *radius_socket = end_socket->next; | |||||
| bNodeSocket *start_angle_socket = radius_socket->next; | |||||
| bNodeSocket *sweep_angle_socket = start_angle_socket->next; | |||||
| bNodeSocket *offset_angle_socket = sweep_angle_socket->next; | |||||
| bNodeSocket *center_out_socket = ((bNodeSocket *)node->outputs.first)->next; | |||||
| bNodeSocket *normal_out_socket = center_out_socket->next; | |||||
| bNodeSocket *radius_out_socket = normal_out_socket->next; | |||||
| const bool radius_mode = (mode == GEO_NODE_CURVE_PRIMITIVE_ARC_TYPE_RADIUS); | |||||
| const bool points_mode = (mode == GEO_NODE_CURVE_PRIMITIVE_ARC_TYPE_POINTS); | |||||
| nodeSetSocketAvailability(ntree, start_socket, points_mode); | |||||
| nodeSetSocketAvailability(ntree, middle_socket, points_mode); | |||||
| nodeSetSocketAvailability(ntree, end_socket, points_mode); | |||||
| nodeSetSocketAvailability(ntree, radius_socket, radius_mode); | |||||
| nodeSetSocketAvailability(ntree, start_angle_socket, radius_mode); | |||||
| nodeSetSocketAvailability(ntree, sweep_angle_socket, radius_mode); | |||||
| nodeSetSocketAvailability(ntree, offset_angle_socket, points_mode); | |||||
| nodeSetSocketAvailability(ntree, center_out_socket, points_mode); | |||||
| nodeSetSocketAvailability(ntree, normal_out_socket, points_mode); | |||||
| nodeSetSocketAvailability(ntree, radius_out_socket, points_mode); | |||||
| } | |||||
| static float3 rotate_vector_around_axis(const float3 vector, const float3 axis, const float angle) | |||||
| { | |||||
| float3 result = vector; | |||||
| float mat[3][3]; | |||||
| axis_angle_to_mat3(mat, axis, angle); | |||||
| mul_m3_v3(mat, result); | |||||
| return result; | |||||
| } | |||||
| static bool colinear_f3_f3_f3(const float3 p1, const float3 p2, const float3 p3) | |||||
| { | |||||
| const float3 a = math::normalize(p2 - p1); | |||||
| const float3 b = math::normalize(p3 - p1); | |||||
| return (ELEM(a, b, b * -1.0f)); | |||||
| } | |||||
| static std::unique_ptr<CurveEval> create_arc_curve_from_points(const int resolution, | |||||
| const float3 a, | |||||
| const float3 b, | |||||
| const float3 c, | |||||
| float angle_offset, | |||||
| const bool connect_center, | |||||
| const bool invert_arc, | |||||
| float3 &r_center, | |||||
| float3 &r_normal, | |||||
| float &r_radius) | |||||
| { | |||||
| std::unique_ptr<CurveEval> curve = std::make_unique<CurveEval>(); | |||||
| std::unique_ptr<PolySpline> spline = std::make_unique<PolySpline>(); | |||||
| if (connect_center) { | |||||
| spline->resize(resolution + 1); | |||||
| } | |||||
| else { | |||||
| spline->resize(resolution); | |||||
| } | |||||
| const int stepcount = resolution - 1; | |||||
| const int centerpoint = resolution; | |||||
| MutableSpan<float3> positions = spline->positions(); | |||||
| spline->radii().fill(1.0f); | |||||
| spline->tilts().fill(0.0f); | |||||
| const bool is_colinear = colinear_f3_f3_f3(a, b, c); | |||||
| float3 center; | |||||
| float3 normal; | |||||
| float radius; | |||||
| const float3 mid_ac = math::midpoint(a, c); | |||||
| normal_tri_v3(normal, a, c, b); | |||||
| if (is_colinear || a == c || a == b || b == c || resolution == 2) { | |||||
| /* If colinear, generate a point line between points. */ | |||||
| float3 p1, p2; | |||||
| /* Find the two points that are furthest away from each other. */ | |||||
| const float ab = math::distance_squared(a, b); | |||||
| const float ac = math::distance_squared(a, c); | |||||
| const float bc = math::distance_squared(b, c); | |||||
| if (ab > ac && ab > bc) { | |||||
| p1 = a; | |||||
| p2 = b; | |||||
| } | |||||
| else if (bc > ab && bc > ac) { | |||||
| p1 = b; | |||||
| p2 = c; | |||||
| } | |||||
Done Inline ActionsI found this a little hard to follow at first, and my instinct was to separate it into a separate function, but I don't see a great way to make that work. Maybe it deserves a comment like "Find the two points that are furthest away from each other" HooglyBoogly: I found this a little hard to follow at first, and my instinct was to separate it into a… | |||||
| else { | |||||
| p1 = a; | |||||
| p2 = c; | |||||
| } | |||||
| const float step = 1.0f / stepcount; | |||||
| for (const int i : IndexRange(resolution)) { | |||||
| const float factor = step * i; | |||||
| positions[i] = math::interpolate(p1, p2, factor); | |||||
| } | |||||
| center = mid_ac; | |||||
| radius = 0.0f; | |||||
| } | |||||
| else { | |||||
| /* Midpoints of `A->B` and `B->C`. */ | |||||
| const float3 mid_ab = math::midpoint(a, b); | |||||
| const float3 mid_bc = math::midpoint(c, b); | |||||
| /* Normalized vectors of `A->B` and `B->C`. */ | |||||
| const float3 nba = math::normalize(b - a); | |||||
| const float3 ncb = math::normalize(c - b); | |||||
| /* Normal of plane of main 2 segments A->B and `B->C`. */ | |||||
| const float3 nabc = math::normalize(math::cross(nba, ncb)); | |||||
| /* Determine center point from the intersection of 3 planes. */ | |||||
| float plane_1[4], plane_2[4], plane_3[4]; | |||||
| plane_from_point_normal_v3(plane_1, mid_ab, nabc); | |||||
| plane_from_point_normal_v3(plane_2, mid_ab, nba); | |||||
| plane_from_point_normal_v3(plane_3, mid_bc, ncb); | |||||
| /* If the 3 planes do not intersect at one point, just return empty geometry. */ | |||||
| if (!isect_plane_plane_plane_v3(plane_1, plane_2, plane_3, center)) { | |||||
| r_center = mid_ac; | |||||
| r_normal = normal; | |||||
| r_radius = 0.0f; | |||||
| return nullptr; | |||||
| } | |||||
| /* Radial vectors. */ | |||||
| const float3 rad_a = math::normalize(a - center); | |||||
| const float3 rad_b = math::normalize(b - center); | |||||
| const float3 rad_c = math::normalize(c - center); | |||||
| /* Calculate angles. */ | |||||
| radius = math::distance(center, b); | |||||
| float angle_ab = angle_signed_on_axis_v3v3_v3(rad_a, rad_b, normal) + 2.0f * M_PI; | |||||
| float angle_ac = angle_signed_on_axis_v3v3_v3(rad_a, rad_c, normal) + 2.0f * M_PI; | |||||
| float angle = (angle_ac > angle_ab) ? angle_ac : angle_ab; | |||||
| angle -= 2.0f * M_PI; | |||||
| if (invert_arc) { | |||||
| angle = -(2.0f * M_PI - angle); | |||||
| } | |||||
| /* Create arc. */ | |||||
| const float step = angle / stepcount; | |||||
| for (const int i : IndexRange(resolution)) { | |||||
| const float factor = step * i + angle_offset; | |||||
| float3 out = rotate_vector_around_axis(rad_a, -normal, factor); | |||||
| positions[i] = out * radius + center; | |||||
| } | |||||
| } | |||||
| if (connect_center) { | |||||
| spline->set_cyclic(true); | |||||
| positions[centerpoint] = center; | |||||
| } | |||||
| /* Ensure normal is relative to Z-up. */ | |||||
| if (math::dot(float3(0, 0, 1), normal) < 0) { | |||||
| normal = -normal; | |||||
| } | |||||
| curve->add_spline(std::move(spline)); | |||||
| curve->attributes.reallocate(curve->splines().size()); | |||||
| r_center = center; | |||||
| r_radius = radius; | |||||
| r_normal = normal; | |||||
| return curve; | |||||
| } | |||||
| std::unique_ptr<CurveEval> create_arc_curve_from_radius(const int resolution, | |||||
| const float radius, | |||||
| const float start_angle, | |||||
| const float sweep_angle, | |||||
| const bool connect_center, | |||||
| const bool invert_arc) | |||||
| { | |||||
| std::unique_ptr<CurveEval> curve = std::make_unique<CurveEval>(); | |||||
| std::unique_ptr<PolySpline> spline = std::make_unique<PolySpline>(); | |||||
| if (connect_center) { | |||||
| spline->resize(resolution + 1); | |||||
| } | |||||
| else { | |||||
| spline->resize(resolution); | |||||
| } | |||||
| const int stepcount = resolution - 1; | |||||
| const int centerpoint = resolution; | |||||
| MutableSpan<float3> positions = spline->positions(); | |||||
| spline->radii().fill(1.0f); | |||||
| spline->tilts().fill(0.0f); | |||||
| const float sweep = (invert_arc) ? -(2.0f * M_PI - sweep_angle) : sweep_angle; | |||||
| const float theta_step = sweep / float(stepcount); | |||||
| for (const int i : IndexRange(resolution)) { | |||||
| const float theta = theta_step * i + start_angle; | |||||
| const float x = radius * cos(theta); | |||||
| const float y = radius * sin(theta); | |||||
| positions[i] = float3(x, y, 0.0f); | |||||
| } | |||||
| if (connect_center) { | |||||
| spline->set_cyclic(true); | |||||
| positions[centerpoint] = float3(0.0f, 0.0f, 0.0f); | |||||
| } | |||||
| curve->add_spline(std::move(spline)); | |||||
| curve->attributes.reallocate(curve->splines().size()); | |||||
| return curve; | |||||
| } | |||||
Done Inline ActionsUse node_storage function, call the variable storage for consistency HooglyBoogly: Use `node_storage` function, call the variable `storage` for consistency | |||||
| static void node_geo_exec(GeoNodeExecParams params) | |||||
| { | |||||
| const NodeGeometryCurvePrimitiveArc &storage = node_storage(params.node()); | |||||
| const GeometryNodeCurvePrimitiveArcMode mode = (GeometryNodeCurvePrimitiveArcMode)storage.mode; | |||||
| switch (mode) { | |||||
| case GEO_NODE_CURVE_PRIMITIVE_ARC_TYPE_POINTS: { | |||||
| std::unique_ptr<CurveEval> curve; | |||||
| float3 r_center, r_normal; | |||||
| float r_radius; | |||||
| curve = create_arc_curve_from_points(std::max(params.extract_input<int>("Resolution"), 2), | |||||
| params.extract_input<float3>("Start"), | |||||
| params.extract_input<float3>("Middle"), | |||||
| params.extract_input<float3>("End"), | |||||
| params.extract_input<float>("Offset Angle"), | |||||
| params.extract_input<bool>("Connect Center"), | |||||
| params.extract_input<bool>("Invert Arc"), | |||||
| r_center, | |||||
| r_normal, | |||||
| r_radius); | |||||
| params.set_output("Curve", GeometrySet::create_with_curve(curve.release())); | |||||
| params.set_output("Center", r_center); | |||||
| params.set_output("Normal", r_normal); | |||||
| params.set_output("Radius", r_radius); | |||||
| break; | |||||
| } | |||||
| case GEO_NODE_CURVE_PRIMITIVE_ARC_TYPE_RADIUS: { | |||||
| std::unique_ptr<CurveEval> curve; | |||||
| const bool use_circle = false; | |||||
| curve = create_arc_curve_from_radius(std::max(params.extract_input<int>("Resolution"), 2), | |||||
| params.extract_input<float>("Radius"), | |||||
| params.extract_input<float>("Start Angle"), | |||||
| params.extract_input<float>("Sweep Angle"), | |||||
| params.extract_input<bool>("Connect Center"), | |||||
| params.extract_input<bool>("Invert Arc")); | |||||
| params.set_output("Curve", GeometrySet::create_with_curve(curve.release())); | |||||
| break; | |||||
| } | |||||
| } | |||||
| } | |||||
| } // namespace blender::nodes::node_geo_curve_primitive_arc_cc | |||||
| void register_node_type_geo_curve_primitive_arc() | |||||
| { | |||||
| namespace file_ns = blender::nodes::node_geo_curve_primitive_arc_cc; | |||||
| static bNodeType ntype; | |||||
| geo_node_type_base(&ntype, GEO_NODE_CURVE_PRIMITIVE_ARC, "Arc", NODE_CLASS_GEOMETRY); | |||||
| node_type_init(&ntype, file_ns::node_init); | |||||
| node_type_update(&ntype, file_ns::node_update); | |||||
| node_type_storage(&ntype, | |||||
| "NodeGeometryCurvePrimitiveArc", | |||||
| 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); | |||||
| } | |||||
Candidates for descriptions: