Changeset View
Changeset View
Standalone View
Standalone View
source/blender/editors/space_node/node_relationships.cc
| Show First 20 Lines • Show All 1,313 Lines • ▼ Show 20 Lines | |||||
| /** \} */ | /** \} */ | ||||
| /* -------------------------------------------------------------------- */ | /* -------------------------------------------------------------------- */ | ||||
| /** \name Node Link Intersect | /** \name Node Link Intersect | ||||
| * \{ */ | * \{ */ | ||||
| static bool node_links_intersect(bNodeLink &link, const float mcoords[][2], int tot) | static bool node_links_intersect(bNodeLink &link, const float mcoords[][2], int tot) | ||||
| { | { | ||||
| float coord_array[NODE_LINK_RESOL + 1][2]; | std::array<float2, NODE_LINK_RESOL + 1> coord_array; | ||||
| if (node_link_bezier_points(nullptr, nullptr, link, coord_array, NODE_LINK_RESOL)) { | if (node_link_bezier_points(nullptr, nullptr, link, coord_array, NODE_LINK_RESOL)) { | ||||
| for (int i = 0; i < tot - 1; i++) { | for (int i = 0; i < tot - 1; i++) { | ||||
| for (int b = 0; b < NODE_LINK_RESOL; b++) { | for (int b = 0; b < NODE_LINK_RESOL; b++) { | ||||
| if (isect_seg_seg_v2(mcoords[i], mcoords[i + 1], coord_array[b], coord_array[b + 1]) > 0) { | if (isect_seg_seg_v2(mcoords[i], mcoords[i + 1], coord_array[b], coord_array[b + 1]) > 0) { | ||||
| return true; | return true; | ||||
| } | } | ||||
| } | } | ||||
| ▲ Show 20 Lines • Show All 525 Lines • ▼ Show 20 Lines | void NODE_OT_detach(wmOperatorType *ot) | ||||
| /* flags */ | /* flags */ | ||||
| ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; | ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; | ||||
| } | } | ||||
| /** \} */ | /** \} */ | ||||
| /* -------------------------------------------------------------------- */ | /* -------------------------------------------------------------------- */ | ||||
| /** \name Automatic Node Insert on Dragging | |||||
| * \{ */ | |||||
| /* prevent duplicate testing code below */ | |||||
| static bool ed_node_link_conditions(ScrArea *area, | |||||
| bool test, | |||||
| SpaceNode **r_snode, | |||||
| bNode **r_select) | |||||
| { | |||||
| SpaceNode *snode = area ? (SpaceNode *)area->spacedata.first : nullptr; | |||||
| *r_snode = snode; | |||||
| *r_select = nullptr; | |||||
| /* no unlucky accidents */ | |||||
| if (area == nullptr || area->spacetype != SPACE_NODE) { | |||||
| return false; | |||||
| } | |||||
| if (!test) { | |||||
| /* no need to look for a node */ | |||||
| return true; | |||||
| } | |||||
| bNode *node; | |||||
| bNode *select = nullptr; | |||||
| for (node = (bNode *)snode->edittree->nodes.first; node; node = node->next) { | |||||
| if (node->flag & SELECT) { | |||||
| if (select) { | |||||
| break; | |||||
| } | |||||
| select = node; | |||||
| } | |||||
| } | |||||
| /* only one selected */ | |||||
| if (node || select == nullptr) { | |||||
| return false; | |||||
| } | |||||
| /* correct node */ | |||||
| if (BLI_listbase_is_empty(&select->inputs) || BLI_listbase_is_empty(&select->outputs)) { | |||||
| return false; | |||||
| } | |||||
| ARegion *region = BKE_area_find_region_type(area, RGN_TYPE_WINDOW); | |||||
| /* test node for links */ | |||||
| LISTBASE_FOREACH (bNodeLink *, link, &snode->edittree->links) { | |||||
| if (node_link_is_hidden_or_dimmed(region->v2d, *link)) { | |||||
| continue; | |||||
| } | |||||
| if (link->tonode == select || link->fromnode == select) { | |||||
| return false; | |||||
| } | |||||
| } | |||||
| *r_select = select; | |||||
| return true; | |||||
| } | |||||
| } // namespace blender::ed::space_node | |||||
| void ED_node_link_intersect_test(ScrArea *area, int test) | |||||
| { | |||||
| using namespace blender::ed::space_node; | |||||
| bNode *select; | |||||
| SpaceNode *snode; | |||||
| if (!ed_node_link_conditions(area, test, &snode, &select)) { | |||||
| return; | |||||
| } | |||||
| /* clear flags */ | |||||
| LISTBASE_FOREACH (bNodeLink *, link, &snode->edittree->links) { | |||||
| link->flag &= ~NODE_LINKFLAG_HILITE; | |||||
| } | |||||
| if (test == 0) { | |||||
| return; | |||||
| } | |||||
| ARegion *region = BKE_area_find_region_type(area, RGN_TYPE_WINDOW); | |||||
| /* find link to select/highlight */ | |||||
| bNodeLink *selink = nullptr; | |||||
| float dist_best = FLT_MAX; | |||||
| LISTBASE_FOREACH (bNodeLink *, link, &snode->edittree->links) { | |||||
| float coord_array[NODE_LINK_RESOL + 1][2]; | |||||
| if (node_link_is_hidden_or_dimmed(region->v2d, *link)) { | |||||
| continue; | |||||
| } | |||||
| if (node_link_bezier_points(nullptr, nullptr, *link, coord_array, NODE_LINK_RESOL)) { | |||||
| float dist = FLT_MAX; | |||||
| /* loop over link coords to find shortest dist to | |||||
| * upper left node edge of a intersected line segment */ | |||||
| for (int i = 0; i < NODE_LINK_RESOL; i++) { | |||||
| /* Check if the node rectangle intersects the line from this point to next one. */ | |||||
| if (BLI_rctf_isect_segment(&select->totr, coord_array[i], coord_array[i + 1])) { | |||||
| /* store the shortest distance to the upper left edge | |||||
| * of all intersections found so far */ | |||||
| const float node_xy[] = {select->totr.xmin, select->totr.ymax}; | |||||
| /* to be precise coord_array should be clipped by select->totr, | |||||
| * but not done since there's no real noticeable difference */ | |||||
| dist = min_ff( | |||||
| dist_squared_to_line_segment_v2(node_xy, coord_array[i], coord_array[i + 1]), dist); | |||||
| } | |||||
| } | |||||
| /* we want the link with the shortest distance to node center */ | |||||
| if (dist < dist_best) { | |||||
| dist_best = dist; | |||||
| selink = link; | |||||
| } | |||||
| } | |||||
| } | |||||
| if (selink) { | |||||
| selink->flag |= NODE_LINKFLAG_HILITE; | |||||
| } | |||||
| } | |||||
| namespace blender::ed::space_node { | |||||
| /** \} */ | |||||
| /* -------------------------------------------------------------------- */ | |||||
| /** \name Node Insert Offset Operator | /** \name Node Insert Offset Operator | ||||
| * \{ */ | * \{ */ | ||||
| static int get_main_socket_priority(const bNodeSocket *socket) | static int get_main_socket_priority(const bNodeSocket *socket) | ||||
| { | { | ||||
| switch ((eNodeSocketDatatype)socket->type) { | switch ((eNodeSocketDatatype)socket->type) { | ||||
| case __SOCK_MESH: | case __SOCK_MESH: | ||||
| return -1; | return -1; | ||||
| ▲ Show 20 Lines • Show All 397 Lines • ▼ Show 20 Lines | void NODE_OT_insert_offset(wmOperatorType *ot) | ||||
| ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_BLOCKING; | ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_BLOCKING; | ||||
| } | } | ||||
| /** \} */ | /** \} */ | ||||
| } // namespace blender::ed::space_node | } // namespace blender::ed::space_node | ||||
| /* -------------------------------------------------------------------- */ | /* -------------------------------------------------------------------- */ | ||||
| /** \name Note Link Insert | /** \name Attach node to existing link | ||||
| * \{ */ | * \{ */ | ||||
| void ED_node_link_insert(Main *bmain, ScrArea *area) | namespace blender::ed::space_node::link_attach { | ||||
| static void link_attach_highlight_clear(ScrArea *area) | |||||
| { | { | ||||
| using namespace blender::ed::space_node; | if (area == nullptr || area->spacetype != SPACE_NODE) { | ||||
| return; | |||||
| } | |||||
| SpaceNode *snode = static_cast<SpaceNode *>(area->spacedata.first); | |||||
| if (snode->edittree == nullptr) { | |||||
| return; | |||||
| } | |||||
| LISTBASE_FOREACH (bNodeLink *, link, &snode->edittree->links) { | |||||
| link->flag &= ~NODE_LINKFLAG_HILITE; | |||||
| } | |||||
| } | |||||
| bNode *node_to_insert; | struct DragInfo { | ||||
| SpaceNode *snode; | SpaceNode *snode; | ||||
| if (!ed_node_link_conditions(area, true, &snode, &node_to_insert)) { | bNode *left_node; | ||||
| bNode *right_node; | |||||
| }; | |||||
| static std::optional<DragInfo> prepare_drag_info(ScrArea *area) | |||||
| { | |||||
| if (area == nullptr || area->spacetype != SPACE_NODE) { | |||||
| return std::nullopt; | |||||
| } | |||||
| SpaceNode *snode = static_cast<SpaceNode *>(area->spacedata.first); | |||||
| bNodeTree *ntree = snode->edittree; | |||||
| if (ntree == nullptr) { | |||||
| return std::nullopt; | |||||
| } | |||||
| Vector<bNode *> selected_nodes; | |||||
| LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { | |||||
| if (node->flag & SELECT) { | |||||
| selected_nodes.append(node); | |||||
| } | |||||
| } | |||||
| if (selected_nodes.is_empty()) { | |||||
| return std::nullopt; | |||||
| } | |||||
| const std::pair<int, int> default_link_counts{0, 0}; | |||||
| Map<bNode *, std::pair<int, int>> link_counts_map; | |||||
| LISTBASE_FOREACH (bNodeLink *, link, &ntree->links) { | |||||
| link_counts_map.lookup_or_add(link->tonode, default_link_counts).first++; | |||||
| link_counts_map.lookup_or_add(link->fromnode, default_link_counts).second++; | |||||
| } | |||||
| DragInfo drag_info; | |||||
| drag_info.snode = snode; | |||||
| drag_info.left_node = *std::min_element( | |||||
| selected_nodes.begin(), selected_nodes.end(), [&](bNode *a, bNode *b) { | |||||
| if (a->totr.xmin < b->totr.xmin) { | |||||
| return true; | |||||
| } | |||||
| const int num_origins_a = link_counts_map.lookup_default(a, default_link_counts).first; | |||||
| const int num_origins_b = link_counts_map.lookup_default(b, default_link_counts).first; | |||||
| return num_origins_a < num_origins_b; | |||||
| }); | |||||
| drag_info.right_node = *std::max_element( | |||||
| selected_nodes.begin(), selected_nodes.end(), [&](bNode *a, bNode *b) { | |||||
| if (a->totr.xmax < b->totr.xmax) { | |||||
| return true; | |||||
| } | |||||
| const int num_targets_a = link_counts_map.lookup_default(a, default_link_counts).second; | |||||
| const int num_targets_b = link_counts_map.lookup_default(b, default_link_counts).second; | |||||
| return num_targets_a > num_targets_b; | |||||
| }); | |||||
| return drag_info; | |||||
| } | |||||
| static bool node_intersects_line_segment(const bNode &node, const float2 &v1, const float2 &v2) | |||||
| { | |||||
| return BLI_rctf_isect_segment(&node.totr, v1, v2); | |||||
| } | |||||
| static bool link_intersects_nodes(const Span<float2> link_coords, const Span<const bNode *> nodes) | |||||
| { | |||||
| for (const bNode *node : nodes) { | |||||
| bool found_intersection = false; | |||||
| for (const int i : IndexRange(link_coords.size() - 1)) { | |||||
| if (node_intersects_line_segment(*node, link_coords[i], link_coords[i + 1])) { | |||||
| found_intersection = true; | |||||
| break; | |||||
| } | |||||
| } | |||||
| if (!found_intersection) { | |||||
| return false; | |||||
| } | |||||
| } | |||||
| return true; | |||||
| } | |||||
| static float link_weight_for_node(const bNode &node, const float2 &v1, const float2 &v2) | |||||
| { | |||||
| const float2 node_xy{node.totr.xmin, node.totr.ymax}; | |||||
| return -dist_squared_to_line_segment_v2(node_xy, v1, v2); | |||||
| } | |||||
| static void link_attach_highlight(ScrArea *area) | |||||
| { | |||||
| link_attach_highlight_clear(area); | |||||
| std::optional<DragInfo> drag_info_opt = prepare_drag_info(area); | |||||
| if (!drag_info_opt) { | |||||
| return; | |||||
| } | |||||
| DragInfo &drag_info = *drag_info_opt; | |||||
| bNodeTree &ntree = *drag_info.snode->edittree; | |||||
| ARegion *region = BKE_area_find_region_type(area, RGN_TYPE_WINDOW); | |||||
| MultiValueMap<bNodeSocket *, bNodeLink *> link_map; | |||||
| LISTBASE_FOREACH (bNodeLink *, link, &ntree.links) { | |||||
| link_map.add(link->fromsock, link); | |||||
| link_map.add(link->tosock, link); | |||||
| } | |||||
| LISTBASE_FOREACH (bNodeSocket *, socket, &drag_info.right_node->outputs) { | |||||
| if (socket->flag & SOCK_UNAVAIL) { | |||||
| continue; | |||||
| } | |||||
| if (!link_map.lookup(socket).is_empty()) { | |||||
| return; | |||||
| } | |||||
| } | |||||
| bNodeSocket *origin_socket = nullptr; | |||||
| LISTBASE_FOREACH (bNodeSocket *, socket, &drag_info.left_node->inputs) { | |||||
| if (socket->flag & SOCK_UNAVAIL) { | |||||
| continue; | |||||
| } | |||||
| const Span<bNodeLink *> links = link_map.lookup(socket); | |||||
| if (links.is_empty()) { | |||||
| continue; | |||||
| } | |||||
| if (links.size() >= 2) { | |||||
| return; | |||||
| } | |||||
| if (origin_socket != nullptr) { | |||||
| return; | |||||
| } | |||||
| origin_socket = links[0]->fromsock; | |||||
| } | |||||
| bNodeLink *best_link = nullptr; | |||||
| float best_link_weight = -FLT_MAX; | |||||
| LISTBASE_FOREACH (bNodeLink *, link, &drag_info.snode->edittree->links) { | |||||
| if (node_link_is_hidden_or_dimmed(region->v2d, *link)) { | |||||
| continue; | |||||
| } | |||||
| if (origin_socket != nullptr && link->fromsock != origin_socket) { | |||||
| continue; | |||||
| } | |||||
| if (ELEM(link->tonode, drag_info.left_node, drag_info.right_node) || | |||||
| ELEM(link->fromnode, drag_info.left_node, drag_info.right_node)) { | |||||
| continue; | |||||
| } | |||||
| std::array<float2, NODE_LINK_RESOL + 1> link_coords; | |||||
| if (!node_link_bezier_points(nullptr, nullptr, *link, link_coords, NODE_LINK_RESOL)) { | |||||
| continue; | |||||
| } | |||||
| if (!link_intersects_nodes(link_coords, {drag_info.left_node})) { | |||||
| continue; | |||||
| } | |||||
| float max_link_weight = -FLT_MAX; | |||||
| for (const int i : IndexRange(NODE_LINK_RESOL)) { | |||||
| const float link_weight = link_weight_for_node( | |||||
| *drag_info.left_node, link_coords[i], link_coords[i + 1]); | |||||
| max_link_weight = std::max(max_link_weight, link_weight); | |||||
| } | |||||
| if (max_link_weight > best_link_weight) { | |||||
| best_link = link; | |||||
| best_link_weight = max_link_weight; | |||||
| } | |||||
| } | |||||
| if (best_link != nullptr) { | |||||
| best_link->flag |= NODE_LINKFLAG_HILITE; | |||||
| } | |||||
| } | |||||
| static bool link_is_valid(const bNodeTree &ntree, const bNodeSocket &from, const bNodeSocket &to) | |||||
| { | |||||
| return ntree.typeinfo->validate_link(static_cast<eNodeSocketDatatype>(from.type), | |||||
| static_cast<eNodeSocketDatatype>(to.type)); | |||||
| } | |||||
| static void link_attach_highlighted(Main *bmain, ScrArea *area) | |||||
| { | |||||
| std::optional<DragInfo> drag_info_opt = prepare_drag_info(area); | |||||
| if (!drag_info_opt) { | |||||
| return; | return; | ||||
| } | } | ||||
| DragInfo &drag_info = *drag_info_opt; | |||||
| SpaceNode &snode = *drag_info.snode; | |||||
| bNodeTree &ntree = *snode.edittree; | |||||
| /* Find link to insert on. */ | |||||
| bNodeTree &ntree = *snode->edittree; | |||||
| bNodeLink *old_link = nullptr; | bNodeLink *old_link = nullptr; | ||||
| LISTBASE_FOREACH (bNodeLink *, link, &ntree.links) { | LISTBASE_FOREACH (bNodeLink *, link, &ntree.links) { | ||||
| if (link->flag & NODE_LINKFLAG_HILITE) { | if (link->flag & NODE_LINKFLAG_HILITE) { | ||||
| old_link = link; | old_link = link; | ||||
| break; | break; | ||||
| } | } | ||||
| } | } | ||||
| if (old_link == nullptr) { | if (old_link == nullptr) { | ||||
| return; | return; | ||||
| } | } | ||||
| old_link->flag &= ~NODE_LINKFLAG_HILITE; | /* Get pointers because the old link is removed. */ | ||||
| bNode &link_left_node = *old_link->fromnode; | |||||
| bNode &link_right_node = *old_link->tonode; | |||||
| bNodeSocket &link_left_socket = *old_link->fromsock; | |||||
| bNodeSocket &link_right_socket = *old_link->tosock; | |||||
| const int link_multi_input_index = old_link->multi_input_socket_index; | |||||
| bNodeSocket *best_input = get_main_socket(ntree, *node_to_insert, SOCK_IN); | nodeRemLink(&ntree, old_link); | ||||
| bNodeSocket *best_output = get_main_socket(ntree, *node_to_insert, SOCK_OUT); | |||||
| MultiValueMap<bNodeSocket *, bNodeLink *> link_map; | |||||
| LISTBASE_FOREACH (bNodeLink *, link, &ntree.links) { | |||||
| link_map.add(link->fromsock, link); | |||||
| link_map.add(link->tosock, link); | |||||
| } | |||||
| /* Ignore main sockets when the types don't match. */ | bNodeSocket *best_input = nullptr; | ||||
| if (best_input != nullptr && ntree.typeinfo->validate_link != nullptr && | bNodeLink *best_input_link = nullptr; | ||||
| !ntree.typeinfo->validate_link(static_cast<eNodeSocketDatatype>(old_link->fromsock->type), | LISTBASE_FOREACH (bNodeSocket *, socket, &drag_info.left_node->inputs) { | ||||
| static_cast<eNodeSocketDatatype>(best_input->type))) { | if (socket->flag & SOCK_UNAVAIL) { | ||||
| continue; | |||||
| } | |||||
| const Span<bNodeLink *> links = link_map.lookup(socket); | |||||
| if (!links.is_empty()) { | |||||
| best_input = socket; | |||||
| best_input_link = links[0]; | |||||
| break; | |||||
| } | |||||
| } | |||||
| if (best_input == nullptr) { | |||||
| best_input = get_main_socket(ntree, *drag_info.left_node, SOCK_IN); | |||||
| if (best_input != nullptr && !link_is_valid(ntree, link_left_socket, *best_input)) { | |||||
| best_input = nullptr; | best_input = nullptr; | ||||
| } | } | ||||
| } | |||||
| bNodeSocket *best_output = get_main_socket(ntree, *drag_info.right_node, SOCK_OUT); | |||||
| if (best_output != nullptr && ntree.typeinfo->validate_link != nullptr && | if (best_output != nullptr && ntree.typeinfo->validate_link != nullptr && | ||||
| !ntree.typeinfo->validate_link(static_cast<eNodeSocketDatatype>(best_output->type), | !link_is_valid(ntree, *best_output, link_right_socket)) { | ||||
| static_cast<eNodeSocketDatatype>(old_link->tosock->type))) { | |||||
| best_output = nullptr; | best_output = nullptr; | ||||
| } | } | ||||
| bNode *from_node = old_link->fromnode; | if (best_input != nullptr && best_input_link == nullptr) { | ||||
| bNodeSocket *from_socket = old_link->fromsock; | nodeAddLink(&ntree, &link_left_node, &link_left_socket, drag_info.left_node, best_input); | ||||
| bNode *to_node = old_link->tonode; | |||||
| if (best_output != nullptr) { | |||||
| /* Relink the "start" of the existing link to the newly inserted node. */ | |||||
| old_link->fromnode = node_to_insert; | |||||
| old_link->fromsock = best_output; | |||||
| BKE_ntree_update_tag_link_changed(&ntree); | |||||
| } | } | ||||
| else { | if (best_output != nullptr) { | ||||
| nodeRemLink(&ntree, old_link); | bNodeLink *link = nodeAddLink( | ||||
| &ntree, drag_info.right_node, best_output, &link_right_node, &link_right_socket); | |||||
| link->multi_input_socket_index = link_multi_input_index; | |||||
| } | } | ||||
| if (best_input != nullptr) { | if (link_right_socket.flag & SOCK_MULTI_INPUT) { | ||||
| /* Add a new link that connects the node on the left to the newly inserted node. */ | sort_multi_input_socket_links(snode, link_right_node, nullptr, nullptr); | ||||
| nodeAddLink(&ntree, from_node, from_socket, node_to_insert, best_input); | |||||
| } | } | ||||
| /* Set up insert offset data, it needs stuff from here. */ | if ((snode.flag & SNODE_SKIP_INSOFFSET) == 0) { | ||||
| if ((snode->flag & SNODE_SKIP_INSOFFSET) == 0) { | |||||
| NodeInsertOfsData *iofsd = MEM_cnew<NodeInsertOfsData>(__func__); | NodeInsertOfsData *iofsd = MEM_cnew<NodeInsertOfsData>(__func__); | ||||
| iofsd->insert = node_to_insert; | iofsd->insert = drag_info.left_node; | ||||
| iofsd->prev = from_node; | iofsd->prev = &link_left_node; | ||||
| iofsd->next = to_node; | iofsd->next = &link_right_node; | ||||
| snode->runtime->iofsd = iofsd; | snode.runtime->iofsd = iofsd; | ||||
| } | } | ||||
| ED_node_tree_propagate_change(nullptr, bmain, snode->edittree); | ED_node_tree_propagate_change(nullptr, bmain, snode.edittree); | ||||
| } | |||||
| } // namespace blender::ed::space_node::link_attach | |||||
| void ED_node_link_attach_highlight(ScrArea *area) | |||||
| { | |||||
| blender::ed::space_node::link_attach::link_attach_highlight(area); | |||||
| } | |||||
| void ED_node_link_attach_highlight_clear(ScrArea *area) | |||||
| { | |||||
| blender::ed::space_node::link_attach::link_attach_highlight_clear(area); | |||||
| } | |||||
| void ED_node_link_attach_highlighted(Main *bmain, ScrArea *area) | |||||
| { | |||||
| blender::ed::space_node::link_attach::link_attach_highlighted(bmain, area); | |||||
| } | } | ||||
| /** \} */ | /** \} */ | ||||