Geometry Nodes: initial support for volumes

For the most part, this just adds boilerplate code for volume support in geometry nodes:
* Add `VolumeComponent` next to `MeshComponent`, etc.
* Support `VolumeComponent` in depsgraph object iterator.

Furthermore, I added initial volume support in a few nodes:
* The Object Info node outputs an object instance when the input is a volume object
  (that will be the same for mesh objects soonish, to avoid copies).
* Support transforming a `VolumeComponent` in the Transform node.
* Support the `VolumeComponent` in Join Geometry nodes, but only when just one of the
  inputs has a volume component for now.

Right now, there is no way to create a `VolumeComponent`, because the Object Info node
outputs an object instance. The `VolumeComponent` will be necessary for upcoming nodes,
which will generate volumes on the fly.

Viewport selection does not work correctly with `VolumeComponent`s currently. I don't
know why that is. That can be figured out a bit later, once we can actually create
new volumes in geometry nodes.

Ref T84604.

Differential Revision: https://developer.blender.org/D10147
This commit is contained in:
Jacques Lucke 2021-01-21 10:32:42 +01:00
parent 985bc08688
commit 793547e7d1
Notes: blender-bot 2023-02-13 23:39:48 +01:00
Referenced by issue #84604, Support volume objects in the geometry node tree for moss use case
Referenced by issue #84606, Points to Volume modifier for the moss use case
8 changed files with 233 additions and 0 deletions

View File

@ -36,6 +36,7 @@ struct Collection;
struct Mesh;
struct Object;
struct PointCloud;
struct Volume;
/* Each geometry component has a specific type. The type determines what kind of data the component
* stores. Functions modifying a geometry will usually just modify a subset of the component types.
@ -44,6 +45,7 @@ enum class GeometryComponentType {
Mesh = 0,
PointCloud = 1,
Instances = 2,
Volume = 3,
};
enum class GeometryOwnershipType {
@ -319,10 +321,13 @@ struct GeometrySet {
bool has_mesh() const;
bool has_pointcloud() const;
bool has_instances() const;
bool has_volume() const;
const Mesh *get_mesh_for_read() const;
const PointCloud *get_pointcloud_for_read() const;
const Volume *get_volume_for_read() const;
Mesh *get_mesh_for_write();
PointCloud *get_pointcloud_for_write();
Volume *get_volume_for_write();
/* Utility methods for replacement. */
void replace_mesh(Mesh *mesh, GeometryOwnershipType ownership = GeometryOwnershipType::Owned);
@ -464,3 +469,25 @@ class InstancesComponent : public GeometryComponent {
static constexpr inline GeometryComponentType static_type = GeometryComponentType::Instances;
};
/** A geometry component that stores volume grids. */
class VolumeComponent : public GeometryComponent {
private:
Volume *volume_ = nullptr;
GeometryOwnershipType ownership_ = GeometryOwnershipType::Owned;
public:
VolumeComponent();
~VolumeComponent();
GeometryComponent *copy() const override;
void clear();
bool has_volume() const;
void replace(Volume *volume, GeometryOwnershipType ownership = GeometryOwnershipType::Owned);
Volume *release();
const Volume *get_for_read() const;
Volume *get_for_write();
static constexpr inline GeometryComponentType static_type = GeometryComponentType::Volume;
};

View File

@ -19,6 +19,7 @@
#include "BKE_mesh.h"
#include "BKE_mesh_wrapper.h"
#include "BKE_pointcloud.h"
#include "BKE_volume.h"
#include "DNA_object_types.h"
@ -51,6 +52,8 @@ GeometryComponent *GeometryComponent::create(GeometryComponentType component_typ
return new PointCloudComponent();
case GeometryComponentType::Instances:
return new InstancesComponent();
case GeometryComponentType::Volume:
return new VolumeComponent();
}
BLI_assert(false);
return nullptr;
@ -201,6 +204,13 @@ const PointCloud *GeometrySet::get_pointcloud_for_read() const
return (component == nullptr) ? nullptr : component->get_for_read();
}
/* Returns a read-only volume or null. */
const Volume *GeometrySet::get_volume_for_read() const
{
const VolumeComponent *component = this->get_component_for_read<VolumeComponent>();
return (component == nullptr) ? nullptr : component->get_for_read();
}
/* Returns true when the geometry set has a point cloud component that has a point cloud. */
bool GeometrySet::has_pointcloud() const
{
@ -215,6 +225,13 @@ bool GeometrySet::has_instances() const
return component != nullptr && component->instances_amount() >= 1;
}
/* Returns true when the geometry set has a volume component that has a volume. */
bool GeometrySet::has_volume() const
{
const VolumeComponent *component = this->get_component_for_read<VolumeComponent>();
return component != nullptr && component->has_volume();
}
/* Create a new geometry set that only contains the given mesh. */
GeometrySet GeometrySet::create_with_mesh(Mesh *mesh, GeometryOwnershipType ownership)
{
@ -262,6 +279,13 @@ PointCloud *GeometrySet::get_pointcloud_for_write()
return component.get_for_write();
}
/* Returns a mutable volume or null. No ownership is transferred. */
Volume *GeometrySet::get_volume_for_write()
{
VolumeComponent &component = this->get_component_for_write<VolumeComponent>();
return component.get_for_write();
}
/** \} */
/* -------------------------------------------------------------------- */
@ -566,6 +590,85 @@ bool InstancesComponent::is_empty() const
/** \} */
/* -------------------------------------------------------------------- */
/** \name Volume Component
* \{ */
VolumeComponent::VolumeComponent() : GeometryComponent(GeometryComponentType::Volume)
{
}
VolumeComponent::~VolumeComponent()
{
this->clear();
}
GeometryComponent *VolumeComponent::copy() const
{
VolumeComponent *new_component = new VolumeComponent();
if (volume_ != nullptr) {
new_component->volume_ = BKE_volume_copy_for_eval(volume_, false);
new_component->ownership_ = GeometryOwnershipType::Owned;
}
return new_component;
}
void VolumeComponent::clear()
{
BLI_assert(this->is_mutable());
if (volume_ != nullptr) {
if (ownership_ == GeometryOwnershipType::Owned) {
BKE_id_free(nullptr, volume_);
}
volume_ = nullptr;
}
}
bool VolumeComponent::has_volume() const
{
return volume_ != nullptr;
}
/* Clear the component and replace it with the new volume. */
void VolumeComponent::replace(Volume *volume, GeometryOwnershipType ownership)
{
BLI_assert(this->is_mutable());
this->clear();
volume_ = volume;
ownership_ = ownership;
}
/* Return the volume and clear the component. The caller takes over responsibility for freeing the
* volume (if the component was responsible before). */
Volume *VolumeComponent::release()
{
BLI_assert(this->is_mutable());
Volume *volume = volume_;
volume_ = nullptr;
return volume;
}
/* Get the volume from this component. This method can be used by multiple threads at the same
* time. Therefore, the returned volume should not be modified. No ownership is transferred. */
const Volume *VolumeComponent::get_for_read() const
{
return volume_;
}
/* Get the volume from this component. This method can only be used when the component is mutable,
* i.e. it is not shared. The returned volume can be modified. No ownership is transferred. */
Volume *VolumeComponent::get_for_write()
{
BLI_assert(this->is_mutable());
if (ownership_ == GeometryOwnershipType::ReadOnly) {
volume_ = BKE_volume_copy_for_eval(volume_, false);
ownership_ = GeometryOwnershipType::Owned;
}
return volume_;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name C API
* \{ */

View File

@ -523,6 +523,8 @@ static void volume_copy_data(Main *UNUSED(bmain),
volume_dst->runtime.grids = OBJECT_GUARDED_NEW(VolumeGridVector, grids_src);
}
#endif
volume_dst->batch_cache = nullptr;
}
static void volume_free_data(ID *id)

View File

@ -193,6 +193,30 @@ bool deg_iterator_components_step(BLI_Iterator *iter)
}
}
/* The volume component. */
if (data->geometry_component_id == 2) {
data->geometry_component_id++;
/* Don't use a temporary object for this component, when the owner is a volume object. */
if (data->geometry_component_owner->type == OB_VOLUME) {
iter->current = data->geometry_component_owner;
return true;
}
const VolumeComponent *component = geometry_set->get_component_for_read<VolumeComponent>();
if (component != nullptr) {
const Volume *volume = component->get_for_read();
Object *temp_object = &data->temp_geometry_component_object;
*temp_object = *data->geometry_component_owner;
temp_object->type = OB_VOLUME;
temp_object->data = (void *)volume;
temp_object->runtime.select_id = data->geometry_component_owner->runtime.select_id;
iter->current = temp_object;
return true;
}
}
data->geometry_component_owner = nullptr;
return false;
}

View File

@ -366,4 +366,11 @@ if(WITH_OPENSUBDIV)
add_definitions(-DWITH_OPENSUBDIV)
endif()
if(WITH_OPENVDB)
list(APPEND INC_SYS
${OPENVDB_INCLUDE_DIRS}
)
add_definitions(-DWITH_OPENVDB ${OPENVDB_DEFINITIONS})
endif()
blender_add_lib(bf_nodes "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")

View File

@ -228,6 +228,14 @@ static void join_components(Span<const InstancesComponent *> src_components, Geo
}
}
static void join_components(Span<const VolumeComponent *> src_components, GeometrySet &result)
{
/* Not yet supported. Joining volume grids with the same name requires resampling of at least one
* of the grids. The cell size of the resulting volume has to be determined somehow. */
VolumeComponent &dst_component = result.get_component_for_write<VolumeComponent>();
UNUSED_VARS(src_components, dst_component);
}
template<typename Component>
static void join_component_type(Span<const GeometrySet *> src_geometry_sets, GeometrySet &result)
{
@ -260,6 +268,7 @@ static void geo_node_join_geometry_exec(GeoNodeExecParams params)
join_component_type<MeshComponent>(src_geometry_sets, geometry_set_result);
join_component_type<PointCloudComponent>(src_geometry_sets, geometry_set_result);
join_component_type<InstancesComponent>(src_geometry_sets, geometry_set_result);
join_component_type<VolumeComponent>(src_geometry_sets, geometry_set_result);
params.set_output("Geometry", std::move(geometry_set_result));
}

View File

@ -19,6 +19,7 @@
#include "BKE_mesh.h"
#include "BKE_mesh_wrapper.h"
#include "BKE_modifier.h"
#include "BKE_volume.h"
#include "BLI_math_matrix.h"
@ -86,6 +87,16 @@ static void geo_node_object_info_exec(GeoNodeExecParams params)
mesh_component.copy_vertex_group_names_from_object(*object);
}
}
if (object->type == OB_VOLUME) {
InstancesComponent &instances = geometry_set.get_component_for_write<InstancesComponent>();
if (transform_space_relative) {
instances.add_instance(object, location, rotation, scale);
}
else {
instances.add_instance(object, {0, 0, 0});
}
}
}
}

View File

@ -14,11 +14,19 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifdef WITH_OPENVDB
# include <openvdb/openvdb.h>
#endif
#include "BLI_math_matrix.h"
#include "DNA_pointcloud_types.h"
#include "DNA_volume_types.h"
#include "BKE_mesh.h"
#include "BKE_volume.h"
#include "DEG_depsgraph_query.h"
#include "node_geometry_util.hh"
@ -116,6 +124,43 @@ static void transform_instances(InstancesComponent &instances,
}
}
static void transform_volume(Volume *volume,
const float3 translation,
const float3 rotation,
const float3 scale,
GeoNodeExecParams &params)
{
#ifdef WITH_OPENVDB
/* Scaling an axis to zero is not supported for volumes. */
const float3 limited_scale = {
(scale.x == 0.0f) ? FLT_EPSILON : scale.x,
(scale.y == 0.0f) ? FLT_EPSILON : scale.y,
(scale.z == 0.0f) ? FLT_EPSILON : scale.z,
};
Main *bmain = DEG_get_bmain(params.depsgraph());
BKE_volume_load(volume, bmain);
float matrix[4][4];
loc_eul_size_to_mat4(matrix, translation, rotation, limited_scale);
openvdb::Mat4s vdb_matrix;
memcpy(vdb_matrix.asPointer(), matrix, sizeof(float[4][4]));
openvdb::Mat4d vdb_matrix_d{vdb_matrix};
const int num_grids = BKE_volume_num_grids(volume);
for (const int i : IndexRange(num_grids)) {
VolumeGrid *volume_grid = BKE_volume_grid_get(volume, i);
openvdb::GridBase::Ptr grid = BKE_volume_grid_openvdb_for_write(volume, volume_grid, false);
openvdb::math::Transform &grid_transform = grid->transform();
grid_transform.postMult(vdb_matrix_d);
}
#else
UNUSED_VARS(volume, translation, rotation, scale, params);
#endif
}
static void geo_node_transform_exec(GeoNodeExecParams params)
{
GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry");
@ -138,6 +183,11 @@ static void geo_node_transform_exec(GeoNodeExecParams params)
transform_instances(instances, translation, rotation, scale);
}
if (geometry_set.has_volume()) {
Volume *volume = geometry_set.get_volume_for_write();
transform_volume(volume, translation, rotation, scale, params);
}
params.set_output("Geometry", std::move(geometry_set));
}
} // namespace blender::nodes