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:
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
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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
|
||||
* \{ */
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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}")
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 ¶ms)
|
||||
{
|
||||
#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
|
||||
|
|
Loading…
Reference in New Issue