Geometry Nodes: Add volume grid conversion nodes #118830

Merged
Hans Goudey merged 5 commits from HooglyBoogly/blender:geometry-nodes-grid-conversion-nodes into main 2024-02-29 01:20:36 +01:00
8 changed files with 352 additions and 1 deletions

View File

@ -475,7 +475,8 @@ class NODE_MT_category_GEO_POINT(Menu):
def draw(self, context):
layout = self.layout
node_add_menu.add_node_type(layout, "GeometryNodeDistributePointsInVolume")
if context.preferences.experimental.use_new_volume_nodes:
node_add_menu.add_node_type(layout, "GeometryNodeDistributePointsInGrid")
node_add_menu.add_node_type(layout, "GeometryNodeDistributePointsOnFaces")
layout.separator()
node_add_menu.add_node_type(layout, "GeometryNodePoints")
@ -717,6 +718,8 @@ class NODE_MT_geometry_node_GEO_VOLUME_OPERATIONS(Menu):
def draw(self, context):
layout = self.layout
node_add_menu.add_node_type(layout, "GeometryNodeVolumeToMesh")
if context.preferences.experimental.use_new_volume_nodes:
node_add_menu.add_node_type(layout, "GeometryNodeGridToMesh")
node_add_menu.draw_assets_for_catalog(layout, "Volume/Operations")

View File

@ -1333,6 +1333,8 @@ void BKE_nodetree_remove_layer_n(struct bNodeTree *ntree, struct Scene *scene, i
#define GEO_NODE_MESH_TO_DENSITY_GRID 2126
#define GEO_NODE_MESH_TO_SDF_GRID 2127
#define GEO_NODE_POINTS_TO_SDF_GRID 2128
#define GEO_NODE_GRID_TO_MESH 2129
#define GEO_NODE_DISTRIBUTE_POINTS_IN_GRID 2130
/** \} */

View File

@ -44,6 +44,8 @@ Mesh *volume_to_mesh(const openvdb::GridBase &grid,
float threshold,
float adaptivity);
Mesh *volume_grid_to_mesh(const openvdb::GridBase &grid, float threshold, float adaptivity);
/**
* Convert an OpenVDB volume grid to corresponding mesh data: vertex positions and quad and
* triangle indices.

View File

@ -184,6 +184,13 @@ Mesh *volume_to_mesh(const openvdb::GridBase &grid,
return mesh;
}
Mesh *volume_grid_to_mesh(const openvdb::GridBase &grid,
const float threshold,
const float adaptivity)
{
return volume_to_mesh(grid, {VOLUME_TO_MESH_RESOLUTION_MODE_GRID}, threshold, adaptivity);
}
#endif /* WITH_OPENVDB */
} // namespace blender::bke

View File

@ -326,6 +326,7 @@ DefNode(GeometryNode, GEO_NODE_CURVE_TOPOLOGY_CURVE_OF_POINT, 0, "CURVE_OF_POINT
DefNode(GeometryNode, GEO_NODE_CURVE_TOPOLOGY_POINTS_OF_CURVE, 0, "POINTS_OF_CURVE", PointsOfCurve, "Points of Curve", "Retrieve a point index within a curve")
DefNode(GeometryNode, GEO_NODE_DEFORM_CURVES_ON_SURFACE, 0, "DEFORM_CURVES_ON_SURFACE", DeformCurvesOnSurface, "Deform Curves on Surface", "Translate and rotate curves based on changes between the object's original and evaluated surface mesh")
DefNode(GeometryNode, GEO_NODE_DELETE_GEOMETRY, 0, "DELETE_GEOMETRY", DeleteGeometry, "Delete Geometry", "Remove selected elements of a geometry")
DefNode(GeometryNode, GEO_NODE_DISTRIBUTE_POINTS_IN_GRID, 0, "DISTRIBUTE_POINTS_IN_GRID", DistributePointsInGrid, "Distribute Points in Grid", "Generate points inside a volume grid")
DefNode(GeometryNode, GEO_NODE_DISTRIBUTE_POINTS_IN_VOLUME, 0, "DISTRIBUTE_POINTS_IN_VOLUME", DistributePointsInVolume, "Distribute Points in Volume", "Generate points inside a volume")
DefNode(GeometryNode, GEO_NODE_DISTRIBUTE_POINTS_ON_FACES, def_geo_distribute_points_on_faces, "DISTRIBUTE_POINTS_ON_FACES", DistributePointsOnFaces, "Distribute Points on Faces", "Generate points spread out on the surface of a mesh")
DefNode(GeometryNode, GEO_NODE_DUAL_MESH, 0, "DUAL_MESH", DualMesh, "Dual Mesh", "Convert Faces into vertices and vertices into faces")
@ -341,6 +342,7 @@ DefNode(GeometryNode, GEO_NODE_FILLET_CURVE, 0, "FILLET_CURVE", FilletCurve, "Fi
DefNode(GeometryNode, GEO_NODE_FLIP_FACES, 0, "FLIP_FACES", FlipFaces, "Flip Faces", "Reverse the order of the vertices and edges of selected faces, flipping their normal direction")
DefNode(GeometryNode, GEO_NODE_GEOMETRY_TO_INSTANCE, 0, "GEOMETRY_TO_INSTANCE", GeometryToInstance, "Geometry to Instance", "Convert each input geometry into an instance, which can be much faster than the Join Geometry node when the inputs are large")
DefNode(GeometryNode, GEO_NODE_GET_NAMED_GRID, 0, "GET_NAMED_GRID", GetNamedGrid, "Get Named Grid", "Get volume grid from a volume geometry with the specified name")
DefNode(GeometryNode, GEO_NODE_GRID_TO_MESH, 0, "GRID_TO_MESH", GridToMesh, "Grid to Mesh", "Generate a mesh on the \"surface\" of a volume grid")
DefNode(GeometryNode, GEO_NODE_IMAGE_INFO, 0, "IMAGE_INFO", ImageInfo, "Image Info", "Retrieve information about an image")
DefNode(GeometryNode, GEO_NODE_IMAGE_TEXTURE, def_geo_image_texture, "IMAGE_TEXTURE", ImageTexture, "Image Texture", "Sample values from an image texture")
DefNode(GeometryNode, GEO_NODE_IMAGE, def_geo_image, "IMAGE", InputImage, "Image", "Input image")

View File

@ -64,6 +64,7 @@ set(SRC
nodes/node_geo_curve_trim.cc
nodes/node_geo_deform_curves_on_surface.cc
nodes/node_geo_delete_geometry.cc
nodes/node_geo_distribute_points_in_grid.cc
nodes/node_geo_distribute_points_in_volume.cc
nodes/node_geo_distribute_points_on_faces.cc
nodes/node_geo_dual_mesh.cc
@ -78,6 +79,7 @@ set(SRC
nodes/node_geo_flip_faces.cc
nodes/node_geo_geometry_to_instance.cc
nodes/node_geo_get_named_grid.cc
nodes/node_geo_grid_to_mesh.cc
nodes/node_geo_image.cc
nodes/node_geo_image_info.cc
nodes/node_geo_image_texture.cc

View File

@ -0,0 +1,277 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#ifdef WITH_OPENVDB
# include <openvdb/openvdb.h>
# include <openvdb/tools/Interpolation.h>
# include <openvdb/tools/PointScatter.h>
#endif
#include "DNA_node_types.h"
#include "DNA_pointcloud_types.h"
#include "BKE_pointcloud.hh"
#include "BKE_volume.hh"
#include "BKE_volume_grid.hh"
#include "NOD_rna_define.hh"
#include "UI_interface.hh"
#include "UI_resources.hh"
#include "GEO_randomize.hh"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_distribute_points_in_grid_cc {
enum class DistributeMode {
Random = 0,
Grid = 1,
};
static void node_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Float>("Grid").hide_value();
b.add_input<decl::Float>("Density")
.default_value(1.0f)
.min(0.0f)
.max(100000.0f)
.subtype(PROP_NONE)
.description(
"When combined with each voxel's value, determines the number of points to sample per "
"unit volume");
b.add_input<decl::Int>("Seed").min(-10000).max(10000).description(
"Seed used by the random number generator to generate random points");
b.add_input<decl::Vector>("Spacing")
.default_value({0.3, 0.3, 0.3})
.min(0.0001f)
.subtype(PROP_XYZ)
.description("Spacing between grid points");
b.add_input<decl::Float>("Threshold")
.default_value(0.1f)
.min(0.0f)
.max(FLT_MAX)
.description("Minimum density of a voxel to contain a grid point");
b.add_output<decl::Geometry>("Points").propagate_all();
}
static void node_layout(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr)
{
uiItemR(layout, ptr, "mode", UI_ITEM_NONE, "", ICON_NONE);
}
static void node_init(bNodeTree * /*tree*/, bNode *node)
{
node->custom1 = int16_t(DistributeMode::Random);
}
static void node_update(bNodeTree *ntree, bNode *node)
{
const auto mode = DistributeMode(node->custom1);
bNodeSocket *sock_density = static_cast<bNodeSocket *>(node->inputs.first)->next;
bNodeSocket *sock_seed = sock_density->next;
bNodeSocket *sock_spacing = sock_seed->next;
bNodeSocket *sock_threshold = sock_spacing->next;
bke::nodeSetSocketAvailability(ntree, sock_density, mode == DistributeMode::Random);
bke::nodeSetSocketAvailability(ntree, sock_seed, mode == DistributeMode::Random);
bke::nodeSetSocketAvailability(ntree, sock_spacing, mode == DistributeMode::Grid);
bke::nodeSetSocketAvailability(ntree, sock_threshold, mode == DistributeMode::Grid);
}
#ifdef WITH_OPENVDB
/* Implements the interface required by #openvdb::tools::NonUniformPointScatter. */
class PositionsVDBWrapper {
private:
float3 offset_fix_;
Vector<float3> &vector_;
public:
PositionsVDBWrapper(Vector<float3> &vector, const float3 &offset_fix)
: offset_fix_(offset_fix), vector_(vector)
{
}
PositionsVDBWrapper(const PositionsVDBWrapper &wrapper) = default;
void add(const openvdb::Vec3R &pos)
{
vector_.append(float3(float(pos[0]), float(pos[1]), float(pos[2])) + offset_fix_);
}
};
/* Use #std::mt19937 as a random number generator. It has a very long period and thus there should
* be no visible patterns in the generated points. */
using RNGType = std::mt19937;
/* Non-uniform scatter allows the amount of points to be scaled with the volume's density. */
using NonUniformPointScatterVDB =
openvdb::tools::NonUniformPointScatter<PositionsVDBWrapper, RNGType>;
static void point_scatter_density_random(const openvdb::FloatGrid &grid,
const float density,
const int seed,
Vector<float3> &r_positions)
{
/* Offset points by half a voxel so that grid points are aligned with world grid points. */
const float3 offset_fix = {0.5f * float(grid.voxelSize().x()),
0.5f * float(grid.voxelSize().y()),
0.5f * float(grid.voxelSize().z())};
/* Setup and call into OpenVDB's point scatter API. */
PositionsVDBWrapper vdb_position_wrapper(r_positions, offset_fix);
RNGType random_generator(seed);
NonUniformPointScatterVDB point_scatter(vdb_position_wrapper, density, random_generator);
point_scatter(grid);
}
static void point_scatter_density_grid(const openvdb::FloatGrid &grid,
const float3 spacing,
const float threshold,
Vector<float3> &r_positions)
{
const openvdb::Vec3d half_voxel(0.5, 0.5, 0.5);
const openvdb::Vec3d voxel_spacing(double(spacing.x) / grid.voxelSize().x(),
double(spacing.y) / grid.voxelSize().y(),
double(spacing.z) / grid.voxelSize().z());
/* Abort if spacing is zero. */
const double min_spacing = std::min(voxel_spacing.x(),
std::min(voxel_spacing.y(), voxel_spacing.z()));
if (std::abs(min_spacing) < 0.0001) {
return;
}
/* Iterate through tiles and voxels on the grid. */
for (openvdb::FloatGrid::ValueOnCIter cell = grid.cbeginValueOn(); cell; ++cell) {
/* Check if the cell's value meets the minimum threshold. */
if (cell.getValue() < threshold) {
continue;
}
/* Compute the bounding box of each tile/voxel. */
const openvdb::CoordBBox bbox = cell.getBoundingBox();
const openvdb::Vec3d box_min = bbox.min().asVec3d() - half_voxel;
const openvdb::Vec3d box_max = bbox.max().asVec3d() + half_voxel;
/* Pick a starting point rounded up to the nearest possible point. */
double abs_spacing_x = std::abs(voxel_spacing.x());
double abs_spacing_y = std::abs(voxel_spacing.y());
double abs_spacing_z = std::abs(voxel_spacing.z());
const openvdb::Vec3d start(ceil(box_min.x() / abs_spacing_x) * abs_spacing_x,
ceil(box_min.y() / abs_spacing_y) * abs_spacing_y,
ceil(box_min.z() / abs_spacing_z) * abs_spacing_z);
/* Iterate through all possible points in box. */
for (double x = start.x(); x < box_max.x(); x += abs_spacing_x) {
for (double y = start.y(); y < box_max.y(); y += abs_spacing_y) {
for (double z = start.z(); z < box_max.z(); z += abs_spacing_z) {
/* Transform with grid matrix and add point. */
const openvdb::Vec3d idx_pos(x, y, z);
const openvdb::Vec3d local_pos = grid.indexToWorld(idx_pos + half_voxel);
r_positions.append({float(local_pos.x()), float(local_pos.y()), float(local_pos.z())});
}
}
}
}
}
#endif /* WITH_OPENVDB */
static void node_geo_exec(GeoNodeExecParams params)
{
#ifdef WITH_OPENVDB
const bke::VolumeGrid<float> volume_grid = params.extract_input<bke::VolumeGrid<float>>("Grid");
if (!volume_grid) {
params.set_default_remaining_outputs();
return;
}
bke::VolumeTreeAccessToken tree_token;
const openvdb::GridBase &base_grid = volume_grid.grid(tree_token);
if (!base_grid.isType<openvdb::FloatGrid>()) {
params.set_default_remaining_outputs();
return;
}
const openvdb::FloatGrid &grid = static_cast<const openvdb::FloatGrid &>(base_grid);
const DistributeMode mode = DistributeMode(params.node().custom1);
float density;
int seed;
float3 spacing{0, 0, 0};
float threshold;
if (mode == DistributeMode::Random) {
density = params.extract_input<float>("Density");
seed = params.extract_input<int>("Seed");
}
else if (mode == DistributeMode::Grid) {
spacing = params.extract_input<float3>("Spacing");
threshold = params.extract_input<float>("Threshold");
}
Vector<float3> positions;
switch (mode) {
case DistributeMode::Random:
point_scatter_density_random(grid, density, seed, positions);
break;
case DistributeMode::Grid:
point_scatter_density_grid(grid, spacing, threshold, positions);
break;
}
PointCloud *pointcloud = BKE_pointcloud_new_nomain(positions.size());
pointcloud->positions_for_write().copy_from(positions);
geometry::debug_randomize_point_order(pointcloud);
params.set_output("Points", GeometrySet::from_pointcloud(pointcloud));
#else
node_geo_exec_with_missing_openvdb(params);
#endif
}
static void node_rna(StructRNA *srna)
{
static const EnumPropertyItem mode_items[] = {
{int(DistributeMode::Random),
"DENSITY_RANDOM",
0,
"Random",
"Distribute points randomly inside of the volume"},
{int(DistributeMode::Grid),
"DENSITY_GRID",
0,
"Grid",
"Distribute the points in a grid pattern inside of the volume"},
{0, nullptr, 0, nullptr, nullptr},
};
RNA_def_node_enum(srna,
"mode",
"Distribution Method",
"Method to use for scattering points",
mode_items,
NOD_inline_enum_accessors(custom1),
int(DistributeMode::Random));
}
static void node_register()
{
static bNodeType ntype;
geo_node_type_base(&ntype,
GEO_NODE_DISTRIBUTE_POINTS_IN_GRID,
"Distribute Points in Grid",
NODE_CLASS_GEOMETRY);
ntype.initfunc = node_init;
ntype.updatefunc = node_update;
blender::bke::node_type_size(&ntype, 170, 100, 320);
ntype.declare = node_declare;
ntype.geometry_node_execute = node_geo_exec;
ntype.draw_buttons = node_layout;
nodeRegisterType(&ntype);
node_rna(ntype.rna_ext.srna);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_distribute_points_in_grid_cc

View File

@ -0,0 +1,56 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BKE_material.h"
#include "BKE_volume_grid.hh"
#include "BKE_volume_to_mesh.hh"
#include "GEO_randomize.hh"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_grid_to_mesh_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Float>("Grid").hide_value();
b.add_input<decl::Float>("Threshold")
.default_value(0.1f)
.description("Values larger than the threshold are inside the generated mesh");
b.add_input<decl::Float>("Adaptivity").min(0.0f).max(1.0f).subtype(PROP_FACTOR);
b.add_output<decl::Geometry>("Mesh");
}
static void node_geo_exec(GeoNodeExecParams params)
{
#ifdef WITH_OPENVDB
const bke::VolumeGrid<float> grid = params.extract_input<bke::VolumeGrid<float>>("Grid");
if (!grid) {
params.set_default_remaining_outputs();
return;
}
bke::VolumeTreeAccessToken tree_token;
Mesh *mesh = bke::volume_grid_to_mesh(grid.get().grid(tree_token),
params.extract_input<float>("Threshold"),
params.extract_input<float>("Adaptivity"));
BKE_id_material_eval_ensure_default_slot(&mesh->id);
geometry::debug_randomize_mesh_order(mesh);
params.set_output("Mesh", GeometrySet::from_mesh(mesh));
#else
node_geo_exec_with_missing_openvdb(params);
#endif
}
static void node_register()
{
static bNodeType ntype;
geo_node_type_base(&ntype, GEO_NODE_GRID_TO_MESH, "Grid to Mesh", NODE_CLASS_GEOMETRY);
ntype.declare = node_declare;
ntype.geometry_node_execute = node_geo_exec;
nodeRegisterType(&ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_grid_to_mesh_cc