Geometry Nodes: Add volume grid conversion nodes #118830
|
@ -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")
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
/** \} */
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
Loading…
Reference in New Issue