Files
test2/source/blender/blenkernel/intern/node_socket_value.cc
Hans Goudey 4f372d64d4 Geometry Nodes: Initial very basic list support
This includes a new list structure type and socket shape, a node
to create lists, a node to retrieve values from lists, and a node to
retrieve the length of lists. It also implements multi-function support
so that function nodes work on lists.

There are three nodes included in this PR.
- **List** Creates a list of elements with a given size. The values
  are computed with a field that can use the index as an input.
- **Get List Item** A field node that retrieves an element from a
  a list at a given index. The index input is dynamic, so if the input
  is a list, the output will be a list too.
- **List Length** Just gives the length of a list.

When a function node is used with multiple list inputs, the shorter
lists are repeated to extend it to the length of the longest.

The list nodes and structure type are hidden behind an experimental
feature until we can be sure they're useful for an actual use case.

Pull Request: https://projects.blender.org/blender/blender/pulls/140679
2025-07-24 16:16:40 +02:00

473 lines
13 KiB
C++

/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bke
*/
#include <sstream>
#include "BKE_node.hh"
#include "BKE_node_socket_value.hh"
#include "BKE_volume_grid.hh"
#include "NOD_geometry_nodes_bundle.hh"
#include "NOD_geometry_nodes_closure.hh"
#include "NOD_geometry_nodes_list.hh"
#include "BLI_color.hh"
#include "BLI_math_rotation_types.hh"
#include "BLI_math_vector_types.hh"
#include "FN_field.hh"
namespace blender::bke {
template<typename T, typename U>
static constexpr bool is_single_or_field_or_grid_v = is_same_any_v<T,
U,
fn::Field<U>
#ifdef WITH_OPENVDB
,
VolumeGrid<U>
#endif
>;
/**
* Very fast (compile-time) conversion from a static C++ type to the corresponding socket type.
*/
template<typename T> static std::optional<eNodeSocketDatatype> static_type_to_socket_type()
{
if constexpr (is_single_or_field_or_grid_v<T, int>) {
return SOCK_INT;
}
if constexpr (is_single_or_field_or_grid_v<T, float>) {
return SOCK_FLOAT;
}
if constexpr (is_single_or_field_or_grid_v<T, bool>) {
return SOCK_BOOLEAN;
}
if constexpr (is_single_or_field_or_grid_v<T, float3>) {
return SOCK_VECTOR;
}
if constexpr (is_single_or_field_or_grid_v<T, ColorGeometry4f>) {
return SOCK_RGBA;
}
if constexpr (is_single_or_field_or_grid_v<T, math::Quaternion>) {
return SOCK_ROTATION;
}
if constexpr (is_same_any_v<T, float4x4, fn::Field<float4x4>>) {
return SOCK_MATRIX;
}
if constexpr (is_same_any_v<T, std::string>) {
return SOCK_STRING;
}
if constexpr (is_same_any_v<T, nodes::BundlePtr>) {
return SOCK_BUNDLE;
}
if constexpr (is_same_any_v<T, nodes::ClosurePtr>) {
return SOCK_CLOSURE;
}
return std::nullopt;
}
/**
* Check if a socket type stores the static C++ type.
*/
template<typename T>
static bool static_type_is_base_socket_type(const eNodeSocketDatatype socket_type)
{
switch (socket_type) {
case SOCK_INT:
return std::is_same_v<T, int>;
case SOCK_FLOAT:
return std::is_same_v<T, float>;
case SOCK_BOOLEAN:
return std::is_same_v<T, bool>;
case SOCK_VECTOR:
return std::is_same_v<T, float3>;
case SOCK_RGBA:
return std::is_same_v<T, ColorGeometry4f>;
case SOCK_ROTATION:
return std::is_same_v<T, math::Quaternion>;
case SOCK_MATRIX:
return std::is_same_v<T, float4x4>;
case SOCK_STRING:
return std::is_same_v<T, std::string>;
case SOCK_MENU:
return std::is_same_v<T, int>;
case SOCK_BUNDLE:
return std::is_same_v<T, nodes::BundlePtr>;
case SOCK_CLOSURE:
return std::is_same_v<T, nodes::ClosurePtr>;
case SOCK_CUSTOM:
case SOCK_SHADER:
case SOCK_OBJECT:
case SOCK_IMAGE:
case SOCK_GEOMETRY:
case SOCK_COLLECTION:
case SOCK_TEXTURE:
case SOCK_MATERIAL:
return false;
}
BLI_assert_unreachable();
return false;
}
template<typename T> T SocketValueVariant::extract()
{
if constexpr (std::is_same_v<T, fn::GField>) {
switch (kind_) {
case Kind::Field: {
return std::move(value_.get<fn::GField>());
}
case Kind::Single: {
const GPointer single_value = this->get_single_ptr();
return fn::make_constant_field(*single_value.type(), single_value.get());
}
case Kind::List:
case Kind::Grid: {
const CPPType *cpp_type = socket_type_to_geo_nodes_base_cpp_type(socket_type_);
BLI_assert(cpp_type);
return fn::make_constant_field(*cpp_type, cpp_type->default_value());
}
case Kind::None: {
BLI_assert_unreachable();
break;
}
}
}
else if constexpr (fn::is_field_v<T>) {
BLI_assert(static_type_is_base_socket_type<typename T::base_type>(socket_type_));
return T(this->extract<fn::GField>());
}
else if constexpr (std::is_same_v<T, nodes::ListPtr>) {
if (kind_ != Kind::List) {
return {};
}
return std::move(value_.get<nodes::ListPtr>());
}
#ifdef WITH_OPENVDB
else if constexpr (std::is_same_v<T, GVolumeGrid>) {
switch (kind_) {
case Kind::Grid: {
BLI_assert(value_);
return std::move(value_.get<GVolumeGrid>());
}
case Kind::Single:
case Kind::List:
case Kind::Field: {
const std::optional<VolumeGridType> grid_type = socket_type_to_grid_type(socket_type_);
BLI_assert(grid_type);
return GVolumeGrid(*grid_type);
}
case Kind::None: {
BLI_assert_unreachable();
break;
}
}
}
else if constexpr (is_VolumeGrid_v<T>) {
BLI_assert(static_type_is_base_socket_type<typename T::base_type>(socket_type_));
return this->extract<GVolumeGrid>().typed<typename T::base_type>();
}
#endif
else {
BLI_assert(static_type_is_base_socket_type<T>(socket_type_));
if (kind_ == Kind::Single) {
return std::move(value_.get<T>());
}
if (kind_ == Kind::Field) {
T ret_value;
std::destroy_at(&ret_value);
fn::evaluate_constant_field(value_.get<fn::GField>(), &ret_value);
return ret_value;
}
if (kind_ == Kind::List) {
return {};
}
}
BLI_assert_unreachable();
return T();
}
template<typename T> T SocketValueVariant::get() const
{
/* Simple implementation in terms of #extract for now. This could potentially use a specialized
* implementation at some point, but for now it's unlikely to be a bottleneck. */
SocketValueVariant copied_variant = *this;
return copied_variant.extract<T>();
}
template<typename T> void SocketValueVariant::store_impl(T value)
{
if constexpr (std::is_same_v<T, fn::GField>) {
const std::optional<eNodeSocketDatatype> new_socket_type =
geo_nodes_base_cpp_type_to_socket_type(value.cpp_type());
BLI_assert(new_socket_type);
socket_type_ = *new_socket_type;
kind_ = Kind::Field;
value_.emplace<fn::GField>(std::move(value));
}
else if constexpr (fn::is_field_v<T>) {
/* Always store #Field<T> as #GField. */
this->store_impl<fn::GField>(std::move(value));
}
else if constexpr (std::is_same_v<T, nodes::ListPtr>) {
kind_ = Kind::List;
const std::optional<eNodeSocketDatatype> new_socket_type =
geo_nodes_base_cpp_type_to_socket_type(value->cpp_type());
BLI_assert(new_socket_type);
socket_type_ = *new_socket_type;
value_.emplace<nodes::ListPtr>(std::move(value));
}
#ifdef WITH_OPENVDB
else if constexpr (std::is_same_v<T, GVolumeGrid>) {
BLI_assert(value);
const VolumeGridType volume_grid_type = value->grid_type();
const std::optional<eNodeSocketDatatype> new_socket_type = grid_type_to_socket_type(
volume_grid_type);
BLI_assert(new_socket_type);
socket_type_ = *new_socket_type;
kind_ = Kind::Grid;
value_.emplace<GVolumeGrid>(std::move(value));
}
else if constexpr (is_VolumeGrid_v<T>) {
BLI_assert(value);
this->store_impl<GVolumeGrid>(std::move(value));
}
#endif
else {
const std::optional<eNodeSocketDatatype> new_socket_type = static_type_to_socket_type<T>();
BLI_assert(new_socket_type);
socket_type_ = *new_socket_type;
kind_ = Kind::Single;
value_.emplace<T>(std::move(value));
}
}
void SocketValueVariant::store_single(const eNodeSocketDatatype socket_type, const void *value)
{
kind_ = Kind::Single;
socket_type_ = socket_type;
switch (socket_type) {
case SOCK_FLOAT: {
value_.emplace<float>(*static_cast<const float *>(value));
break;
}
case SOCK_INT: {
value_.emplace<int>(*static_cast<const int *>(value));
break;
}
case SOCK_VECTOR: {
value_.emplace<float3>(*static_cast<const float3 *>(value));
break;
}
case SOCK_BOOLEAN: {
value_.emplace<bool>(*static_cast<const bool *>(value));
break;
}
case SOCK_ROTATION: {
value_.emplace<math::Quaternion>(*static_cast<const math::Quaternion *>(value));
break;
}
case SOCK_MATRIX: {
value_.emplace<float4x4>(*static_cast<const float4x4 *>(value));
break;
}
case SOCK_RGBA: {
value_.emplace<ColorGeometry4f>(*static_cast<const ColorGeometry4f *>(value));
break;
}
case SOCK_STRING: {
value_.emplace<std::string>(*static_cast<const std::string *>(value));
break;
}
case SOCK_BUNDLE: {
value_.emplace<nodes::BundlePtr>(*static_cast<const nodes::BundlePtr *>(value));
break;
}
case SOCK_CLOSURE: {
value_.emplace<nodes::ClosurePtr>(*static_cast<const nodes::ClosurePtr *>(value));
break;
}
default: {
BLI_assert_unreachable();
break;
}
}
}
bool SocketValueVariant::is_context_dependent_field() const
{
if (!value_.is<fn::GField>()) {
return false;
}
const fn::GField &field = value_.get<fn::GField>();
if (!field) {
return false;
}
return field.node().depends_on_input();
}
bool SocketValueVariant::is_volume_grid() const
{
return kind_ == Kind::Grid;
}
bool SocketValueVariant::is_single() const
{
return kind_ == Kind::Single;
}
bool SocketValueVariant::is_list() const
{
return kind_ == Kind::List;
}
void SocketValueVariant::convert_to_single()
{
switch (kind_) {
case Kind::Single: {
/* Nothing to do. */
break;
}
case Kind::Field: {
/* Evaluates the field without inputs to try to get a single value. If the field depends on
* context, the default value is used instead. */
fn::GField field = std::move(value_.get<fn::GField>());
void *buffer = this->allocate_single(socket_type_);
fn::evaluate_constant_field(field, buffer);
break;
}
case Kind::List:
case Kind::Grid: {
/* Can't convert a grid to a single value, so just use the default value of the current
* socket type. */
const CPPType &cpp_type = *socket_type_to_geo_nodes_base_cpp_type(socket_type_);
this->store_single(socket_type_, cpp_type.default_value());
break;
}
case Kind::None: {
BLI_assert_unreachable();
break;
}
}
}
GPointer SocketValueVariant::get_single_ptr() const
{
BLI_assert(kind_ == Kind::Single);
const CPPType *type = socket_type_to_geo_nodes_base_cpp_type(socket_type_);
BLI_assert(type != nullptr);
const void *data = value_.get();
return GPointer(*type, data);
}
GMutablePointer SocketValueVariant::get_single_ptr()
{
const GPointer ptr = const_cast<const SocketValueVariant *>(this)->get_single_ptr();
return GMutablePointer(ptr.type(), const_cast<void *>(ptr.get()));
}
void *SocketValueVariant::allocate_single(const eNodeSocketDatatype socket_type)
{
kind_ = Kind::Single;
socket_type_ = socket_type;
switch (socket_type) {
case SOCK_FLOAT:
return value_.allocate<float>();
case SOCK_INT:
return value_.allocate<int>();
case SOCK_VECTOR:
return value_.allocate<float3>();
case SOCK_BOOLEAN:
return value_.allocate<bool>();
case SOCK_ROTATION:
return value_.allocate<math::Quaternion>();
case SOCK_MATRIX:
return value_.allocate<float4x4>();
case SOCK_RGBA:
return value_.allocate<ColorGeometry4f>();
case SOCK_STRING:
return value_.allocate<std::string>();
case SOCK_MENU:
return value_.allocate<int>();
case SOCK_BUNDLE:
return value_.allocate<nodes::BundlePtr>();
case SOCK_CLOSURE:
return value_.allocate<nodes::ClosurePtr>();
default: {
BLI_assert_unreachable();
return nullptr;
}
}
}
std::ostream &operator<<(std::ostream &stream, const SocketValueVariant &value_variant)
{
SocketValueVariant variant_copy = value_variant;
variant_copy.convert_to_single();
if (value_variant.kind_ == SocketValueVariant::Kind::Single) {
const GPointer value = variant_copy.get_single_ptr();
const CPPType &cpp_type = *value.type();
if (cpp_type.is_printable()) {
std::stringstream ss;
cpp_type.print(value.get(), ss);
stream << ss.str();
return stream;
}
}
stream << "SocketValueVariant";
return stream;
}
bool SocketValueVariant::valid_for_socket(eNodeSocketDatatype socket_type) const
{
if (kind_ == Kind::None) {
return false;
}
if (socket_type == SOCK_MENU) {
return socket_type_ == SOCK_INT;
}
return socket_type_ == socket_type;
}
#define INSTANTIATE(TYPE) \
template TYPE SocketValueVariant::extract(); \
template TYPE SocketValueVariant::get() const; \
template void SocketValueVariant::store_impl(TYPE);
#ifdef WITH_OPENVDB
# define INSTANTIATE_SINGLE_AND_FIELD_AND_GRID(TYPE) \
INSTANTIATE(TYPE) \
INSTANTIATE(fn::Field<TYPE>) \
INSTANTIATE(VolumeGrid<TYPE>)
#else
# define INSTANTIATE_SINGLE_AND_FIELD_AND_GRID(TYPE) \
INSTANTIATE(TYPE) \
INSTANTIATE(fn::Field<TYPE>)
#endif
INSTANTIATE_SINGLE_AND_FIELD_AND_GRID(int)
INSTANTIATE_SINGLE_AND_FIELD_AND_GRID(bool)
INSTANTIATE_SINGLE_AND_FIELD_AND_GRID(float)
INSTANTIATE_SINGLE_AND_FIELD_AND_GRID(blender::float3)
INSTANTIATE_SINGLE_AND_FIELD_AND_GRID(blender::ColorGeometry4f)
INSTANTIATE_SINGLE_AND_FIELD_AND_GRID(blender::math::Quaternion)
INSTANTIATE(std::string)
INSTANTIATE(fn::GField)
INSTANTIATE(blender::nodes::BundlePtr)
INSTANTIATE(blender::nodes::ClosurePtr)
INSTANTIATE(blender::nodes::ListPtr)
INSTANTIATE(float4x4)
INSTANTIATE(fn::Field<float4x4>)
#ifdef WITH_OPENVDB
INSTANTIATE(GVolumeGrid)
#endif
} // namespace blender::bke