Geometry Nodes: add Voxel Index node
This node gives access to the integer coordinates of the the voxel that is currently being evaluated by a field. It can be used together with e.g. the Integer Math and Sample Grid Index node to sample neighboring voxel values. Previously, one could only get the position of the voxel in object space. Since sometimes field are evaluated on tiles of many voxels, just having the voxel coordinates can be misleading. Therefore, this same node also outputs whether it is a tile and the extent of the tile (which is 1 for normal voxels). Pull Request: https://projects.blender.org/blender/blender/pulls/147268
This commit is contained in:
@@ -911,6 +911,7 @@ class NODE_MT_gn_volume_read_base(node_add_menu.NodeMenu):
|
||||
layout = self.layout
|
||||
self.node_operator(layout, "GeometryNodeGetNamedGrid")
|
||||
self.node_operator(layout, "GeometryNodeGridInfo")
|
||||
self.node_operator(layout, "GeometryNodeInputVoxelIndex")
|
||||
|
||||
self.draw_assets_for_catalog(layout, self.menu_path)
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
|
||||
#include "FN_field.hh"
|
||||
|
||||
#include "BLI_math_basis_types.hh"
|
||||
|
||||
#ifdef WITH_OPENVDB
|
||||
|
||||
# include "openvdb_fwd.hh"
|
||||
@@ -27,6 +29,11 @@ class VoxelFieldContext : public fn::FieldContext {
|
||||
GVArray get_varray_for_input(const fn::FieldInput &field_input,
|
||||
const IndexMask &mask,
|
||||
ResourceScope &scope) const override;
|
||||
|
||||
Span<openvdb::Coord> voxels() const
|
||||
{
|
||||
return voxels_;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -45,6 +52,44 @@ class TilesFieldContext : public fn::FieldContext {
|
||||
GVArray get_varray_for_input(const fn::FieldInput &field_input,
|
||||
const IndexMask &mask,
|
||||
ResourceScope &scope) const override;
|
||||
|
||||
Span<openvdb::CoordBBox> tiles() const
|
||||
{
|
||||
return tiles_;
|
||||
}
|
||||
};
|
||||
|
||||
class VoxelCoordinateFieldInput : public fn::FieldInput {
|
||||
private:
|
||||
math::Axis axis_;
|
||||
|
||||
public:
|
||||
VoxelCoordinateFieldInput(math::Axis axis);
|
||||
|
||||
GVArray get_varray_for_context(const fn::FieldContext &context,
|
||||
const IndexMask &mask,
|
||||
ResourceScope &scope) const override;
|
||||
};
|
||||
|
||||
class VoxelExtentFieldInput : public fn::FieldInput {
|
||||
private:
|
||||
math::Axis axis_;
|
||||
|
||||
public:
|
||||
VoxelExtentFieldInput(math::Axis axis);
|
||||
|
||||
GVArray get_varray_for_context(const fn::FieldContext &context,
|
||||
const IndexMask &mask,
|
||||
ResourceScope &scope) const override;
|
||||
};
|
||||
|
||||
class IsTileFieldInput : public fn::FieldInput {
|
||||
public:
|
||||
IsTileFieldInput();
|
||||
|
||||
GVArray get_varray_for_context(const fn::FieldContext &context,
|
||||
const IndexMask &mask,
|
||||
ResourceScope &scope) const override;
|
||||
};
|
||||
|
||||
} // namespace blender::bke
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
#include "BKE_geometry_fields.hh"
|
||||
#include "BKE_volume_grid_fields.hh"
|
||||
|
||||
#include "BLT_translation.hh"
|
||||
|
||||
#ifdef WITH_OPENVDB
|
||||
|
||||
# include <openvdb/math/Transform.h>
|
||||
@@ -18,28 +20,27 @@ VoxelFieldContext::VoxelFieldContext(const openvdb::math::Transform &transform,
|
||||
}
|
||||
|
||||
GVArray VoxelFieldContext::get_varray_for_input(const fn::FieldInput &field_input,
|
||||
const IndexMask & /*mask*/,
|
||||
ResourceScope & /*scope*/) const
|
||||
const IndexMask &mask,
|
||||
ResourceScope &scope) const
|
||||
{
|
||||
const bke::AttributeFieldInput *attribute_field_input =
|
||||
dynamic_cast<const bke::AttributeFieldInput *>(&field_input);
|
||||
if (!attribute_field_input) {
|
||||
return {};
|
||||
}
|
||||
if (attribute_field_input->attribute_name() != "position") {
|
||||
return {};
|
||||
}
|
||||
|
||||
/* Support retrieving voxel positions. */
|
||||
Array<float3> positions(voxels_.size());
|
||||
threading::parallel_for(positions.index_range(), 1024, [&](const IndexRange range) {
|
||||
for (const int64_t i : range) {
|
||||
const openvdb::Coord &voxel = voxels_[i];
|
||||
const openvdb::Vec3d position = transform_.indexToWorld(voxel);
|
||||
positions[i] = float3(position.x(), position.y(), position.z());
|
||||
if (const auto *attribute_field = dynamic_cast<const bke::AttributeFieldInput *>(&field_input)) {
|
||||
if (attribute_field->attribute_name() == "position") {
|
||||
/* Support retrieving voxel positions. */
|
||||
Array<float3> positions(voxels_.size());
|
||||
threading::parallel_for(positions.index_range(), 1024, [&](const IndexRange range) {
|
||||
for (const int64_t i : range) {
|
||||
const openvdb::Coord &voxel = voxels_[i];
|
||||
const openvdb::Vec3d position = transform_.indexToWorld(voxel);
|
||||
positions[i] = float3(position.x(), position.y(), position.z());
|
||||
}
|
||||
});
|
||||
return VArray<float3>::from_container(std::move(positions));
|
||||
}
|
||||
});
|
||||
return VArray<float3>::from_container(std::move(positions));
|
||||
}
|
||||
if (dynamic_cast<const fn::IndexFieldInput *>(&field_input)) {
|
||||
return {};
|
||||
}
|
||||
return field_input.get_varray_for_context(*this, mask, scope);
|
||||
}
|
||||
|
||||
TilesFieldContext::TilesFieldContext(const openvdb::math::Transform &transform,
|
||||
@@ -49,28 +50,89 @@ TilesFieldContext::TilesFieldContext(const openvdb::math::Transform &transform,
|
||||
}
|
||||
|
||||
GVArray TilesFieldContext::get_varray_for_input(const fn::FieldInput &field_input,
|
||||
const IndexMask & /*mask*/,
|
||||
ResourceScope & /*scope*/) const
|
||||
const IndexMask &mask,
|
||||
ResourceScope &scope) const
|
||||
{
|
||||
const bke::AttributeFieldInput *attribute_field_input =
|
||||
dynamic_cast<const bke::AttributeFieldInput *>(&field_input);
|
||||
if (attribute_field_input == nullptr) {
|
||||
return {};
|
||||
}
|
||||
if (attribute_field_input->attribute_name() != "position") {
|
||||
return {};
|
||||
}
|
||||
|
||||
/* Support retrieving tile positions. */
|
||||
Array<float3> positions(tiles_.size());
|
||||
threading::parallel_for(positions.index_range(), 1024, [&](const IndexRange range) {
|
||||
for (const int64_t i : range) {
|
||||
const openvdb::CoordBBox &tile = tiles_[i];
|
||||
const openvdb::Vec3d position = transform_.indexToWorld(tile.getCenter());
|
||||
positions[i] = float3(position.x(), position.y(), position.z());
|
||||
if (const auto *attribute_field = dynamic_cast<const bke::AttributeFieldInput *>(&field_input)) {
|
||||
if (attribute_field->attribute_name() == "position") {
|
||||
/* Support retrieving tile positions. */
|
||||
Array<float3> positions(tiles_.size());
|
||||
threading::parallel_for(positions.index_range(), 1024, [&](const IndexRange range) {
|
||||
for (const int64_t i : range) {
|
||||
const openvdb::CoordBBox &tile = tiles_[i];
|
||||
const openvdb::Vec3d position = transform_.indexToWorld(tile.getCenter());
|
||||
positions[i] = float3(position.x(), position.y(), position.z());
|
||||
}
|
||||
});
|
||||
return VArray<float3>::from_container(std::move(positions));
|
||||
}
|
||||
});
|
||||
return VArray<float3>::from_container(std::move(positions));
|
||||
}
|
||||
if (dynamic_cast<const fn::IndexFieldInput *>(&field_input)) {
|
||||
return {};
|
||||
}
|
||||
return field_input.get_varray_for_context(*this, mask, scope);
|
||||
}
|
||||
|
||||
VoxelCoordinateFieldInput::VoxelCoordinateFieldInput(const math::Axis axis)
|
||||
: fn::FieldInput(CPPType::get<int>(), TIP_("Voxel Coordinate")), axis_(axis)
|
||||
{
|
||||
}
|
||||
|
||||
GVArray VoxelCoordinateFieldInput::get_varray_for_context(const fn::FieldContext &context,
|
||||
const IndexMask &mask,
|
||||
ResourceScope & /*scope*/) const
|
||||
{
|
||||
if (const auto *voxel_context = dynamic_cast<const VoxelFieldContext *>(&context)) {
|
||||
const Span<openvdb::Coord> voxels = voxel_context->voxels();
|
||||
Array<int> result(mask.min_array_size());
|
||||
mask.foreach_index_optimized<int>([&](const int i) { result[i] = voxels[i][axis_.as_int()]; });
|
||||
return VArray<int>::from_container(std::move(result));
|
||||
}
|
||||
if (const auto *tiles_context = dynamic_cast<const TilesFieldContext *>(&context)) {
|
||||
const Span<openvdb::CoordBBox> tiles = tiles_context->tiles();
|
||||
Array<int> result(mask.min_array_size());
|
||||
mask.foreach_index_optimized<int>(
|
||||
[&](const int i) { result[i] = tiles[i].min()[axis_.as_int()]; });
|
||||
return VArray<int>::from_container(std::move(result));
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
VoxelExtentFieldInput::VoxelExtentFieldInput(const math::Axis axis)
|
||||
: fn::FieldInput(CPPType::get<int>(), TIP_("Voxel Extent")), axis_(axis)
|
||||
{
|
||||
}
|
||||
|
||||
GVArray VoxelExtentFieldInput::get_varray_for_context(const fn::FieldContext &context,
|
||||
const IndexMask &mask,
|
||||
ResourceScope & /*scope*/) const
|
||||
{
|
||||
if (dynamic_cast<const VoxelFieldContext *>(&context)) {
|
||||
return VArray<int>::from_single(1, mask.min_array_size());
|
||||
}
|
||||
if (const auto *tiles_context = dynamic_cast<const TilesFieldContext *>(&context)) {
|
||||
const Span<openvdb::CoordBBox> tiles = tiles_context->tiles();
|
||||
Array<int> result(mask.min_array_size());
|
||||
mask.foreach_index_optimized<int>(
|
||||
[&](const int i) { result[i] = tiles[i].dim()[axis_.as_int()]; });
|
||||
return VArray<int>::from_container(std::move(result));
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
IsTileFieldInput::IsTileFieldInput() : fn::FieldInput(CPPType::get<bool>(), TIP_("Is Tile")) {}
|
||||
|
||||
GVArray IsTileFieldInput::get_varray_for_context(const fn::FieldContext &context,
|
||||
const IndexMask &mask,
|
||||
ResourceScope & /*scope*/) const
|
||||
{
|
||||
if (dynamic_cast<const VoxelFieldContext *>(&context)) {
|
||||
return VArray<bool>::from_single(false, mask.min_array_size());
|
||||
}
|
||||
if (dynamic_cast<const TilesFieldContext *>(&context)) {
|
||||
return VArray<bool>::from_single(true, mask.min_array_size());
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace blender::bke
|
||||
|
||||
@@ -10218,6 +10218,7 @@ static void rna_def_nodes(BlenderRNA *brna)
|
||||
define("GeometryNode", "GeometryNodeInputSplineCyclic");
|
||||
define("GeometryNode", "GeometryNodeInputSplineResolution");
|
||||
define("GeometryNode", "GeometryNodeInputTangent");
|
||||
define("GeometryNode", "GeometryNodeInputVoxelIndex");
|
||||
define("GeometryNode", "GeometryNodeInstanceOnPoints");
|
||||
define("GeometryNode", "GeometryNodeInstancesToPoints");
|
||||
define("GeometryNode", "GeometryNodeInstanceTransform");
|
||||
|
||||
@@ -149,6 +149,7 @@ set(SRC
|
||||
nodes/node_geo_input_spline_length.cc
|
||||
nodes/node_geo_input_spline_resolution.cc
|
||||
nodes/node_geo_input_tangent.cc
|
||||
nodes/node_geo_input_voxel_index.cc
|
||||
nodes/node_geo_instance_on_points.cc
|
||||
nodes/node_geo_instances_to_points.cc
|
||||
nodes/node_geo_interpolate_curves.cc
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "node_geometry_util.hh"
|
||||
|
||||
#include "BKE_volume_grid_fields.hh"
|
||||
|
||||
namespace blender::nodes::node_geo_input_voxel_index_cc {
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.use_custom_socket_order();
|
||||
b.allow_any_socket_order();
|
||||
b.add_output<decl::Int>("X").field_source().description(
|
||||
"X coordinate of the voxel in index space, or the minimum X coordinate of a tile");
|
||||
b.add_output<decl::Int>("Y").field_source().description(
|
||||
"Y coordinate of the voxel in index space, or the minimum Y coordinate of a tile");
|
||||
b.add_output<decl::Int>("Z").field_source().description(
|
||||
"Z coordinate of the voxel in index space, or the minimum Z coordinate of a tile");
|
||||
auto &panel = b.add_panel("Tile").default_closed(true);
|
||||
panel.add_output<decl::Bool>("Is Tile").field_source().description(
|
||||
"True if the field is evaluated on a tile, i.e. on multiple voxels at once. If this is "
|
||||
"false, the extent is always 1");
|
||||
panel.add_output<decl::Int>("Extent X")
|
||||
.field_source()
|
||||
.description(
|
||||
"Number of voxels in the X direction of the tile, or 1 if the field is evaluated on a "
|
||||
"voxel");
|
||||
panel.add_output<decl::Int>("Extent Y")
|
||||
.field_source()
|
||||
.description(
|
||||
"Number of voxels in the Y direction of the tile, or 1 if the field is evaluated on a "
|
||||
"voxel");
|
||||
panel.add_output<decl::Int>("Extent Z")
|
||||
.field_source()
|
||||
.description(
|
||||
"Number of voxels in the Z direction of the tile, or 1 if the field is evaluated on a "
|
||||
"voxel");
|
||||
}
|
||||
|
||||
static void node_geo_exec(GeoNodeExecParams params)
|
||||
{
|
||||
#ifdef WITH_OPENVDB
|
||||
if (params.output_is_required("X")) {
|
||||
params.set_output("X",
|
||||
fn::GField(std::make_shared<bke::VoxelCoordinateFieldInput>(math::Axis::X)));
|
||||
}
|
||||
if (params.output_is_required("Y")) {
|
||||
params.set_output("Y",
|
||||
fn::GField(std::make_shared<bke::VoxelCoordinateFieldInput>(math::Axis::Y)));
|
||||
}
|
||||
if (params.output_is_required("Z")) {
|
||||
params.set_output("Z",
|
||||
fn::GField(std::make_shared<bke::VoxelCoordinateFieldInput>(math::Axis::Z)));
|
||||
}
|
||||
if (params.output_is_required("Is Tile")) {
|
||||
params.set_output("Is Tile", fn::GField(std::make_shared<bke::IsTileFieldInput>()));
|
||||
}
|
||||
if (params.output_is_required("Extent X")) {
|
||||
params.set_output("Extent X",
|
||||
fn::GField(std::make_shared<bke::VoxelExtentFieldInput>(math::Axis::X)));
|
||||
}
|
||||
if (params.output_is_required("Extent Y")) {
|
||||
params.set_output("Extent Y",
|
||||
fn::GField(std::make_shared<bke::VoxelExtentFieldInput>(math::Axis::Y)));
|
||||
}
|
||||
if (params.output_is_required("Extent Z")) {
|
||||
params.set_output("Extent Z",
|
||||
fn::GField(std::make_shared<bke::VoxelExtentFieldInput>(math::Axis::Z)));
|
||||
}
|
||||
#else
|
||||
node_geo_exec_with_missing_openvdb(params);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static blender::bke::bNodeType ntype;
|
||||
|
||||
geo_node_type_base(&ntype, "GeometryNodeInputVoxelIndex");
|
||||
ntype.ui_name = "Voxel Index";
|
||||
ntype.ui_description =
|
||||
"Retrieve the integer coordinates of the voxel that the field is evaluated on";
|
||||
ntype.nclass = NODE_CLASS_INPUT;
|
||||
ntype.geometry_node_execute = node_geo_exec;
|
||||
ntype.declare = node_declare;
|
||||
blender::bke::node_register_type(ntype);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_geo_input_voxel_index_cc
|
||||
Reference in New Issue
Block a user