Geometry Nodes: new Volume Cube node
This commit adds a Volume Cube primitive node. It outputs a volume that contains a single "density" float grid. The density per voxel can be controlled with a field that depends on the voxel position (using the existing Position node). Other field inputs are not supported. The density field is evaluated on every voxel. Possible future improvements are listed in D15198. Differential Revision: https://developer.blender.org/D15198
This commit is contained in:
parent
75489b5887
commit
838c4a97f1
Notes:
blender-bot
2023-02-14 10:32:59 +01:00
Referenced by issue #98940, Texture nodes CPU evaluation design
|
@ -714,6 +714,7 @@ geometry_node_categories = [
|
|||
NodeItem("ShaderNodeVectorRotate"),
|
||||
]),
|
||||
GeometryNodeCategory("GEO_VOLUME", "Volume", items=[
|
||||
NodeItem("GeometryNodeVolumeCube"),
|
||||
NodeItem("GeometryNodeVolumeToMesh"),
|
||||
]),
|
||||
GeometryNodeCategory("GEO_GROUP", "Group", items=node_group_items),
|
||||
|
|
|
@ -1495,6 +1495,7 @@ struct TexResult;
|
|||
#define GEO_NODE_REMOVE_ATTRIBUTE 1158
|
||||
#define GEO_NODE_INPUT_INSTANCE_ROTATION 1159
|
||||
#define GEO_NODE_INPUT_INSTANCE_SCALE 1160
|
||||
#define GEO_NODE_VOLUME_CUBE 1161
|
||||
|
||||
/** \} */
|
||||
|
||||
|
|
|
@ -4836,6 +4836,7 @@ static void registerGeometryNodes()
|
|||
register_node_type_geo_translate_instances();
|
||||
register_node_type_geo_triangulate();
|
||||
register_node_type_geo_viewer();
|
||||
register_node_type_geo_volume_cube();
|
||||
register_node_type_geo_volume_to_mesh();
|
||||
}
|
||||
|
||||
|
|
|
@ -135,6 +135,7 @@ void register_node_type_geo_transform(void);
|
|||
void register_node_type_geo_translate_instances(void);
|
||||
void register_node_type_geo_triangulate(void);
|
||||
void register_node_type_geo_viewer(void);
|
||||
void register_node_type_geo_volume_cube(void);
|
||||
void register_node_type_geo_volume_to_mesh(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
|
|
@ -397,6 +397,7 @@ DefNode(GeometryNode, GEO_NODE_TRANSLATE_INSTANCES, 0, "TRANSLATE_INSTANCES", Tr
|
|||
DefNode(GeometryNode, GEO_NODE_TRIANGULATE, def_geo_triangulate, "TRIANGULATE", Triangulate, "Triangulate", "")
|
||||
DefNode(GeometryNode, GEO_NODE_TRIM_CURVE, def_geo_curve_trim, "TRIM_CURVE", TrimCurve, "Trim Curve", "")
|
||||
DefNode(GeometryNode, GEO_NODE_VIEWER, def_geo_viewer, "VIEWER", Viewer, "Viewer", "")
|
||||
DefNode(GeometryNode, GEO_NODE_VOLUME_CUBE, 0, "VOLUME_CUBE", VolumeCube, "Volume Cube", "")
|
||||
DefNode(GeometryNode, GEO_NODE_VOLUME_TO_MESH, def_geo_volume_to_mesh, "VOLUME_TO_MESH", VolumeToMesh, "Volume to Mesh", "")
|
||||
|
||||
/* undefine macros */
|
||||
|
|
|
@ -144,6 +144,7 @@ set(SRC
|
|||
nodes/node_geo_translate_instances.cc
|
||||
nodes/node_geo_triangulate.cc
|
||||
nodes/node_geo_viewer.cc
|
||||
nodes/node_geo_volume_cube.cc
|
||||
nodes/node_geo_volume_to_mesh.cc
|
||||
|
||||
node_geometry_exec.cc
|
||||
|
|
|
@ -0,0 +1,197 @@
|
|||
/* 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;
|
||||
}
|
||||
|
||||
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));
|
||||
const float3 scale_fac = (bounds_max - bounds_min) / float3(resolution - 1);
|
||||
grid->transform().postScale(openvdb::math::Vec3<float>(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);
|
||||
}
|
Loading…
Reference in New Issue