This refactors how volume grids are stored with the following new goals in mind:
* Get a **stand-alone volume grid** data structure that can be used by geometry nodes.
Previously, the `VolumeGrid` data structure was tightly coupled with the `Volume` data block.
* Support **implicit sharing of grids and trees**. Previously, it was possible to share data
when multiple `Volume` data blocks loaded grids from the same `.vdb` files but this was
not flexible enough.
* Get a safe API for **lazy-loading and unloading** of grids without requiring explicit calls
to some "load" function all the time.
* Get a safe API for **caching grids from files** that is not coupled to the `Volume` data block.
* Get a **tiered API** for different levels of `openvdb` involvement:
* No `OpenVDB`: Since `WITH_OPENVDB` is optional, it's helpful to have parts of the API that
still work in this case. This makes it possible to write high level code for volumes that does
not require `#ifdef WITH_OPENVDB` checks everywhere. This is in `BKE_volume_grid_fwd.hh`.
* Shallow `OpenVDB`: Code using this API requires `WITH_OPENVDB` checks. However, care
is taken to not include the expensive parts of `OpenVDB` and to use forward declarations as
much as possible. This is in `BKE_volume_grid.hh` and uses `openvdb_fwd.hh`.
* "Full" `OpenVDB`: This API requires more heavy `OpenVDB` includes. Fortunately, it turned
out to be not necessary for the common API. So this is only used for task specific APIs.
At the core of the new API is the `VolumeGridData` type. It's a wrapper around an
`openvdb::Grid` and adds some features on top like implicit sharing, lazy-loading and unloading.
Then there are `GVolumeGrid` and `VolumeGrid` which are containers for a volume grid.
Semantically, each `VolumeGrid` has its own independent grid, but this is cheap due to implicit
sharing. At highest level we currently have the `Volume` data-block which contains a list of
`VolumeGrid`.
```mermaid
flowchart LR
Volume --> VolumeGrid --> VolumeGridData --> openvdb::Grid
```
The loading of `.vdb` files is abstracted away behind the volume file cache API. This API makes
it easy to load and reuse entire files and individual grids from disk. It also supports caching
simplify levels for grids on disk.
An important new concept are the "tree access tokens". Whenever some code wants to work
with an openvdb tree, it has to retrieve an access token from the corresponding `VolumeGridData`.
This access token has to be kept alive for as long as the code works with the grid data. The same
token is valid for read and write access. The purpose of these access tokens is to make it possible
to detect when some code is currently working with the openvdb tree. This allows freeing it if it's
possible to reload it later on (e.g. from disk). It's possible to free a tree that is referenced by
multiple owners, but only no one is actively working with. In some sense, this is similar to the
existing `ImageUser` concept.
The most important new files to read are `BKE_volume_grid.hh` and `BKE_volume_grid_file_cache.hh`.
Most other changes are updates to existing code to use the new API.
Pull Request: https://projects.blender.org/blender/blender/pulls/116315
205 lines
7.3 KiB
C++
205 lines
7.3 KiB
C++
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
#include "BLI_math_matrix.hh"
|
|
#include "BLI_task.hh"
|
|
|
|
#include "BKE_mesh.hh"
|
|
#include "BKE_mesh_runtime.hh"
|
|
#include "BKE_volume.hh"
|
|
#include "BKE_volume_openvdb.hh"
|
|
|
|
#include "GEO_mesh_to_volume.hh"
|
|
|
|
#ifdef WITH_OPENVDB
|
|
# include <algorithm>
|
|
# include <openvdb/openvdb.h>
|
|
# include <openvdb/tools/GridTransformer.h>
|
|
# include <openvdb/tools/LevelSetUtil.h>
|
|
# include <openvdb/tools/VolumeToMesh.h>
|
|
|
|
namespace blender::geometry {
|
|
|
|
/* This class follows the MeshDataAdapter interface from openvdb. */
|
|
class OpenVDBMeshAdapter {
|
|
private:
|
|
Span<float3> positions_;
|
|
Span<int> corner_verts_;
|
|
Span<int3> corner_tris_;
|
|
float4x4 transform_;
|
|
|
|
public:
|
|
OpenVDBMeshAdapter(const Mesh &mesh, float4x4 transform);
|
|
size_t polygonCount() const;
|
|
size_t pointCount() const;
|
|
size_t vertexCount(size_t /*polygon_index*/) const;
|
|
void getIndexSpacePoint(size_t polygon_index, size_t vertex_index, openvdb::Vec3d &pos) const;
|
|
};
|
|
|
|
OpenVDBMeshAdapter::OpenVDBMeshAdapter(const Mesh &mesh, float4x4 transform)
|
|
: positions_(mesh.vert_positions()),
|
|
corner_verts_(mesh.corner_verts()),
|
|
corner_tris_(mesh.corner_tris()),
|
|
transform_(transform)
|
|
{
|
|
}
|
|
|
|
size_t OpenVDBMeshAdapter::polygonCount() const
|
|
{
|
|
return size_t(corner_tris_.size());
|
|
}
|
|
|
|
size_t OpenVDBMeshAdapter::pointCount() const
|
|
{
|
|
return size_t(positions_.size());
|
|
}
|
|
|
|
size_t OpenVDBMeshAdapter::vertexCount(size_t /*polygon_index*/) const
|
|
{
|
|
/* All polygons are triangles. */
|
|
return 3;
|
|
}
|
|
|
|
void OpenVDBMeshAdapter::getIndexSpacePoint(size_t polygon_index,
|
|
size_t vertex_index,
|
|
openvdb::Vec3d &pos) const
|
|
{
|
|
const int3 &tri = corner_tris_[polygon_index];
|
|
const float3 transformed_co = math::transform_point(
|
|
transform_, positions_[corner_verts_[tri[vertex_index]]]);
|
|
pos = &transformed_co.x;
|
|
}
|
|
|
|
float volume_compute_voxel_size(const Depsgraph *depsgraph,
|
|
const FunctionRef<Bounds<float3>()> bounds_fn,
|
|
const MeshToVolumeResolution res,
|
|
const float exterior_band_width,
|
|
const float4x4 &transform)
|
|
{
|
|
const float volume_simplify = BKE_volume_simplify_factor(depsgraph);
|
|
if (volume_simplify == 0.0f) {
|
|
return 0.0f;
|
|
}
|
|
|
|
if (res.mode == MESH_TO_VOLUME_RESOLUTION_MODE_VOXEL_SIZE) {
|
|
return res.settings.voxel_size / volume_simplify;
|
|
}
|
|
if (res.settings.voxel_amount <= 0) {
|
|
return 0;
|
|
}
|
|
|
|
const Bounds<float3> bounds = bounds_fn();
|
|
|
|
/* Compute the diagonal of the bounding box. This is used because
|
|
* it will always be bigger than the widest side of the mesh. */
|
|
const float diagonal = math::distance(math::transform_point(transform, bounds.min),
|
|
math::transform_point(transform, bounds.max));
|
|
|
|
/* To get the approximate size per voxel, first subtract the exterior band from the requested
|
|
* voxel amount, then divide the diagonal with this value if it's bigger than 1. */
|
|
const float voxel_size =
|
|
(diagonal / std::max(1.0f, float(res.settings.voxel_amount) - 2.0f * exterior_band_width));
|
|
|
|
/* Return the simplified voxel size. */
|
|
return voxel_size / volume_simplify;
|
|
}
|
|
|
|
static openvdb::FloatGrid::Ptr mesh_to_fog_volume_grid(
|
|
const Mesh *mesh,
|
|
const float4x4 &mesh_to_volume_space_transform,
|
|
const float voxel_size,
|
|
const float interior_band_width,
|
|
const float density)
|
|
{
|
|
if (voxel_size < 1e-5f) {
|
|
return nullptr;
|
|
}
|
|
|
|
float4x4 mesh_to_index_space_transform = math::from_scale<float4x4>(float3(1.0f / voxel_size));
|
|
mesh_to_index_space_transform *= mesh_to_volume_space_transform;
|
|
/* Better align generated grid with the source mesh. */
|
|
mesh_to_index_space_transform.location() -= 0.5f;
|
|
|
|
OpenVDBMeshAdapter mesh_adapter{*mesh, mesh_to_index_space_transform};
|
|
const float interior = std::max(1.0f, interior_band_width / voxel_size);
|
|
|
|
openvdb::math::Transform::Ptr transform = openvdb::math::Transform::createLinearTransform(
|
|
voxel_size);
|
|
openvdb::FloatGrid::Ptr new_grid = openvdb::tools::meshToVolume<openvdb::FloatGrid>(
|
|
mesh_adapter, *transform, 1.0f, interior);
|
|
|
|
openvdb::tools::sdfToFogVolume(*new_grid);
|
|
|
|
if (density != 1.0f) {
|
|
openvdb::tools::foreach (new_grid->beginValueOn(),
|
|
[&](const openvdb::FloatGrid::ValueOnIter &iter) {
|
|
iter.modifyValue([&](float &value) { value *= density; });
|
|
});
|
|
}
|
|
return new_grid;
|
|
}
|
|
|
|
static openvdb::FloatGrid::Ptr mesh_to_sdf_volume_grid(const Mesh &mesh,
|
|
const float voxel_size,
|
|
const float half_band_width)
|
|
{
|
|
if (voxel_size <= 0.0f || half_band_width <= 0.0f) {
|
|
return nullptr;
|
|
}
|
|
|
|
const Span<float3> positions = mesh.vert_positions();
|
|
const Span<int> corner_verts = mesh.corner_verts();
|
|
const Span<int3> corner_tris = mesh.corner_tris();
|
|
|
|
std::vector<openvdb::Vec3s> points(positions.size());
|
|
std::vector<openvdb::Vec3I> triangles(corner_tris.size());
|
|
|
|
threading::parallel_for(positions.index_range(), 2048, [&](const IndexRange range) {
|
|
for (const int i : range) {
|
|
const float3 &co = positions[i];
|
|
points[i] = openvdb::Vec3s(co.x, co.y, co.z) - 0.5f * voxel_size;
|
|
}
|
|
});
|
|
|
|
threading::parallel_for(corner_tris.index_range(), 2048, [&](const IndexRange range) {
|
|
for (const int i : range) {
|
|
const int3 &tri = corner_tris[i];
|
|
triangles[i] = openvdb::Vec3I(
|
|
corner_verts[tri[0]], corner_verts[tri[1]], corner_verts[tri[2]]);
|
|
}
|
|
});
|
|
|
|
openvdb::math::Transform::Ptr transform = openvdb::math::Transform::createLinearTransform(
|
|
voxel_size);
|
|
openvdb::FloatGrid::Ptr new_grid = openvdb::tools::meshToLevelSet<openvdb::FloatGrid>(
|
|
*transform, points, triangles, half_band_width);
|
|
|
|
return new_grid;
|
|
}
|
|
|
|
bke::VolumeGridData *fog_volume_grid_add_from_mesh(Volume *volume,
|
|
const StringRefNull name,
|
|
const Mesh *mesh,
|
|
const float4x4 &mesh_to_volume_space_transform,
|
|
const float voxel_size,
|
|
const float interior_band_width,
|
|
const float density)
|
|
{
|
|
openvdb::FloatGrid::Ptr mesh_grid = mesh_to_fog_volume_grid(
|
|
mesh, mesh_to_volume_space_transform, voxel_size, interior_band_width, density);
|
|
return mesh_grid ? BKE_volume_grid_add_vdb(*volume, name, std::move(mesh_grid)) : nullptr;
|
|
}
|
|
|
|
bke::VolumeGridData *sdf_volume_grid_add_from_mesh(Volume *volume,
|
|
const StringRefNull name,
|
|
const Mesh &mesh,
|
|
const float voxel_size,
|
|
const float half_band_width)
|
|
{
|
|
openvdb::FloatGrid::Ptr mesh_grid = mesh_to_sdf_volume_grid(mesh, voxel_size, half_band_width);
|
|
return mesh_grid ? BKE_volume_grid_add_vdb(*volume, name, std::move(mesh_grid)) : nullptr;
|
|
}
|
|
} // namespace blender::geometry
|
|
#endif
|