Files
test2/source/blender/nodes/intern/geometry_nodes_gizmos.cc
Jeroen Bakker 650413e1e6 Fix: Crash when Switching to Node Editor
The [sponza](https://projects.blender.org/blender/blender-benchmarks/src/branch/main/cycles/sponza)
benchmark scene crashes when switching to the compositor tab due to a
missing null pointer check.

Pull Request: https://projects.blender.org/blender/blender/pulls/127230
2024-09-06 14:08:48 +02:00

526 lines
20 KiB
C++

/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_math_base_safe.h"
#include "BLI_math_rotation.hh"
#include "BKE_compute_contexts.hh"
#include "BKE_context.hh"
#include "BKE_modifier.hh"
#include "BKE_node.hh"
#include "BKE_node_runtime.hh"
#include "BKE_node_tree_zones.hh"
#include "BKE_object.hh"
#include "BKE_workspace.hh"
#include "NOD_geometry_nodes_gizmos.hh"
#include "NOD_inverse_eval_params.hh"
#include "NOD_inverse_eval_path.hh"
#include "DNA_modifier_types.h"
#include "DNA_space_types.h"
#include "DNA_windowmanager_types.h"
#include "ED_node.hh"
namespace blender::nodes::gizmos {
bool is_builtin_gizmo_node(const bNode &node)
{
return ELEM(node.type, GEO_NODE_GIZMO_LINEAR, GEO_NODE_GIZMO_DIAL, GEO_NODE_GIZMO_TRANSFORM);
}
/**
* Get the part of a socket value that may be edited with gizmos.
*/
static ie::ElemVariant get_gizmo_socket_elem(const bNode &node, const bNodeSocket &socket)
{
switch (node.type) {
case GEO_NODE_GIZMO_LINEAR: {
return {ie::FloatElem::all()};
}
case GEO_NODE_GIZMO_DIAL: {
return {ie::FloatElem::all()};
}
case GEO_NODE_GIZMO_TRANSFORM: {
const auto &storage = *static_cast<const NodeGeometryTransformGizmo *>(node.storage);
ie::MatrixElem elem;
if (storage.flag & GEO_NODE_TRANSFORM_GIZMO_USE_TRANSLATION_ALL) {
elem.translation = ie::VectorElem::all();
}
if (storage.flag &
(GEO_NODE_TRANSFORM_GIZMO_USE_ROTATION_ALL | GEO_NODE_TRANSFORM_GIZMO_USE_SCALE_ALL))
{
elem.rotation = ie::RotationElem::all();
elem.scale = ie::VectorElem::all();
}
return {elem};
}
}
const eNodeSocketDatatype socket_type = eNodeSocketDatatype(socket.type);
if (std::optional<ie::ElemVariant> elem = ie::get_elem_variant_for_socket_type(socket_type)) {
elem->set_all();
return *elem;
}
BLI_assert_unreachable();
return {};
}
static TreeGizmoPropagation build_tree_gizmo_propagation(bNodeTree &tree)
{
BLI_assert(!tree.has_available_link_cycle());
TreeGizmoPropagation gizmo_propagation;
struct GizmoInput {
const bNodeSocket *gizmo_socket;
/* For multi-input sockets we start propagation at the origin socket. */
const bNodeSocket *propagation_start_socket;
ie::ElemVariant elem;
};
/* Gather all gizmo inputs so that we can find their inverse evaluation targets afterwards. */
Vector<GizmoInput> all_gizmo_inputs;
for (const bNode *node : tree.all_nodes()) {
if (node->is_muted()) {
continue;
}
if (node->is_group()) {
if (!node->id) {
continue;
}
const bNodeTree &group = *reinterpret_cast<const bNodeTree *>(node->id);
if (!group.runtime->gizmo_propagation) {
continue;
}
const TreeGizmoPropagation &group_gizmo_propagation = *group.runtime->gizmo_propagation;
for (const ie::GroupInputElem &group_input_elem :
group_gizmo_propagation.gizmo_inputs_by_group_inputs.keys())
{
const bNodeSocket &input_socket = node->input_socket(group_input_elem.group_input_index);
all_gizmo_inputs.append({&input_socket, &input_socket, group_input_elem.elem});
}
}
if (is_builtin_gizmo_node(*node)) {
gizmo_propagation.gizmo_nodes.append(node);
const bNodeSocket &gizmo_input_socket = node->input_socket(0);
gizmo_propagation.gizmo_endpoint_sockets.add(&gizmo_input_socket);
const ie::ElemVariant elem = get_gizmo_socket_elem(*node, gizmo_input_socket);
for (const bNodeLink *link : gizmo_input_socket.directly_linked_links()) {
if (!link->is_used()) {
continue;
}
all_gizmo_inputs.append({&gizmo_input_socket, link->fromsock, elem});
}
}
}
/* Find the local gizmo targets for all gizmo inputs. */
for (const GizmoInput &gizmo_input : all_gizmo_inputs) {
gizmo_propagation.gizmo_endpoint_sockets.add(gizmo_input.gizmo_socket);
const ie::SocketElem gizmo_input_socket_elem{gizmo_input.gizmo_socket, gizmo_input.elem};
/* The conversion is necessary when e.g. connecting a Rotation directly to the matrix input of
* the Transform Gizmo node. */
const std::optional<ie::ElemVariant> converted_elem = ie::convert_socket_elem(
*gizmo_input.gizmo_socket, *gizmo_input.propagation_start_socket, gizmo_input.elem);
if (!converted_elem) {
continue;
}
const ie::LocalInverseEvalTargets targets = ie::find_local_inverse_eval_targets(
tree, {gizmo_input.propagation_start_socket, *converted_elem});
const bool has_target = !targets.input_sockets.is_empty() ||
!targets.group_inputs.is_empty() || !targets.value_nodes.is_empty();
if (!has_target) {
continue;
}
/* Remember all the gizmo targets for quick lookup later on. */
for (const ie::SocketElem &input_socket : targets.input_sockets) {
gizmo_propagation.gizmo_inputs_by_node_inputs.add(input_socket, gizmo_input_socket_elem);
gizmo_propagation.gizmo_endpoint_sockets.add(input_socket.socket);
}
for (const ie::ValueNodeElem &value_node : targets.value_nodes) {
gizmo_propagation.gizmo_inputs_by_value_nodes.add(value_node, gizmo_input_socket_elem);
gizmo_propagation.gizmo_endpoint_sockets.add(&value_node.node->output_socket(0));
}
for (const ie::GroupInputElem &group_input : targets.group_inputs) {
gizmo_propagation.gizmo_inputs_by_group_inputs.add(group_input, gizmo_input_socket_elem);
for (const bNode *group_input_node : tree.group_input_nodes()) {
gizmo_propagation.gizmo_endpoint_sockets.add(
&group_input_node->output_socket(group_input.group_input_index));
}
}
}
return gizmo_propagation;
}
bool update_tree_gizmo_propagation(bNodeTree &tree)
{
tree.ensure_topology_cache();
if (tree.has_available_link_cycle()) {
const bool changed = tree.runtime->gizmo_propagation.get() != nullptr;
tree.runtime->gizmo_propagation.reset();
return changed;
}
TreeGizmoPropagation new_gizmo_propagation = build_tree_gizmo_propagation(tree);
const bool changed = tree.runtime->gizmo_propagation ?
*tree.runtime->gizmo_propagation != new_gizmo_propagation :
true;
tree.runtime->gizmo_propagation = std::make_unique<TreeGizmoPropagation>(
std::move(new_gizmo_propagation));
return changed;
}
static void foreach_gizmo_for_input(const ie::SocketElem &input_socket,
ComputeContextBuilder &compute_context_builder,
const bNodeTree &tree,
const ForeachGizmoInModifierFn fn);
static void foreach_gizmo_for_group_input(const bNodeTree &tree,
const ie::GroupInputElem &group_input,
ComputeContextBuilder &compute_context_builder,
const ForeachGizmoInModifierFn fn)
{
const TreeGizmoPropagation &gizmo_propagation = *tree.runtime->gizmo_propagation;
for (const ie::SocketElem &gizmo_input :
gizmo_propagation.gizmo_inputs_by_group_inputs.lookup(group_input))
{
foreach_gizmo_for_input(gizmo_input, compute_context_builder, tree, fn);
}
}
static void foreach_gizmo_for_input(const ie::SocketElem &input_socket,
ComputeContextBuilder &compute_context_builder,
const bNodeTree &tree,
const ForeachGizmoInModifierFn fn)
{
const bke::bNodeTreeZones *zones = tree.zones();
if (!zones) {
/* There are invalid zones. */
return;
}
const bNode &node = input_socket.socket->owner_node();
if (zones->get_zone_by_node(node.identifier) != nullptr) {
/* Gizmos in zones are not supported yet. */
return;
}
if (is_builtin_gizmo_node(node)) {
if (node.is_muted()) {
return;
}
/* Found an actual built-in gizmo node. */
fn(*compute_context_builder.current(), node, *input_socket.socket);
return;
}
if (node.is_group()) {
const bNodeTree &group = *reinterpret_cast<const bNodeTree *>(node.id);
group.ensure_topology_cache();
compute_context_builder.push<bke::GroupNodeComputeContext>(node, tree);
foreach_gizmo_for_group_input(
group,
ie::GroupInputElem{input_socket.socket->index(), input_socket.elem},
compute_context_builder,
fn);
compute_context_builder.pop();
}
}
static void foreach_active_gizmo_in_open_node_editor(
const SpaceNode &snode,
const Object *object_filter,
const NodesModifierData *nmd_filter,
ComputeContextBuilder &compute_context_builder,
const ForeachGizmoFn fn)
{
if (snode.nodetree == nullptr) {
return;
}
if (snode.edittree == nullptr || !snode.edittree->runtime->gizmo_propagation) {
return;
}
const std::optional<ed::space_node::ObjectAndModifier> object_and_modifier =
ed::space_node::get_modifier_for_node_editor(snode);
if (!object_and_modifier) {
return;
}
if (object_filter) {
if (object_and_modifier->object != object_filter) {
return;
}
}
if (nmd_filter) {
if (object_and_modifier->nmd != nmd_filter) {
return;
}
}
const Object &object = *object_and_modifier->object;
const NodesModifierData &nmd = *object_and_modifier->nmd;
if (!(nmd.modifier.mode & eModifierMode_Realtime)) {
/* Disabled modifiers can't have gizmos currently. */
return;
}
const ComputeContext *prev_compute_context = compute_context_builder.current();
compute_context_builder.push<bke::ModifierComputeContext>(nmd.modifier.name);
BLI_SCOPED_DEFER([&]() { compute_context_builder.pop_until(prev_compute_context); });
if (!ed::space_node::push_compute_context_for_tree_path(snode, compute_context_builder)) {
return;
}
snode.edittree->ensure_topology_cache();
const TreeGizmoPropagation &gizmo_propagation = *snode.edittree->runtime->gizmo_propagation;
Set<ie::SocketElem> used_gizmo_inputs;
/* Check gizmos on value nodes. */
for (auto &&item : gizmo_propagation.gizmo_inputs_by_value_nodes.items()) {
const bNode &node = *item.key.node;
const bNodeSocket &output_socket = node.output_socket(0);
if ((node.flag & NODE_SELECT) || (output_socket.flag & SOCK_GIZMO_PIN)) {
used_gizmo_inputs.add_multiple(item.value);
continue;
}
for (const ie::SocketElem &socket_elem : item.value) {
if (socket_elem.socket->owner_node().flag & NODE_SELECT) {
used_gizmo_inputs.add(socket_elem);
}
}
}
/* Check gizmos on input sockets. */
for (auto &&item : gizmo_propagation.gizmo_inputs_by_node_inputs.items()) {
const bNodeSocket &socket = *item.key.socket;
const bNode &node = socket.owner_node();
if ((node.flag & NODE_SELECT) || (socket.flag & SOCK_GIZMO_PIN)) {
used_gizmo_inputs.add_multiple(item.value);
continue;
}
for (const ie::SocketElem &socket_elem : item.value) {
if (socket_elem.socket->owner_node().flag & NODE_SELECT) {
used_gizmo_inputs.add(socket_elem);
}
}
}
/* Check built-in gizmo nodes. */
for (const bNode *gizmo_node : gizmo_propagation.gizmo_nodes) {
if (gizmo_node->is_muted()) {
continue;
}
const bNodeSocket &gizmo_input_socket = gizmo_node->input_socket(0);
if ((gizmo_node->flag & NODE_SELECT) || (gizmo_input_socket.flag & SOCK_GIZMO_PIN)) {
used_gizmo_inputs.add(
{&gizmo_input_socket,
*ie::get_elem_variant_for_socket_type(eNodeSocketDatatype(gizmo_input_socket.type))});
}
}
for (const ie::SocketElem &gizmo_input : used_gizmo_inputs) {
foreach_gizmo_for_input(gizmo_input,
compute_context_builder,
*snode.edittree,
[&](const ComputeContext &compute_context,
const bNode &gizmo_node,
const bNodeSocket &gizmo_socket) {
fn(object, nmd, compute_context, gizmo_node, gizmo_socket);
});
}
}
static void foreach_active_gizmo_in_open_editors(const wmWindowManager &wm,
const Object *object_filter,
const NodesModifierData *nmd_filter,
ComputeContextBuilder &compute_context_builder,
const ForeachGizmoFn fn)
{
LISTBASE_FOREACH (const wmWindow *, window, &wm.windows) {
const bScreen *active_screen = BKE_workspace_active_screen_get(window->workspace_hook);
Vector<const bScreen *> screens = {active_screen};
if (ELEM(active_screen->state, SCREENMAXIMIZED, SCREENFULL)) {
const ScrArea *area = static_cast<const ScrArea *>(active_screen->areabase.first);
screens.append(area->full);
}
for (const bScreen *screen : screens) {
LISTBASE_FOREACH (const ScrArea *, area, &screen->areabase) {
const SpaceLink *sl = static_cast<SpaceLink *>(area->spacedata.first);
if (sl == nullptr) {
continue;
}
if (sl->spacetype != SPACE_NODE) {
continue;
}
const SpaceNode &snode = *reinterpret_cast<const SpaceNode *>(sl);
foreach_active_gizmo_in_open_node_editor(
snode, object_filter, nmd_filter, compute_context_builder, fn);
}
}
}
}
static void foreach_active_gizmo_exposed_to_modifier(
const NodesModifierData &nmd,
ComputeContextBuilder &compute_context_builder,
const ForeachGizmoInModifierFn fn)
{
if (!nmd.node_group) {
return;
}
const bNodeTree &tree = *nmd.node_group;
if (!tree.runtime->gizmo_propagation) {
return;
}
compute_context_builder.push<bke::ModifierComputeContext>(nmd.modifier.name);
BLI_SCOPED_DEFER([&]() { compute_context_builder.pop(); });
for (auto &&item : tree.runtime->gizmo_propagation->gizmo_inputs_by_group_inputs.items()) {
for (const ie::SocketElem &socket_elem : item.value) {
foreach_gizmo_for_input(socket_elem, compute_context_builder, tree, fn);
}
}
}
void foreach_active_gizmo_in_modifier(const Object &object,
const NodesModifierData &nmd,
const wmWindowManager &wm,
ComputeContextBuilder &compute_context_builder,
const ForeachGizmoInModifierFn fn)
{
if (!nmd.node_group) {
return;
}
foreach_active_gizmo_in_open_editors(wm,
&object,
&nmd,
compute_context_builder,
[&](const Object &object_with_gizmo,
const NodesModifierData &nmd_with_gizmo,
const ComputeContext &compute_context,
const bNode &gizmo_node,
const bNodeSocket &gizmo_socket) {
BLI_assert(&object == &object_with_gizmo);
BLI_assert(&nmd == &nmd_with_gizmo);
UNUSED_VARS_NDEBUG(object_with_gizmo, nmd_with_gizmo);
fn(compute_context, gizmo_node, gizmo_socket);
});
foreach_active_gizmo_exposed_to_modifier(nmd, compute_context_builder, fn);
}
void foreach_active_gizmo(const bContext &C,
ComputeContextBuilder &compute_context_builder,
const ForeachGizmoFn fn)
{
const wmWindowManager *wm = CTX_wm_manager(&C);
if (!wm) {
return;
}
foreach_active_gizmo_in_open_editors(*wm, nullptr, nullptr, compute_context_builder, fn);
if (const Object *active_object = CTX_data_active_object(&C)) {
if (const ModifierData *md = BKE_object_active_modifier(active_object)) {
if (!(md->mode & eModifierMode_Realtime)) {
return;
}
if (md->type == eModifierType_Nodes) {
const NodesModifierData &nmd = *reinterpret_cast<const NodesModifierData *>(md);
foreach_active_gizmo_exposed_to_modifier(
nmd,
compute_context_builder,
[&](const ComputeContext &compute_context,
const bNode &gizmo_node,
const bNodeSocket &gizmo_socket) {
fn(*active_object, nmd, compute_context, gizmo_node, gizmo_socket);
});
}
}
}
}
void foreach_compute_context_on_gizmo_path(const ComputeContext &gizmo_context,
const bNode &gizmo_node,
const bNodeSocket &gizmo_socket,
FunctionRef<void(const ComputeContext &context)> fn)
{
ie::foreach_element_on_inverse_eval_path(
gizmo_context, {&gizmo_socket, get_gizmo_socket_elem(gizmo_node, gizmo_socket)}, fn, {});
}
void foreach_socket_on_gizmo_path(
const ComputeContext &gizmo_context,
const bNode &gizmo_node,
const bNodeSocket &gizmo_socket,
FunctionRef<void(
const ComputeContext &context, const bNodeSocket &socket, const ie::ElemVariant &elem)> fn)
{
ie::foreach_element_on_inverse_eval_path(
gizmo_context, {&gizmo_socket, get_gizmo_socket_elem(gizmo_node, gizmo_socket)}, {}, fn);
}
ie::ElemVariant get_editable_gizmo_elem(const ComputeContext &gizmo_context,
const bNode &gizmo_node,
const bNodeSocket &gizmo_socket)
{
std::optional<ie::ElemVariant> found_elem = ie::get_elem_variant_for_socket_type(
eNodeSocketDatatype(gizmo_socket.type));
BLI_assert(found_elem.has_value());
ie::foreach_element_on_inverse_eval_path(
gizmo_context,
{&gizmo_socket, get_gizmo_socket_elem(gizmo_node, gizmo_socket)},
{},
[&](const ComputeContext &context, const bNodeSocket &socket, const ie::ElemVariant &elem) {
if (context.hash() == gizmo_context.hash() && &socket == &gizmo_socket) {
found_elem->merge(elem);
}
});
return *found_elem;
}
void apply_gizmo_change(
bContext &C,
Object &object,
NodesModifierData &nmd,
geo_eval_log::GeoModifierLog &eval_log,
const ComputeContext &gizmo_context,
const bNodeSocket &gizmo_socket,
const FunctionRef<void(bke::SocketValueVariant &value)> apply_on_gizmo_value_fn)
{
Vector<ie::SocketToUpdate> sockets_to_update;
const bNodeTree &gizmo_node_tree = gizmo_socket.owner_tree();
geo_eval_log::GeoTreeLog &gizmo_tree_log = eval_log.get_tree_log(gizmo_context.hash());
/* Gather all sockets to update together with their new values. */
for (const bNodeLink *link : gizmo_socket.directly_linked_links()) {
gizmo_node_tree.ensure_topology_cache();
if (!link->is_used()) {
continue;
}
if (link->fromnode->is_dangling_reroute()) {
continue;
}
const std::optional<bke::SocketValueVariant> old_value = ie::get_logged_socket_value(
gizmo_tree_log, *link->fromsock);
if (!old_value) {
continue;
}
const std::optional<bke::SocketValueVariant> old_value_converted =
ie::convert_single_socket_value(*link->fromsock, *link->tosock, *old_value);
if (!old_value_converted) {
continue;
}
bke::SocketValueVariant new_value = *old_value_converted;
apply_on_gizmo_value_fn(new_value);
sockets_to_update.append({&gizmo_context, &gizmo_socket, link, new_value});
}
/* Actually backpropagate the socket values. */
ie::backpropagate_socket_values(C, object, nmd, eval_log, sockets_to_update);
}
} // namespace blender::nodes::gizmos