diff options
Diffstat (limited to 'source/blender/nodes/geometry/nodes/node_geo_volume_cube.cc')
-rw-r--r-- | source/blender/nodes/geometry/nodes/node_geo_volume_cube.cc | 204 |
1 files changed, 204 insertions, 0 deletions
diff --git a/source/blender/nodes/geometry/nodes/node_geo_volume_cube.cc b/source/blender/nodes/geometry/nodes/node_geo_volume_cube.cc new file mode 100644 index 00000000000..e964bf03ed2 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_volume_cube.cc @@ -0,0 +1,204 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#ifdef WITH_OPENVDB +# include <openvdb/openvdb.h> +# include <openvdb/tools/Dense.h> +# include <openvdb/tools/LevelSetUtil.h> +# include <openvdb/tools/ParticlesToLevelSet.h> +#endif + +#include "node_geometry_util.hh" + +#include "DNA_mesh_types.h" + +#include "BLI_task.hh" + +#include "BKE_geometry_set.hh" +#include "BKE_lib_id.h" +#include "BKE_mesh.h" +#include "BKE_volume.h" + +namespace blender::nodes::node_geo_volume_cube_cc { + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Float>(N_("Density")) + .description(N_("Volume density per voxel")) + .supports_field() + .default_value(1.0f); + b.add_input<decl::Float>(N_("Background")) + .description(N_("Value for voxels outside of the cube")); + + b.add_input<decl::Vector>(N_("Min")) + .description(N_("Minimum boundary of volume")) + .default_value(float3(-1.0f)); + b.add_input<decl::Vector>(N_("Max")) + .description(N_("Maximum boundary of volume")) + .default_value(float3(1.0f)); + + b.add_input<decl::Int>(N_("Resolution X")) + .description(N_("Number of voxels in the X axis")) + .default_value(32) + .min(2); + b.add_input<decl::Int>(N_("Resolution Y")) + .description(N_("Number of voxels in the Y axis")) + .default_value(32) + .min(2); + b.add_input<decl::Int>(N_("Resolution Z")) + .description(N_("Number of voxels in the Z axis")) + .default_value(32) + .min(2); + + b.add_output<decl::Geometry>(N_("Volume")); +} + +static float map(const float x, + const float in_min, + const float in_max, + const float out_min, + const float out_max) +{ + return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; +} + +class Grid3DFieldContext : public FieldContext { + private: + int3 resolution_; + float3 bounds_min_; + float3 bounds_max_; + + public: + Grid3DFieldContext(const int3 resolution, const float3 bounds_min, const float3 bounds_max) + : resolution_(resolution), bounds_min_(bounds_min), bounds_max_(bounds_max) + { + } + + int64_t points_num() const + { + return static_cast<int64_t>(resolution_.x) * static_cast<int64_t>(resolution_.y) * + static_cast<int64_t>(resolution_.z); + } + + GVArray get_varray_for_input(const FieldInput &field_input, + const IndexMask UNUSED(mask), + ResourceScope &UNUSED(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 {}; + } + + Array<float3> positions(this->points_num()); + + threading::parallel_for(IndexRange(resolution_.x), 1, [&](const IndexRange x_range) { + /* Start indexing at current X slice. */ + int64_t index = x_range.start() * resolution_.y * resolution_.z; + for (const int64_t x_i : x_range) { + const float x = map(x_i, 0.0f, resolution_.x - 1, bounds_min_.x, bounds_max_.x); + for (const int64_t y_i : IndexRange(resolution_.y)) { + const float y = map(y_i, 0.0f, resolution_.y - 1, bounds_min_.y, bounds_max_.y); + for (const int64_t z_i : IndexRange(resolution_.z)) { + const float z = map(z_i, 0.0f, resolution_.z - 1, bounds_min_.z, bounds_max_.z); + positions[index] = float3(x, y, z); + index++; + } + } + } + }); + return VArray<float3>::ForContainer(std::move(positions)); + } +}; + +#ifdef WITH_OPENVDB +static void node_geo_exec(GeoNodeExecParams params) +{ + const float3 bounds_min = params.extract_input<float3>("Min"); + const float3 bounds_max = params.extract_input<float3>("Max"); + + const int3 resolution = int3(params.extract_input<int>("Resolution X"), + params.extract_input<int>("Resolution Y"), + params.extract_input<int>("Resolution Z")); + + if (resolution.x < 2 || resolution.y < 2 || resolution.z < 2) { + params.error_message_add(NodeWarningType::Error, TIP_("Resolution must be greater than 1")); + params.set_default_remaining_outputs(); + return; + } + + if (bounds_min.x == bounds_max.x || bounds_min.y == bounds_max.y || + bounds_min.z == bounds_max.z) { + params.error_message_add(NodeWarningType::Error, + TIP_("Bounding box volume must be greater than 0")); + params.set_default_remaining_outputs(); + return; + } + + const double3 scale_fac = double3(bounds_max - bounds_min) / double3(resolution - 1); + if (!BKE_volume_grid_determinant_valid(scale_fac.x * scale_fac.y * scale_fac.z)) { + params.error_message_add(NodeWarningType::Warning, + TIP_("Volume scale is lower than permitted by OpenVDB")); + params.set_default_remaining_outputs(); + return; + } + + Field<float> input_field = params.extract_input<Field<float>>("Density"); + + /* Evaluate input field on a 3D grid. */ + Grid3DFieldContext context(resolution, bounds_min, bounds_max); + FieldEvaluator evaluator(context, context.points_num()); + Array<float> densities(context.points_num()); + evaluator.add_with_destination(std::move(input_field), densities.as_mutable_span()); + evaluator.evaluate(); + + /* Store resulting values in openvdb grid. */ + const float background = params.extract_input<float>("Background"); + openvdb::FloatGrid::Ptr grid = openvdb::FloatGrid::create(background); + grid->setGridClass(openvdb::GRID_FOG_VOLUME); + + openvdb::tools::Dense<float, openvdb::tools::LayoutZYX> dense_grid{ + openvdb::math::CoordBBox({0, 0, 0}, {resolution.x - 1, resolution.y - 1, resolution.z - 1}), + densities.data()}; + openvdb::tools::copyFromDense(dense_grid, *grid, 0.0f); + + grid->transform().preTranslate(openvdb::math::Vec3<float>(-0.5f)); + grid->transform().postScale(openvdb::math::Vec3<double>(scale_fac.x, scale_fac.y, scale_fac.z)); + grid->transform().postTranslate( + openvdb::math::Vec3<float>(bounds_min.x, bounds_min.y, bounds_min.z)); + + Volume *volume = (Volume *)BKE_id_new_nomain(ID_VO, nullptr); + BKE_volume_init_grids(volume); + + BKE_volume_grid_add_vdb(*volume, "density", std::move(grid)); + + GeometrySet r_geometry_set; + r_geometry_set.replace_volume(volume); + params.set_output("Volume", r_geometry_set); +} + +#else +static void node_geo_exec(GeoNodeExecParams params) +{ + params.error_message_add(NodeWarningType::Error, + TIP_("Disabled, Blender was compiled without OpenVDB")); + params.set_default_remaining_outputs(); +} +#endif /* WITH_OPENVDB */ + +} // namespace blender::nodes::node_geo_volume_cube_cc + +void register_node_type_geo_volume_cube() +{ + namespace file_ns = blender::nodes::node_geo_volume_cube_cc; + + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_VOLUME_CUBE, "Volume Cube", NODE_CLASS_GEOMETRY); + + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec; + nodeRegisterType(&ntype); +} |