Geometry Nodes: Handle multiple grids in the volume to mesh node

In future use cases, a volume can contain many grids that represent the
density information. In this case, it's better if the volume to mesh node
creates a mesh based on all of the grids in the volume.

This is also a benefit to share-ability, since one doesn't have to
specify the grid name in the node. Instead, in the future we can have
a way to split particular grids into separate volumes, if only one
grid should be considered.

The code changes are relatively simple:
 - Move the old volume to mesh node to the legacy folder.
 - Run the volume to mesh node on all instance geometry, like elsewhere.
 - Make the blenkernel's volume to mesh API a bit more specific.

Differential Revision: https://developer.blender.org/D12997
This commit is contained in:
Hans Goudey 2021-10-26 11:25:44 -05:00
parent 7979dff9dc
commit 8ddfdfd2b2
Notes: blender-bot 2023-09-08 04:55:43 +02:00
Referenced by issue #92168, Update volume to mesh node to remove grid name
Referenced by issue #91668, How to deal with multiple volume grids in a geometry
14 changed files with 384 additions and 77 deletions

View File

@ -760,6 +760,7 @@ geometry_node_categories = [
]),
GeometryNodeCategory("GEO_VOLUME", "Volume", items=[
NodeItem("GeometryNodeLegacyPointsToVolume", poll=geometry_nodes_legacy_poll),
NodeItem("GeometryNodeLegacyVolumeToMesh", poll=geometry_nodes_legacy_poll),
NodeItem("GeometryNodeVolumeToMesh"),
]),

View File

@ -1442,7 +1442,7 @@ int ntreeTexExecTree(struct bNodeTree *ntree,
#define GEO_NODE_COLLECTION_INFO 1023
#define GEO_NODE_IS_VIEWPORT 1024
#define GEO_NODE_LEGACY_ATTRIBUTE_PROXIMITY 1025
#define GEO_NODE_VOLUME_TO_MESH 1026
#define GEO_NODE_LEGACY_VOLUME_TO_MESH 1026
#define GEO_NODE_LEGACY_ATTRIBUTE_COMBINE_XYZ 1027
#define GEO_NODE_LEGACY_ATTRIBUTE_SEPARATE_XYZ 1028
#define GEO_NODE_SUBDIVIDE_MESH 1029
@ -1548,6 +1548,7 @@ int ntreeTexExecTree(struct bNodeTree *ntree,
#define GEO_NODE_CURVE_TO_POINTS 1130
#define GEO_NODE_INSTANCES_TO_POINTS 1131
#define GEO_NODE_IMAGE_TEXTURE 1132
#define GEO_NODE_VOLUME_TO_MESH 1133
/** \} */

View File

@ -160,6 +160,7 @@ bool BKE_volume_save(const struct Volume *volume,
#ifdef __cplusplus
# include "BLI_float3.hh"
# include "BLI_float4x4.hh"
# include "BLI_string_ref.hh"
bool BKE_volume_min_max(const Volume *volume, blender::float3 &r_min, blender::float3 &r_max);
@ -167,6 +168,10 @@ bool BKE_volume_min_max(const Volume *volume, blender::float3 &r_min, blender::f
# include <openvdb/openvdb.h>
# include <openvdb/points/PointDataGrid.h>
VolumeGrid *BKE_volume_grid_add_vdb(Volume &volume,
blender::StringRef name,
openvdb::GridBase::Ptr vdb_grid);
bool BKE_volume_grid_bounds(openvdb::GridBase::ConstPtr grid,
blender::float3 &r_min,
blender::float3 &r_max);

View File

@ -14,6 +14,8 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "BLI_span.hh"
#include "DNA_modifier_types.h"
#ifdef WITH_OPENVDB
@ -33,10 +35,40 @@ struct VolumeToMeshResolution {
};
#ifdef WITH_OPENVDB
/**
* The result of converting a volume grid to mesh data, in the format used by the OpenVDB API.
*/
struct OpenVDBMeshData {
std::vector<openvdb::Vec3s> verts;
std::vector<openvdb::Vec3I> tris;
std::vector<openvdb::Vec4I> quads;
bool is_empty() const
{
return verts.empty();
}
};
struct Mesh *volume_to_mesh(const openvdb::GridBase &grid,
const VolumeToMeshResolution &resolution,
const float threshold,
const float adaptivity);
struct OpenVDBMeshData volume_to_mesh_data(const openvdb::GridBase &grid,
const VolumeToMeshResolution &resolution,
const float threshold,
const float adaptivity);
void fill_mesh_from_openvdb_data(const Span<openvdb::Vec3s> vdb_verts,
const Span<openvdb::Vec3I> vdb_tris,
const Span<openvdb::Vec4I> vdb_quads,
const int vert_offset,
const int poly_offset,
const int loop_offset,
MutableSpan<MVert> verts,
MutableSpan<MPoly> polys,
MutableSpan<MLoop> loops);
#endif
} // namespace blender::bke

View File

@ -5753,6 +5753,7 @@ static void registerGeometryNodes()
register_node_type_geo_legacy_select_by_handle_type();
register_node_type_geo_legacy_select_by_material();
register_node_type_geo_legacy_subdivision_surface();
register_node_type_geo_legacy_volume_to_mesh();
register_node_type_geo_align_rotation_to_vector();
register_node_type_geo_attribute_capture();

View File

@ -36,6 +36,7 @@
#include "BLI_math.h"
#include "BLI_path_util.h"
#include "BLI_string.h"
#include "BLI_string_ref.hh"
#include "BLI_task.hh"
#include "BLI_utildefines.h"
@ -71,6 +72,7 @@ static CLG_LogRef LOG = {"bke.volume"};
using blender::float3;
using blender::float4x4;
using blender::IndexRange;
using blender::StringRef;
#ifdef WITH_OPENVDB
# include <atomic>
@ -1451,6 +1453,21 @@ VolumeGrid *BKE_volume_grid_add(Volume *volume, const char *name, VolumeGridType
#endif
}
#ifdef WITH_OPENVDB
VolumeGrid *BKE_volume_grid_add_vdb(Volume &volume,
const StringRef name,
openvdb::GridBase::Ptr vdb_grid)
{
VolumeGridVector &grids = *volume.runtime.grids;
BLI_assert(BKE_volume_grid_find_for_read(&volume, name.data()) == nullptr);
BLI_assert(BKE_volume_grid_type_openvdb(*vdb_grid) != VOLUME_GRID_UNKNOWN);
vdb_grid->setName(name);
grids.emplace_back(vdb_grid);
return &grids.back();
}
#endif
void BKE_volume_grid_remove(Volume *volume, VolumeGrid *grid)
{
#ifdef WITH_OPENVDB

View File

@ -121,46 +121,66 @@ struct VolumeToMeshOp {
}
};
static Mesh *new_mesh_from_openvdb_data(Span<openvdb::Vec3s> verts,
Span<openvdb::Vec3I> tris,
Span<openvdb::Vec4I> quads)
/**
* Convert mesh data from the format provided by OpenVDB into Blender's #Mesh data structure.
* This can be used to add mesh data from a grid into an existing mesh rather than merging multiple
* meshes later on.
*/
void fill_mesh_from_openvdb_data(const Span<openvdb::Vec3s> vdb_verts,
const Span<openvdb::Vec3I> vdb_tris,
const Span<openvdb::Vec4I> vdb_quads,
const int vert_offset,
const int poly_offset,
const int loop_offset,
MutableSpan<MVert> verts,
MutableSpan<MPoly> polys,
MutableSpan<MLoop> loops)
{
const int tot_loops = 3 * tris.size() + 4 * quads.size();
const int tot_polys = tris.size() + quads.size();
Mesh *mesh = BKE_mesh_new_nomain(verts.size(), 0, 0, tot_loops, tot_polys);
/* Write vertices. */
for (const int i : verts.index_range()) {
const blender::float3 co = blender::float3(verts[i].asV());
copy_v3_v3(mesh->mvert[i].co, co);
for (const int i : vdb_verts.index_range()) {
const blender::float3 co = blender::float3(vdb_verts[i].asV());
copy_v3_v3(verts[vert_offset + i].co, co);
}
/* Write triangles. */
for (const int i : tris.index_range()) {
mesh->mpoly[i].loopstart = 3 * i;
mesh->mpoly[i].totloop = 3;
for (const int i : vdb_tris.index_range()) {
polys[poly_offset + i].loopstart = loop_offset + 3 * i;
polys[poly_offset + i].totloop = 3;
for (int j = 0; j < 3; j++) {
/* Reverse vertex order to get correct normals. */
mesh->mloop[3 * i + j].v = tris[i][2 - j];
loops[loop_offset + 3 * i + j].v = vert_offset + vdb_tris[i][2 - j];
}
}
/* Write quads. */
const int poly_offset = tris.size();
const int loop_offset = tris.size() * 3;
for (const int i : quads.index_range()) {
mesh->mpoly[poly_offset + i].loopstart = loop_offset + 4 * i;
mesh->mpoly[poly_offset + i].totloop = 4;
const int quad_offset = poly_offset + vdb_tris.size();
const int quad_loop_offset = loop_offset + vdb_tris.size() * 3;
for (const int i : vdb_quads.index_range()) {
polys[quad_offset + i].loopstart = quad_loop_offset + 4 * i;
polys[quad_offset + i].totloop = 4;
for (int j = 0; j < 4; j++) {
/* Reverse vertex order to get correct normals. */
mesh->mloop[loop_offset + 4 * i + j].v = quads[i][3 - j];
loops[quad_loop_offset + 4 * i + j].v = vert_offset + vdb_quads[i][3 - j];
}
}
}
BKE_mesh_calc_edges(mesh, false, false);
BKE_mesh_normals_tag_dirty(mesh);
return mesh;
/**
* Convert an OpenVDB volume grid to corresponding mesh data: vertex positions and quad and
* triangle indices.
*/
bke::OpenVDBMeshData volume_to_mesh_data(const openvdb::GridBase &grid,
const VolumeToMeshResolution &resolution,
const float threshold,
const float adaptivity)
{
const VolumeGridType grid_type = BKE_volume_grid_type_openvdb(grid);
VolumeToMeshOp to_mesh_op{grid, resolution, threshold, adaptivity};
if (!BKE_volume_grid_type_operation(grid_type, to_mesh_op)) {
return {};
}
return {std::move(to_mesh_op.verts), std::move(to_mesh_op.tris), std::move(to_mesh_op.quads)};
}
Mesh *volume_to_mesh(const openvdb::GridBase &grid,
@ -168,14 +188,27 @@ Mesh *volume_to_mesh(const openvdb::GridBase &grid,
const float threshold,
const float adaptivity)
{
const VolumeGridType grid_type = BKE_volume_grid_type_openvdb(grid);
const bke::OpenVDBMeshData mesh_data = volume_to_mesh_data(
grid, resolution, threshold, adaptivity);
VolumeToMeshOp to_mesh_op{grid, resolution, threshold, adaptivity};
if (!BKE_volume_grid_type_operation(grid_type, to_mesh_op)) {
return nullptr;
}
const int tot_loops = 3 * mesh_data.tris.size() + 4 * mesh_data.quads.size();
const int tot_polys = mesh_data.tris.size() + mesh_data.quads.size();
Mesh *mesh = BKE_mesh_new_nomain(mesh_data.verts.size(), 0, 0, tot_loops, tot_polys);
return new_mesh_from_openvdb_data(to_mesh_op.verts, to_mesh_op.tris, to_mesh_op.quads);
fill_mesh_from_openvdb_data(mesh_data.verts,
mesh_data.tris,
mesh_data.quads,
0,
0,
0,
{mesh->mvert, mesh->totvert},
{mesh->mpoly, mesh->totpoly},
{mesh->mloop, mesh->totloop});
BKE_mesh_calc_edges(mesh, false, false);
BKE_mesh_normals_tag_dirty(mesh);
return mesh;
}
#endif /* WITH_OPENVDB */

View File

@ -1985,7 +1985,7 @@ void blo_do_versions_290(FileData *fd, Library *UNUSED(lib), Main *bmain)
if (!MAIN_VERSION_ATLEAST(bmain, 293, 18)) {
FOREACH_NODETREE_BEGIN (bmain, ntree, id) {
if (ntree->type == NTREE_GEOMETRY) {
version_node_socket_name(ntree, GEO_NODE_VOLUME_TO_MESH, "Grid", "Density");
version_node_socket_name(ntree, GEO_NODE_LEGACY_VOLUME_TO_MESH, "Grid", "Density");
}
}
FOREACH_NODETREE_END;

View File

@ -967,7 +967,7 @@ static bool geometry_node_is_293_legacy(const short node_type)
/* Maybe legacy: Special case for grid names? Or finish patch from level set branch to
* generate a mesh for all grids in the volume. */
case GEO_NODE_VOLUME_TO_MESH:
case GEO_NODE_LEGACY_VOLUME_TO_MESH:
return false;
/* Legacy: Transferred *all* attributes before, will not transfer all built-ins now. */
@ -2098,6 +2098,7 @@ void blo_do_versions_300(FileData *fd, Library *UNUSED(lib), Main *bmain)
}
version_node_id(ntree, FN_NODE_SLICE_STRING, "FunctionNodeSliceString");
version_geometry_nodes_set_position_node_offset(ntree);
version_node_id(ntree, GEO_NODE_LEGACY_VOLUME_TO_MESH, "GeometryNodeLegacyVolumeToMesh");
}
/* Keep this block, even when empty. */

View File

@ -193,6 +193,8 @@ set(SRC
geometry/nodes/legacy/node_geo_raycast.cc
geometry/nodes/legacy/node_geo_select_by_material.cc
geometry/nodes/legacy/node_geo_subdivision_surface.cc
geometry/nodes/legacy/node_geo_volume_to_mesh.cc
geometry/nodes/node_geo_attribute_capture.cc
geometry/nodes/node_geo_attribute_remove.cc
geometry/nodes/node_geo_attribute_statistic.cc

View File

@ -47,6 +47,7 @@ void register_node_type_geo_legacy_curve_subdivide(void);
void register_node_type_geo_legacy_edge_split(void);
void register_node_type_geo_legacy_subdivision_surface(void);
void register_node_type_geo_legacy_raycast(void);
void register_node_type_geo_legacy_volume_to_mesh(void);
void register_node_type_geo_align_rotation_to_vector(void);
void register_node_type_geo_attribute_capture(void);

View File

@ -321,6 +321,7 @@ DefNode(GeometryNode, GEO_NODE_LEGACY_POINTS_TO_VOLUME, def_geo_legacy_points_to
DefNode(GeometryNode, GEO_NODE_LEGACY_RAYCAST, def_geo_legacy_raycast, "LEGACY_RAYCAST", LegacyRaycast, "Raycast", "")
DefNode(GeometryNode, GEO_NODE_LEGACY_SELECT_BY_MATERIAL, 0, "LEGACY_SELECT_BY_MATERIAL", LegacySelectByMaterial, "Select by Material", "")
DefNode(GeometryNode, GEO_NODE_LEGACY_SUBDIVISION_SURFACE, def_geo_subdivision_surface, "LEGACY_SUBDIVISION_SURFACE", LegacySubdivisionSurface, "Subdivision Surface", "")
DefNode(GeometryNode, GEO_NODE_LEGACY_VOLUME_TO_MESH, def_geo_volume_to_mesh, "LEGACY_VOLUME_TO_MESH", LegacyVolumeToMesh, "Volume to Mesh", "")
DefNode(GeometryNode, GEO_NODE_CAPTURE_ATTRIBUTE, def_geo_attribute_capture, "CAPTURE_ATTRIBUTE", CaptureAttribute, "Capture Attribute", "")
DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_REMOVE, 0, "ATTRIBUTE_REMOVE", AttributeRemove, "Attribute Remove", "")

View File

@ -0,0 +1,173 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "DEG_depsgraph_query.h"
#ifdef WITH_OPENVDB
# include <openvdb/tools/GridTransformer.h>
# include <openvdb/tools/VolumeToMesh.h>
#endif
#include "node_geometry_util.hh"
#include "BKE_lib_id.h"
#include "BKE_material.h"
#include "BKE_mesh.h"
#include "BKE_mesh_runtime.h"
#include "BKE_volume.h"
#include "BKE_volume_to_mesh.hh"
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
#include "UI_interface.h"
#include "UI_resources.h"
namespace blender::nodes {
static void geo_node_volume_to_mesh_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Geometry>("Geometry");
b.add_input<decl::String>("Density");
b.add_input<decl::Float>("Voxel Size").default_value(0.3f).min(0.01f).subtype(PROP_DISTANCE);
b.add_input<decl::Float>("Voxel Amount").default_value(64.0f).min(0.0f);
b.add_input<decl::Float>("Threshold").default_value(0.1f).min(0.0f);
b.add_input<decl::Float>("Adaptivity").min(0.0f).max(1.0f).subtype(PROP_FACTOR);
b.add_output<decl::Geometry>("Geometry");
}
static void geo_node_volume_to_mesh_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr)
{
uiLayoutSetPropSep(layout, true);
uiLayoutSetPropDecorate(layout, false);
uiItemR(layout, ptr, "resolution_mode", 0, IFACE_("Resolution"), ICON_NONE);
}
static void geo_node_volume_to_mesh_init(bNodeTree *UNUSED(ntree), bNode *node)
{
NodeGeometryVolumeToMesh *data = (NodeGeometryVolumeToMesh *)MEM_callocN(
sizeof(NodeGeometryVolumeToMesh), __func__);
data->resolution_mode = VOLUME_TO_MESH_RESOLUTION_MODE_GRID;
bNodeSocket *grid_socket = nodeFindSocket(node, SOCK_IN, "Density");
bNodeSocketValueString *grid_socket_value = (bNodeSocketValueString *)grid_socket->default_value;
STRNCPY(grid_socket_value->value, "density");
node->storage = data;
}
static void geo_node_volume_to_mesh_update(bNodeTree *UNUSED(ntree), bNode *node)
{
NodeGeometryVolumeToMesh *data = (NodeGeometryVolumeToMesh *)node->storage;
bNodeSocket *voxel_size_socket = nodeFindSocket(node, SOCK_IN, "Voxel Size");
bNodeSocket *voxel_amount_socket = nodeFindSocket(node, SOCK_IN, "Voxel Amount");
nodeSetSocketAvailability(voxel_amount_socket,
data->resolution_mode == VOLUME_TO_MESH_RESOLUTION_MODE_VOXEL_AMOUNT);
nodeSetSocketAvailability(voxel_size_socket,
data->resolution_mode == VOLUME_TO_MESH_RESOLUTION_MODE_VOXEL_SIZE);
}
#ifdef WITH_OPENVDB
static void create_mesh_from_volume(GeometrySet &geometry_set_in,
GeometrySet &geometry_set_out,
GeoNodeExecParams &params)
{
if (!geometry_set_in.has<VolumeComponent>()) {
return;
}
const NodeGeometryVolumeToMesh &storage =
*(const NodeGeometryVolumeToMesh *)params.node().storage;
bke::VolumeToMeshResolution resolution;
resolution.mode = (VolumeToMeshResolutionMode)storage.resolution_mode;
if (resolution.mode == VOLUME_TO_MESH_RESOLUTION_MODE_VOXEL_AMOUNT) {
resolution.settings.voxel_amount = params.get_input<float>("Voxel Amount");
if (resolution.settings.voxel_amount <= 0.0f) {
return;
}
}
else if (resolution.mode == VOLUME_TO_MESH_RESOLUTION_MODE_VOXEL_SIZE) {
resolution.settings.voxel_size = params.get_input<float>("Voxel Size");
if (resolution.settings.voxel_size <= 0.0f) {
return;
}
}
const VolumeComponent *component = geometry_set_in.get_component_for_read<VolumeComponent>();
const Volume *volume = component->get_for_read();
if (volume == nullptr) {
return;
}
const Main *bmain = DEG_get_bmain(params.depsgraph());
BKE_volume_load(volume, bmain);
const std::string grid_name = params.get_input<std::string>("Density");
const VolumeGrid *volume_grid = BKE_volume_grid_find_for_read(volume, grid_name.c_str());
if (volume_grid == nullptr) {
return;
}
float threshold = params.get_input<float>("Threshold");
float adaptivity = params.get_input<float>("Adaptivity");
const openvdb::GridBase::ConstPtr grid = BKE_volume_grid_openvdb_for_read(volume, volume_grid);
Mesh *mesh = bke::volume_to_mesh(*grid, resolution, threshold, adaptivity);
if (mesh == nullptr) {
return;
}
BKE_id_material_eval_ensure_default_slot(&mesh->id);
MeshComponent &dst_component = geometry_set_out.get_component_for_write<MeshComponent>();
dst_component.replace(mesh);
}
#endif /* WITH_OPENVDB */
static void geo_node_volume_to_mesh_exec(GeoNodeExecParams params)
{
GeometrySet geometry_set_in = params.extract_input<GeometrySet>("Geometry");
GeometrySet geometry_set_out;
#ifdef WITH_OPENVDB
create_mesh_from_volume(geometry_set_in, geometry_set_out, params);
#else
params.error_message_add(NodeWarningType::Error,
TIP_("Disabled, Blender was compiled without OpenVDB"));
#endif
params.set_output("Geometry", geometry_set_out);
}
} // namespace blender::nodes
void register_node_type_geo_legacy_volume_to_mesh()
{
static bNodeType ntype;
geo_node_type_base(
&ntype, GEO_NODE_LEGACY_VOLUME_TO_MESH, "Volume to Mesh", NODE_CLASS_GEOMETRY, 0);
ntype.declare = blender::nodes::geo_node_volume_to_mesh_declare;
node_type_storage(
&ntype, "NodeGeometryVolumeToMesh", node_free_standard_storage, node_copy_standard_storage);
node_type_size(&ntype, 170, 120, 700);
node_type_init(&ntype, blender::nodes::geo_node_volume_to_mesh_init);
node_type_update(&ntype, blender::nodes::geo_node_volume_to_mesh_update);
ntype.geometry_node_execute = blender::nodes::geo_node_volume_to_mesh_exec;
ntype.draw_buttons = blender::nodes::geo_node_volume_to_mesh_layout;
nodeRegisterType(&ntype);
}

View File

@ -39,13 +39,12 @@ namespace blender::nodes {
static void geo_node_volume_to_mesh_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Geometry>("Geometry");
b.add_input<decl::String>("Density");
b.add_input<decl::Geometry>("Volume");
b.add_input<decl::Float>("Voxel Size").default_value(0.3f).min(0.01f).subtype(PROP_DISTANCE);
b.add_input<decl::Float>("Voxel Amount").default_value(64.0f).min(0.0f);
b.add_input<decl::Float>("Threshold").default_value(0.1f).min(0.0f);
b.add_input<decl::Float>("Adaptivity").min(0.0f).max(1.0f).subtype(PROP_FACTOR);
b.add_output<decl::Geometry>("Geometry");
b.add_output<decl::Geometry>("Mesh");
}
static void geo_node_volume_to_mesh_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr)
@ -60,11 +59,6 @@ static void geo_node_volume_to_mesh_init(bNodeTree *UNUSED(ntree), bNode *node)
NodeGeometryVolumeToMesh *data = (NodeGeometryVolumeToMesh *)MEM_callocN(
sizeof(NodeGeometryVolumeToMesh), __func__);
data->resolution_mode = VOLUME_TO_MESH_RESOLUTION_MODE_GRID;
bNodeSocket *grid_socket = nodeFindSocket(node, SOCK_IN, "Density");
bNodeSocketValueString *grid_socket_value = (bNodeSocketValueString *)grid_socket->default_value;
STRNCPY(grid_socket_value->value, "density");
node->storage = data;
}
@ -82,75 +76,120 @@ static void geo_node_volume_to_mesh_update(bNodeTree *UNUSED(ntree), bNode *node
#ifdef WITH_OPENVDB
static void create_mesh_from_volume(GeometrySet &geometry_set_in,
GeometrySet &geometry_set_out,
GeoNodeExecParams &params)
static bke::VolumeToMeshResolution get_resolution_param(const GeoNodeExecParams &params)
{
if (!geometry_set_in.has<VolumeComponent>()) {
return;
}
const NodeGeometryVolumeToMesh &storage =
*(const NodeGeometryVolumeToMesh *)params.node().storage;
bke::VolumeToMeshResolution resolution;
resolution.mode = (VolumeToMeshResolutionMode)storage.resolution_mode;
if (resolution.mode == VOLUME_TO_MESH_RESOLUTION_MODE_VOXEL_AMOUNT) {
resolution.settings.voxel_amount = params.get_input<float>("Voxel Amount");
if (resolution.settings.voxel_amount <= 0.0f) {
return;
}
resolution.settings.voxel_amount = std::max(params.get_input<float>("Voxel Amount"), 0.0f);
}
else if (resolution.mode == VOLUME_TO_MESH_RESOLUTION_MODE_VOXEL_SIZE) {
resolution.settings.voxel_size = params.get_input<float>("Voxel Size");
if (resolution.settings.voxel_size <= 0.0f) {
return;
}
resolution.settings.voxel_size = std::max(params.get_input<float>("Voxel Size"), 0.0f);
}
const VolumeComponent *component = geometry_set_in.get_component_for_read<VolumeComponent>();
const Volume *volume = component->get_for_read();
return resolution;
}
static Mesh *create_mesh_from_volume_grids(Span<openvdb::GridBase::ConstPtr> grids,
const float threshold,
const float adaptivity,
const bke::VolumeToMeshResolution &resolution)
{
Array<bke::OpenVDBMeshData> mesh_data(grids.size());
for (const int i : grids.index_range()) {
mesh_data[i] = bke::volume_to_mesh_data(*grids[i], resolution, threshold, adaptivity);
}
int vert_offset = 0;
int poly_offset = 0;
int loop_offset = 0;
Array<int> vert_offsets(mesh_data.size());
Array<int> poly_offsets(mesh_data.size());
Array<int> loop_offsets(mesh_data.size());
for (const int i : grids.index_range()) {
const bke::OpenVDBMeshData &data = mesh_data[i];
vert_offsets[i] = vert_offset;
poly_offsets[i] = poly_offset;
loop_offsets[i] = loop_offset;
vert_offset += data.verts.size();
poly_offset += (data.tris.size() + data.quads.size());
loop_offset += (3 * data.tris.size() + 4 * data.quads.size());
}
Mesh *mesh = BKE_mesh_new_nomain(vert_offset, 0, 0, loop_offset, poly_offset);
BKE_id_material_eval_ensure_default_slot(&mesh->id);
MutableSpan<MVert> verts{mesh->mvert, mesh->totvert};
MutableSpan<MLoop> loops{mesh->mloop, mesh->totloop};
MutableSpan<MPoly> polys{mesh->mpoly, mesh->totpoly};
for (const int i : grids.index_range()) {
const bke::OpenVDBMeshData &data = mesh_data[i];
bke::fill_mesh_from_openvdb_data(data.verts,
data.tris,
data.quads,
vert_offsets[i],
poly_offsets[i],
loop_offsets[i],
verts,
polys,
loops);
}
BKE_mesh_calc_edges(mesh, false, false);
BKE_mesh_normals_tag_dirty(mesh);
return mesh;
}
static Mesh *create_mesh_from_volume(GeometrySet &geometry_set, GeoNodeExecParams &params)
{
const Volume *volume = geometry_set.get_volume_for_read();
if (volume == nullptr) {
return;
return nullptr;
}
const bke::VolumeToMeshResolution resolution = get_resolution_param(params);
const Main *bmain = DEG_get_bmain(params.depsgraph());
BKE_volume_load(volume, bmain);
const std::string grid_name = params.get_input<std::string>("Density");
const VolumeGrid *volume_grid = BKE_volume_grid_find_for_read(volume, grid_name.c_str());
if (volume_grid == nullptr) {
return;
Vector<openvdb::GridBase::ConstPtr> grids;
for (const int i : IndexRange(BKE_volume_num_grids(volume))) {
const VolumeGrid *volume_grid = BKE_volume_grid_get_for_read(volume, i);
openvdb::GridBase::ConstPtr grid = BKE_volume_grid_openvdb_for_read(volume, volume_grid);
grids.append(std::move(grid));
}
float threshold = params.get_input<float>("Threshold");
float adaptivity = params.get_input<float>("Adaptivity");
const openvdb::GridBase::ConstPtr grid = BKE_volume_grid_openvdb_for_read(volume, volume_grid);
Mesh *mesh = bke::volume_to_mesh(*grid, resolution, threshold, adaptivity);
if (mesh == nullptr) {
return;
if (grids.is_empty()) {
return nullptr;
}
BKE_id_material_eval_ensure_default_slot(&mesh->id);
MeshComponent &dst_component = geometry_set_out.get_component_for_write<MeshComponent>();
dst_component.replace(mesh);
return create_mesh_from_volume_grids(grids,
params.get_input<float>("Threshold"),
params.get_input<float>("Adaptivity"),
resolution);
}
#endif /* WITH_OPENVDB */
static void geo_node_volume_to_mesh_exec(GeoNodeExecParams params)
{
GeometrySet geometry_set_in = params.extract_input<GeometrySet>("Geometry");
GeometrySet geometry_set_out;
GeometrySet geometry_set = params.extract_input<GeometrySet>("Volume");
#ifdef WITH_OPENVDB
create_mesh_from_volume(geometry_set_in, geometry_set_out, params);
geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) {
Mesh *mesh = create_mesh_from_volume(geometry_set, params);
geometry_set.replace_mesh(mesh);
geometry_set.keep_only({GEO_COMPONENT_TYPE_MESH, GEO_COMPONENT_TYPE_INSTANCES});
});
#else
params.error_message_add(NodeWarningType::Error,
TIP_("Disabled, Blender was compiled without OpenVDB"));
#endif
params.set_output("Geometry", geometry_set_out);
params.set_output("Mesh", std::move(geometry_set));
}
} // namespace blender::nodes