forked from bartvdbraak/blender
Merge branch 'blender-v2.90-release'
This commit is contained in:
commit
1f8cb90828
@ -1201,12 +1201,16 @@ void GeometryManager::device_update_volume_images(Device *device, Scene *scene,
|
||||
}
|
||||
|
||||
ImageHandle &handle = attr.data_voxel();
|
||||
/* We can build directly from OpenVDB data structures, no need to
|
||||
* load such images early. */
|
||||
if (!handle.vdb_loader()) {
|
||||
const int slot = handle.svm_slot();
|
||||
if (slot != -1) {
|
||||
volume_images.insert(slot);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (int slot, volume_images) {
|
||||
pool.push(function_bind(
|
||||
|
@ -18,6 +18,7 @@
|
||||
#include "device/device.h"
|
||||
#include "render/colorspace.h"
|
||||
#include "render/image_oiio.h"
|
||||
#include "render/image_vdb.h"
|
||||
#include "render/scene.h"
|
||||
#include "render/stats.h"
|
||||
|
||||
@ -172,6 +173,31 @@ device_texture *ImageHandle::image_memory(const int tile_index) const
|
||||
return img ? img->mem : NULL;
|
||||
}
|
||||
|
||||
VDBImageLoader *ImageHandle::vdb_loader(const int tile_index) const
|
||||
{
|
||||
if (tile_index >= tile_slots.size()) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ImageManager::Image *img = manager->images[tile_slots[tile_index]];
|
||||
|
||||
if (img == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ImageLoader *loader = img->loader;
|
||||
|
||||
if (loader == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (loader->is_vdb_loader()) {
|
||||
return dynamic_cast<VDBImageLoader *>(loader);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool ImageHandle::operator==(const ImageHandle &other) const
|
||||
{
|
||||
return manager == other.manager && tile_slots == other.tile_slots;
|
||||
@ -258,6 +284,11 @@ bool ImageLoader::equals(const ImageLoader *a, const ImageLoader *b)
|
||||
}
|
||||
}
|
||||
|
||||
bool ImageLoader::is_vdb_loader() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Image Manager */
|
||||
|
||||
ImageManager::ImageManager(const DeviceInfo &info)
|
||||
|
@ -39,6 +39,7 @@ class Progress;
|
||||
class RenderStats;
|
||||
class Scene;
|
||||
class ColorSpaceProcessor;
|
||||
class VDBImageLoader;
|
||||
|
||||
/* Image Parameters */
|
||||
class ImageParams {
|
||||
@ -124,6 +125,8 @@ class ImageLoader {
|
||||
virtual bool equals(const ImageLoader &other) const = 0;
|
||||
static bool equals(const ImageLoader *a, const ImageLoader *b);
|
||||
|
||||
virtual bool is_vdb_loader() const;
|
||||
|
||||
/* Work around for no RTTI. */
|
||||
};
|
||||
|
||||
@ -149,6 +152,8 @@ class ImageHandle {
|
||||
int svm_slot(const int tile_index = 0) const;
|
||||
device_texture *image_memory(const int tile_index = 0) const;
|
||||
|
||||
VDBImageLoader *vdb_loader(const int tile_index = 0) const;
|
||||
|
||||
protected:
|
||||
vector<int> tile_slots;
|
||||
ImageManager *manager;
|
||||
|
@ -185,4 +185,16 @@ void VDBImageLoader::cleanup()
|
||||
#endif
|
||||
}
|
||||
|
||||
bool VDBImageLoader::is_vdb_loader() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef WITH_OPENVDB
|
||||
openvdb::GridBase::ConstPtr VDBImageLoader::get_grid()
|
||||
{
|
||||
return grid;
|
||||
}
|
||||
#endif
|
||||
|
||||
CCL_NAMESPACE_END
|
||||
|
@ -43,6 +43,12 @@ class VDBImageLoader : public ImageLoader {
|
||||
|
||||
virtual void cleanup() override;
|
||||
|
||||
virtual bool is_vdb_loader() const override;
|
||||
|
||||
#ifdef WITH_OPENVDB
|
||||
openvdb::GridBase::ConstPtr get_grid();
|
||||
#endif
|
||||
|
||||
protected:
|
||||
string grid_name;
|
||||
#ifdef WITH_OPENVDB
|
||||
|
@ -15,34 +15,25 @@
|
||||
*/
|
||||
|
||||
#include "render/attribute.h"
|
||||
#include "render/image_vdb.h"
|
||||
#include "render/mesh.h"
|
||||
#include "render/scene.h"
|
||||
|
||||
#ifdef WITH_OPENVDB
|
||||
# include <openvdb/tools/Dense.h>
|
||||
# include <openvdb/tools/GridTransformer.h>
|
||||
# include <openvdb/tools/Morphology.h>
|
||||
#endif
|
||||
|
||||
#include "util/util_foreach.h"
|
||||
#include "util/util_hash.h"
|
||||
#include "util/util_logging.h"
|
||||
#include "util/util_openvdb.h"
|
||||
#include "util/util_progress.h"
|
||||
#include "util/util_types.h"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
const int64_t VOXEL_INDEX_NONE = -1;
|
||||
|
||||
static int64_t compute_voxel_index(const int3 &resolution, int64_t x, int64_t y, int64_t z)
|
||||
{
|
||||
if (x < 0 || x >= resolution.x) {
|
||||
return VOXEL_INDEX_NONE;
|
||||
}
|
||||
else if (y < 0 || y >= resolution.y) {
|
||||
return VOXEL_INDEX_NONE;
|
||||
}
|
||||
else if (z < 0 || z >= resolution.z) {
|
||||
return VOXEL_INDEX_NONE;
|
||||
}
|
||||
|
||||
return x + y * resolution.x + z * resolution.x * resolution.y;
|
||||
}
|
||||
|
||||
struct QuadData {
|
||||
int v0, v1, v2, v3;
|
||||
|
||||
@ -123,122 +114,146 @@ static void create_quad(int3 corners[8],
|
||||
quads.push_back(quad);
|
||||
}
|
||||
|
||||
struct VolumeParams {
|
||||
int3 resolution;
|
||||
float3 cell_size;
|
||||
float3 start_point;
|
||||
int pad_size;
|
||||
};
|
||||
|
||||
static const int CUBE_SIZE = 8;
|
||||
|
||||
/* Create a mesh from a volume.
|
||||
*
|
||||
* The way the algorithm works is as follows:
|
||||
*
|
||||
* - The coordinates of active voxels from a dense volume (or 3d image) are
|
||||
* gathered inside an auxiliary volume.
|
||||
* - Each set of coordinates of an CUBE_SIZE cube are mapped to the same
|
||||
* coordinate of the auxiliary volume.
|
||||
* - Quads are created between active and non-active voxels in the auxiliary
|
||||
* volume to generate a tight mesh around the volume.
|
||||
* - The topologies of input OpenVDB grids are merged into a temporary grid.
|
||||
* - Voxels of the temporary grid are dilated to account for the padding necessary for volume
|
||||
* sampling.
|
||||
* - Quads are created on the boundary between active and inactive leaf nodes of the temporary
|
||||
* grid.
|
||||
*/
|
||||
class VolumeMeshBuilder {
|
||||
/* Auxiliary volume that is used to check if a node already added. */
|
||||
vector<char> grid;
|
||||
|
||||
/* The resolution of the auxiliary volume, set to be equal to 1/CUBE_SIZE
|
||||
* of the original volume on each axis. */
|
||||
int3 res;
|
||||
|
||||
size_t number_of_nodes;
|
||||
|
||||
/* Offset due to padding in the original grid. Padding will transform the
|
||||
* coordinates of the original grid from 0...res to -padding...res+padding,
|
||||
* so some coordinates are negative, and we need to properly account for
|
||||
* them. */
|
||||
int3 pad_offset;
|
||||
|
||||
VolumeParams *params;
|
||||
|
||||
public:
|
||||
VolumeMeshBuilder(VolumeParams *volume_params);
|
||||
#ifdef WITH_OPENVDB
|
||||
/* use a MaskGrid to store the topology to save memory */
|
||||
openvdb::MaskGrid::Ptr topology_grid;
|
||||
openvdb::CoordBBox bbox;
|
||||
#endif
|
||||
bool first_grid;
|
||||
|
||||
void add_node(int x, int y, int z);
|
||||
VolumeMeshBuilder();
|
||||
|
||||
void add_node_with_padding(int x, int y, int z);
|
||||
#ifdef WITH_OPENVDB
|
||||
void add_grid(openvdb::GridBase::ConstPtr grid, bool do_clipping, float volume_clipping);
|
||||
#endif
|
||||
|
||||
void create_mesh(vector<float3> &vertices, vector<int> &indices, vector<float3> &face_normals);
|
||||
void add_padding(int pad_size);
|
||||
|
||||
void create_mesh(vector<float3> &vertices,
|
||||
vector<int> &indices,
|
||||
vector<float3> &face_normals,
|
||||
const float face_overlap_avoidance);
|
||||
|
||||
private:
|
||||
void generate_vertices_and_quads(vector<int3> &vertices_is, vector<QuadData> &quads);
|
||||
|
||||
void convert_object_space(const vector<int3> &vertices, vector<float3> &out_vertices);
|
||||
void convert_object_space(const vector<int3> &vertices,
|
||||
vector<float3> &out_vertices,
|
||||
const float face_overlap_avoidance);
|
||||
|
||||
void convert_quads_to_tris(const vector<QuadData> &quads,
|
||||
vector<int> &tris,
|
||||
vector<float3> &face_normals);
|
||||
|
||||
bool empty_grid() const;
|
||||
|
||||
#ifdef WITH_OPENVDB
|
||||
template <typename GridType>
|
||||
void merge_grid(openvdb::GridBase::ConstPtr grid, bool do_clipping, float volume_clipping)
|
||||
{
|
||||
typename GridType::ConstPtr typed_grid = openvdb::gridConstPtrCast<GridType>(grid);
|
||||
|
||||
if (do_clipping) {
|
||||
using ValueType = typename GridType::ValueType;
|
||||
typename GridType::Ptr copy = typed_grid->deepCopy();
|
||||
typename GridType::ValueOnIter iter = copy->beginValueOn();
|
||||
|
||||
for (; iter; ++iter) {
|
||||
if (iter.getValue() < ValueType(volume_clipping)) {
|
||||
iter.setValueOff();
|
||||
}
|
||||
}
|
||||
|
||||
typed_grid = copy;
|
||||
}
|
||||
|
||||
topology_grid->topologyUnion(*typed_grid);
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
VolumeMeshBuilder::VolumeMeshBuilder(VolumeParams *volume_params)
|
||||
VolumeMeshBuilder::VolumeMeshBuilder()
|
||||
{
|
||||
params = volume_params;
|
||||
number_of_nodes = 0;
|
||||
|
||||
const int64_t x = divide_up(params->resolution.x, CUBE_SIZE);
|
||||
const int64_t y = divide_up(params->resolution.y, CUBE_SIZE);
|
||||
const int64_t z = divide_up(params->resolution.z, CUBE_SIZE);
|
||||
|
||||
/* Adding 2*pad_size since we pad in both positive and negative directions
|
||||
* along the axis. */
|
||||
const int64_t px = divide_up(params->resolution.x + 2 * params->pad_size, CUBE_SIZE);
|
||||
const int64_t py = divide_up(params->resolution.y + 2 * params->pad_size, CUBE_SIZE);
|
||||
const int64_t pz = divide_up(params->resolution.z + 2 * params->pad_size, CUBE_SIZE);
|
||||
|
||||
res = make_int3(px, py, pz);
|
||||
pad_offset = make_int3(px - x, py - y, pz - z);
|
||||
|
||||
grid.resize(px * py * pz, 0);
|
||||
first_grid = true;
|
||||
}
|
||||
|
||||
void VolumeMeshBuilder::add_node(int x, int y, int z)
|
||||
#ifdef WITH_OPENVDB
|
||||
void VolumeMeshBuilder::add_grid(openvdb::GridBase::ConstPtr grid, bool do_clipping, float volume_clipping)
|
||||
{
|
||||
/* Map coordinates to index space. */
|
||||
const int index_x = (x / CUBE_SIZE) + pad_offset.x;
|
||||
const int index_y = (y / CUBE_SIZE) + pad_offset.y;
|
||||
const int index_z = (z / CUBE_SIZE) + pad_offset.z;
|
||||
|
||||
assert((index_x >= 0) && (index_y >= 0) && (index_z >= 0));
|
||||
|
||||
const int64_t index = compute_voxel_index(res, index_x, index_y, index_z);
|
||||
if (index == VOXEL_INDEX_NONE) {
|
||||
return;
|
||||
/* set the transform of our grid from the first one */
|
||||
if (first_grid) {
|
||||
topology_grid = openvdb::MaskGrid::create();
|
||||
topology_grid->setTransform(grid->transform().copy());
|
||||
first_grid = false;
|
||||
}
|
||||
/* if the transforms do not match, we need to resample one of the grids so that
|
||||
* its index space registers with that of the other, here we resample our mask
|
||||
* grid so memory usage is kept low */
|
||||
else if (topology_grid->transform() != grid->transform()) {
|
||||
openvdb::MaskGrid::Ptr temp_grid = topology_grid->copyWithNewTree();
|
||||
temp_grid->setTransform(grid->transform().copy());
|
||||
openvdb::tools::resampleToMatch<openvdb::tools::BoxSampler>(*topology_grid, *temp_grid);
|
||||
topology_grid = temp_grid;
|
||||
topology_grid->setTransform(grid->transform().copy());
|
||||
}
|
||||
|
||||
/* We already have a node here. */
|
||||
if (grid[index] == 1) {
|
||||
return;
|
||||
if (grid->isType<openvdb::FloatGrid>()) {
|
||||
merge_grid<openvdb::FloatGrid>(grid, do_clipping, volume_clipping);
|
||||
}
|
||||
else if (grid->isType<openvdb::Vec3fGrid>()) {
|
||||
merge_grid<openvdb::Vec3fGrid>(grid, do_clipping, volume_clipping);
|
||||
}
|
||||
else if (grid->isType<openvdb::Vec4fGrid>()) {
|
||||
merge_grid<openvdb::Vec4fGrid>(grid, do_clipping, volume_clipping);
|
||||
}
|
||||
else if (grid->isType<openvdb::BoolGrid>()) {
|
||||
merge_grid<openvdb::BoolGrid>(grid, do_clipping, volume_clipping);
|
||||
}
|
||||
else if (grid->isType<openvdb::DoubleGrid>()) {
|
||||
merge_grid<openvdb::DoubleGrid>(grid, do_clipping, volume_clipping);
|
||||
}
|
||||
else if (grid->isType<openvdb::Int32Grid>()) {
|
||||
merge_grid<openvdb::Int32Grid>(grid, do_clipping, volume_clipping);
|
||||
}
|
||||
else if (grid->isType<openvdb::Int64Grid>()) {
|
||||
merge_grid<openvdb::Int64Grid>(grid, do_clipping, volume_clipping);
|
||||
}
|
||||
else if (grid->isType<openvdb::Vec3IGrid>()) {
|
||||
merge_grid<openvdb::Vec3IGrid>(grid, do_clipping, volume_clipping);
|
||||
}
|
||||
else if (grid->isType<openvdb::Vec3dGrid>()) {
|
||||
merge_grid<openvdb::Vec3dGrid>(grid, do_clipping, volume_clipping);
|
||||
}
|
||||
else if (grid->isType<openvdb::MaskGrid>()) {
|
||||
topology_grid->topologyUnion(*openvdb::gridConstPtrCast<openvdb::MaskGrid>(grid));
|
||||
}
|
||||
|
||||
++number_of_nodes;
|
||||
|
||||
grid[index] = 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
void VolumeMeshBuilder::add_node_with_padding(int x, int y, int z)
|
||||
void VolumeMeshBuilder::add_padding(int pad_size)
|
||||
{
|
||||
for (int px = x - params->pad_size; px < x + params->pad_size; ++px) {
|
||||
for (int py = y - params->pad_size; py < y + params->pad_size; ++py) {
|
||||
for (int pz = z - params->pad_size; pz < z + params->pad_size; ++pz) {
|
||||
add_node(px, py, pz);
|
||||
}
|
||||
}
|
||||
}
|
||||
#ifdef WITH_OPENVDB
|
||||
openvdb::tools::dilateVoxels(topology_grid->tree(), pad_size);
|
||||
#else
|
||||
(void)pad_size;
|
||||
#endif
|
||||
}
|
||||
|
||||
void VolumeMeshBuilder::create_mesh(vector<float3> &vertices,
|
||||
vector<int> &indices,
|
||||
vector<float3> &face_normals)
|
||||
vector<float3> &face_normals,
|
||||
const float face_overlap_avoidance)
|
||||
{
|
||||
/* We create vertices in index space (is), and only convert them to object
|
||||
* space when done. */
|
||||
@ -247,7 +262,7 @@ void VolumeMeshBuilder::create_mesh(vector<float3> &vertices,
|
||||
|
||||
generate_vertices_and_quads(vertices_is, quads);
|
||||
|
||||
convert_object_space(vertices_is, vertices);
|
||||
convert_object_space(vertices_is, vertices, face_overlap_avoidance);
|
||||
|
||||
convert_quads_to_tris(quads, indices, face_normals);
|
||||
}
|
||||
@ -255,23 +270,21 @@ void VolumeMeshBuilder::create_mesh(vector<float3> &vertices,
|
||||
void VolumeMeshBuilder::generate_vertices_and_quads(vector<ccl::int3> &vertices_is,
|
||||
vector<QuadData> &quads)
|
||||
{
|
||||
#ifdef WITH_OPENVDB
|
||||
const openvdb::MaskGrid::TreeType &tree = topology_grid->tree();
|
||||
tree.evalLeafBoundingBox(bbox);
|
||||
|
||||
const int3 resolution = make_int3(bbox.dim().x(), bbox.dim().y(), bbox.dim().z());
|
||||
|
||||
unordered_map<size_t, int> used_verts;
|
||||
|
||||
for (int z = 0; z < res.z; ++z) {
|
||||
for (int y = 0; y < res.y; ++y) {
|
||||
for (int x = 0; x < res.x; ++x) {
|
||||
int64_t voxel_index = compute_voxel_index(res, x, y, z);
|
||||
if (grid[voxel_index] == 0) {
|
||||
continue;
|
||||
}
|
||||
for (auto iter = tree.cbeginLeaf(); iter; ++iter) {
|
||||
openvdb::CoordBBox leaf_bbox = iter->getNodeBoundingBox();
|
||||
/* +1 to convert from exclusive to include bounds. */
|
||||
leaf_bbox.max() = leaf_bbox.max().offsetBy(1);
|
||||
|
||||
/* Compute min and max coords of the node in index space. */
|
||||
int3 min = make_int3((x - pad_offset.x) * CUBE_SIZE,
|
||||
(y - pad_offset.y) * CUBE_SIZE,
|
||||
(z - pad_offset.z) * CUBE_SIZE);
|
||||
|
||||
/* Maximum is just CUBE_SIZE voxels away from minimum on each axis. */
|
||||
int3 max = make_int3(min.x + CUBE_SIZE, min.y + CUBE_SIZE, min.z + CUBE_SIZE);
|
||||
int3 min = make_int3(leaf_bbox.min().x(), leaf_bbox.min().y(), leaf_bbox.min().z());
|
||||
int3 max = make_int3(leaf_bbox.max().x(), leaf_bbox.max().y(), leaf_bbox.max().z());
|
||||
|
||||
int3 corners[8] = {
|
||||
make_int3(min[0], min[1], min[2]),
|
||||
@ -284,56 +297,70 @@ void VolumeMeshBuilder::generate_vertices_and_quads(vector<ccl::int3> &vertices_
|
||||
make_int3(min[0], max[1], max[2]),
|
||||
};
|
||||
|
||||
/* Only create a quad if on the border between an active and
|
||||
* an inactive node.
|
||||
/* Only create a quad if on the border between an active and an inactive leaf.
|
||||
*
|
||||
* We verify that a leaf exists by probing a coordinate that is at its center,
|
||||
* to do so we compute the center of the current leaf and offset this coordinate
|
||||
* by the size of a leaf in each direction.
|
||||
*/
|
||||
static const int LEAF_DIM = openvdb::MaskGrid::TreeType::LeafNodeType::DIM;
|
||||
auto center = leaf_bbox.min() + openvdb::Coord(LEAF_DIM / 2);
|
||||
|
||||
voxel_index = compute_voxel_index(res, x - 1, y, z);
|
||||
if (voxel_index == VOXEL_INDEX_NONE || grid[voxel_index] == 0) {
|
||||
create_quad(corners, vertices_is, quads, res, used_verts, QUAD_X_MIN);
|
||||
if (!tree.probeLeaf(openvdb::Coord(center.x() - LEAF_DIM, center.y(), center.z()))) {
|
||||
create_quad(corners, vertices_is, quads, resolution, used_verts, QUAD_X_MIN);
|
||||
}
|
||||
|
||||
voxel_index = compute_voxel_index(res, x + 1, y, z);
|
||||
if (voxel_index == VOXEL_INDEX_NONE || grid[voxel_index] == 0) {
|
||||
create_quad(corners, vertices_is, quads, res, used_verts, QUAD_X_MAX);
|
||||
if (!tree.probeLeaf(openvdb::Coord(center.x() + LEAF_DIM, center.y(), center.z()))) {
|
||||
create_quad(corners, vertices_is, quads, resolution, used_verts, QUAD_X_MAX);
|
||||
}
|
||||
|
||||
voxel_index = compute_voxel_index(res, x, y - 1, z);
|
||||
if (voxel_index == VOXEL_INDEX_NONE || grid[voxel_index] == 0) {
|
||||
create_quad(corners, vertices_is, quads, res, used_verts, QUAD_Y_MIN);
|
||||
if (!tree.probeLeaf(openvdb::Coord(center.x(), center.y() - LEAF_DIM, center.z()))) {
|
||||
create_quad(corners, vertices_is, quads, resolution, used_verts, QUAD_Y_MIN);
|
||||
}
|
||||
|
||||
voxel_index = compute_voxel_index(res, x, y + 1, z);
|
||||
if (voxel_index == VOXEL_INDEX_NONE || grid[voxel_index] == 0) {
|
||||
create_quad(corners, vertices_is, quads, res, used_verts, QUAD_Y_MAX);
|
||||
if (!tree.probeLeaf(openvdb::Coord(center.x(), center.y() + LEAF_DIM, center.z()))) {
|
||||
create_quad(corners, vertices_is, quads, resolution, used_verts, QUAD_Y_MAX);
|
||||
}
|
||||
|
||||
voxel_index = compute_voxel_index(res, x, y, z - 1);
|
||||
if (voxel_index == VOXEL_INDEX_NONE || grid[voxel_index] == 0) {
|
||||
create_quad(corners, vertices_is, quads, res, used_verts, QUAD_Z_MIN);
|
||||
if (!tree.probeLeaf(openvdb::Coord(center.x(), center.y(), center.z() - LEAF_DIM))) {
|
||||
create_quad(corners, vertices_is, quads, resolution, used_verts, QUAD_Z_MIN);
|
||||
}
|
||||
|
||||
voxel_index = compute_voxel_index(res, x, y, z + 1);
|
||||
if (voxel_index == VOXEL_INDEX_NONE || grid[voxel_index] == 0) {
|
||||
create_quad(corners, vertices_is, quads, res, used_verts, QUAD_Z_MAX);
|
||||
}
|
||||
}
|
||||
if (!tree.probeLeaf(openvdb::Coord(center.x(), center.y(), center.z() + LEAF_DIM))) {
|
||||
create_quad(corners, vertices_is, quads, resolution, used_verts, QUAD_Z_MAX);
|
||||
}
|
||||
}
|
||||
#else
|
||||
(void)vertices_is;
|
||||
(void)quads;
|
||||
#endif
|
||||
}
|
||||
|
||||
void VolumeMeshBuilder::convert_object_space(const vector<int3> &vertices,
|
||||
vector<float3> &out_vertices)
|
||||
vector<float3> &out_vertices,
|
||||
const float face_overlap_avoidance)
|
||||
{
|
||||
#ifdef WITH_OPENVDB
|
||||
/* compute the offset for the face overlap avoidance */
|
||||
bbox = topology_grid->evalActiveVoxelBoundingBox();
|
||||
openvdb::Coord dim = bbox.dim();
|
||||
|
||||
float3 cell_size = make_float3(1.0f / dim.x(), 1.0f / dim.y(), 1.0f / dim.z());
|
||||
float3 point_offset = cell_size * face_overlap_avoidance;
|
||||
|
||||
out_vertices.reserve(vertices.size());
|
||||
|
||||
for (size_t i = 0; i < vertices.size(); ++i) {
|
||||
float3 vertex = make_float3(vertices[i].x, vertices[i].y, vertices[i].z);
|
||||
vertex *= params->cell_size;
|
||||
vertex += params->start_point;
|
||||
|
||||
out_vertices.push_back(vertex);
|
||||
openvdb::math::Vec3d p = topology_grid->indexToWorld(
|
||||
openvdb::math::Vec3d(vertices[i].x, vertices[i].y, vertices[i].z));
|
||||
float3 vertex = make_float3((float)p.x(), (float)p.y(), (float)p.z());
|
||||
out_vertices.push_back(vertex + point_offset);
|
||||
}
|
||||
#else
|
||||
(void)vertices;
|
||||
(void)out_vertices;
|
||||
(void)face_overlap_avoidance;
|
||||
#endif
|
||||
}
|
||||
|
||||
void VolumeMeshBuilder::convert_quads_to_tris(const vector<QuadData> &quads,
|
||||
@ -359,57 +386,115 @@ void VolumeMeshBuilder::convert_quads_to_tris(const vector<QuadData> &quads,
|
||||
}
|
||||
}
|
||||
|
||||
/* ************************************************************************** */
|
||||
bool VolumeMeshBuilder::empty_grid() const
|
||||
{
|
||||
#ifdef WITH_OPENVDB
|
||||
return !topology_grid || topology_grid->tree().leafCount() == 0;
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
struct VoxelAttributeGrid {
|
||||
float *data;
|
||||
int channels;
|
||||
};
|
||||
#ifdef WITH_OPENVDB
|
||||
template<typename GridType>
|
||||
static openvdb::GridBase::ConstPtr openvdb_grid_from_device_texture(device_texture *image_memory,
|
||||
float volume_clipping,
|
||||
Transform transform_3d)
|
||||
{
|
||||
using ValueType = typename GridType::ValueType;
|
||||
|
||||
openvdb::CoordBBox dense_bbox(0,
|
||||
0,
|
||||
0,
|
||||
image_memory->data_width - 1,
|
||||
image_memory->data_height - 1,
|
||||
image_memory->data_depth - 1);
|
||||
openvdb::tools::Dense<ValueType, openvdb::tools::MemoryLayout::LayoutXYZ> dense(
|
||||
dense_bbox, static_cast<ValueType *>(image_memory->host_pointer));
|
||||
|
||||
typename GridType::Ptr sparse = GridType::create(ValueType(0.0f));
|
||||
openvdb::tools::copyFromDense(dense, *sparse, ValueType(volume_clipping));
|
||||
|
||||
/* copyFromDense will remove any leaf node that contains constant data and replace it with a tile,
|
||||
* however, we need to preserve the leaves in order to generate the mesh, so revoxelize the leaves
|
||||
* that were pruned. This should not affect areas that were skipped due to the volume_clipping parameter. */
|
||||
sparse->tree().voxelizeActiveTiles();
|
||||
|
||||
/* Compute index to world matrix. */
|
||||
float3 voxel_size = make_float3(1.0f / image_memory->data_width, 1.0f / image_memory->data_height, 1.0f / image_memory->data_depth);
|
||||
|
||||
transform_3d = transform_inverse(transform_3d);
|
||||
|
||||
openvdb::Mat4R index_to_world_mat((double)(voxel_size.x * transform_3d[0][0]), 0.0, 0.0, 0.0,
|
||||
0.0, (double)(voxel_size.y * transform_3d[1][1]), 0.0, 0.0,
|
||||
0.0, 0.0, (double)(voxel_size.z * transform_3d[2][2]), 0.0,
|
||||
(double)transform_3d[0][3], (double)transform_3d[1][3], (double)transform_3d[2][3], 1.0);
|
||||
|
||||
openvdb::math::Transform::Ptr index_to_world_tfm = openvdb::math::Transform::createLinearTransform(index_to_world_mat);
|
||||
|
||||
sparse->setTransform(index_to_world_tfm);
|
||||
|
||||
return sparse;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* ************************************************************************** */
|
||||
|
||||
void GeometryManager::create_volume_mesh(Mesh *mesh, Progress &progress)
|
||||
{
|
||||
string msg = string_printf("Computing Volume Mesh %s", mesh->name.c_str());
|
||||
progress.set_status("Updating Mesh", msg);
|
||||
|
||||
vector<VoxelAttributeGrid> voxel_grids;
|
||||
|
||||
/* Compute volume parameters. */
|
||||
VolumeParams volume_params;
|
||||
volume_params.resolution = make_int3(0, 0, 0);
|
||||
|
||||
Transform transform = transform_identity();
|
||||
VolumeMeshBuilder builder;
|
||||
|
||||
#ifdef WITH_OPENVDB
|
||||
foreach (Attribute &attr, mesh->attributes.attributes) {
|
||||
if (attr.element != ATTR_ELEMENT_VOXEL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
bool do_clipping = false;
|
||||
|
||||
ImageHandle &handle = attr.data_voxel();
|
||||
|
||||
/* Try building from OpenVDB grid directly. */
|
||||
VDBImageLoader *vdb_loader = handle.vdb_loader();
|
||||
openvdb::GridBase::ConstPtr grid;
|
||||
if (vdb_loader) {
|
||||
grid = vdb_loader->get_grid();
|
||||
|
||||
/* If building from an OpenVDB grid, we need to manually clip the values. */
|
||||
do_clipping = true;
|
||||
}
|
||||
|
||||
/* Else fall back to creating an OpenVDB grid from the dense volume data. */
|
||||
if (!grid) {
|
||||
device_texture *image_memory = handle.image_memory();
|
||||
int3 resolution = make_int3(
|
||||
image_memory->data_width, image_memory->data_height, image_memory->data_depth);
|
||||
|
||||
if (volume_params.resolution == make_int3(0, 0, 0)) {
|
||||
volume_params.resolution = resolution;
|
||||
if (image_memory->data_elements == 1) {
|
||||
grid = openvdb_grid_from_device_texture<openvdb::FloatGrid>(image_memory,
|
||||
mesh->volume_clipping,
|
||||
handle.metadata().transform_3d);
|
||||
}
|
||||
else if (volume_params.resolution != resolution) {
|
||||
/* TODO: support this as it's common for OpenVDB. */
|
||||
VLOG(1) << "Can't create accurate volume mesh, all voxel grid resolutions must be equal\n";
|
||||
continue;
|
||||
else if (image_memory->data_elements == 3) {
|
||||
grid = openvdb_grid_from_device_texture<openvdb::Vec3fGrid>(image_memory,
|
||||
mesh->volume_clipping,
|
||||
handle.metadata().transform_3d);
|
||||
}
|
||||
|
||||
VoxelAttributeGrid voxel_grid;
|
||||
voxel_grid.data = static_cast<float *>(image_memory->host_pointer);
|
||||
voxel_grid.channels = image_memory->data_elements;
|
||||
voxel_grids.push_back(voxel_grid);
|
||||
|
||||
/* TODO: support multiple transforms. */
|
||||
if (image_memory->info.use_transform_3d) {
|
||||
transform = image_memory->info.transform_3d;
|
||||
else if (image_memory->data_elements == 4) {
|
||||
grid = openvdb_grid_from_device_texture<openvdb::Vec4fGrid>(image_memory,
|
||||
mesh->volume_clipping,
|
||||
handle.metadata().transform_3d);
|
||||
}
|
||||
}
|
||||
|
||||
if (voxel_grids.empty()) {
|
||||
if (grid) {
|
||||
builder.add_grid(grid, do_clipping, mesh->volume_clipping);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (builder.empty_grid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -438,56 +523,19 @@ void GeometryManager::create_volume_mesh(Mesh *mesh, Progress &progress)
|
||||
return;
|
||||
}
|
||||
|
||||
/* Compute start point and cell size from transform. */
|
||||
const int3 resolution = volume_params.resolution;
|
||||
float3 start_point = make_float3(0.0f, 0.0f, 0.0f);
|
||||
float3 cell_size = make_float3(1.0f / resolution.x, 1.0f / resolution.y, 1.0f / resolution.z);
|
||||
|
||||
/* TODO: support arbitrary transforms, not just scale + translate. */
|
||||
const Transform itfm = transform_inverse(transform);
|
||||
start_point = transform_point(&itfm, start_point);
|
||||
cell_size = transform_direction(&itfm, cell_size);
|
||||
builder.add_padding(pad_size);
|
||||
|
||||
/* Slightly offset vertex coordinates to avoid overlapping faces with other
|
||||
* volumes or meshes. The proper solution would be to improve intersection in
|
||||
* the kernel to support robust handling of multiple overlapping faces or use
|
||||
* an all-hit intersection similar to shadows. */
|
||||
const float3 face_overlap_avoidance = cell_size * 0.1f *
|
||||
hash_uint_to_float(hash_string(mesh->name.c_str()));
|
||||
|
||||
volume_params.start_point = start_point + face_overlap_avoidance;
|
||||
volume_params.cell_size = cell_size;
|
||||
volume_params.pad_size = pad_size;
|
||||
|
||||
/* Build bounding mesh around non-empty volume cells. */
|
||||
VolumeMeshBuilder builder(&volume_params);
|
||||
const float clipping = mesh->volume_clipping;
|
||||
|
||||
for (int z = 0; z < resolution.z; ++z) {
|
||||
for (int y = 0; y < resolution.y; ++y) {
|
||||
for (int x = 0; x < resolution.x; ++x) {
|
||||
int64_t voxel_index = compute_voxel_index(resolution, x, y, z);
|
||||
|
||||
for (size_t i = 0; i < voxel_grids.size(); ++i) {
|
||||
const VoxelAttributeGrid &voxel_grid = voxel_grids[i];
|
||||
const int channels = voxel_grid.channels;
|
||||
|
||||
for (int c = 0; c < channels; c++) {
|
||||
if (voxel_grid.data[voxel_index * channels + c] >= clipping) {
|
||||
builder.add_node_with_padding(x, y, z);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const float face_overlap_avoidance = 0.1f * hash_uint_to_float(hash_string(mesh->name.c_str()));
|
||||
|
||||
/* Create mesh. */
|
||||
vector<float3> vertices;
|
||||
vector<int> indices;
|
||||
vector<float3> face_normals;
|
||||
builder.create_mesh(vertices, indices, face_normals);
|
||||
builder.create_mesh(vertices, indices, face_normals, face_overlap_avoidance);
|
||||
|
||||
mesh->clear(true);
|
||||
mesh->reserve_mesh(vertices.size(), indices.size() / 3);
|
||||
@ -514,10 +562,6 @@ void GeometryManager::create_volume_mesh(Mesh *mesh, Progress &progress)
|
||||
indices.size() * sizeof(int)) /
|
||||
(1024.0 * 1024.0)
|
||||
<< "Mb.";
|
||||
|
||||
VLOG(1) << "Memory usage volume grid: "
|
||||
<< (resolution.x * resolution.y * resolution.z * sizeof(float)) / (1024.0 * 1024.0)
|
||||
<< "Mb.";
|
||||
}
|
||||
|
||||
CCL_NAMESPACE_END
|
||||
|
@ -88,6 +88,7 @@ set(SRC_HEADERS
|
||||
util_murmurhash.h
|
||||
util_openimagedenoise.h
|
||||
util_opengl.h
|
||||
util_openvdb.h
|
||||
util_optimization.h
|
||||
util_param.h
|
||||
util_path.h
|
||||
|
32
intern/cycles/util/util_openvdb.h
Normal file
32
intern/cycles/util/util_openvdb.h
Normal file
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2011-2020 Blender Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef __UTIL_OPENVDB_H__
|
||||
#define __UTIL_OPENVDB_H__
|
||||
|
||||
#ifdef WITH_OPENVDB
|
||||
# include <openvdb/openvdb.h>
|
||||
|
||||
namespace openvdb {
|
||||
|
||||
using Vec4fTree = tree::Tree4<Vec4f, 5, 4, 3>::Type;
|
||||
using Vec4fGrid = Grid<Vec4fTree>;
|
||||
|
||||
}; // namespace openvdb
|
||||
|
||||
#endif
|
||||
|
||||
#endif /* __UTIL_OPENVDB_H__ */
|
@ -190,6 +190,9 @@ typedef struct PChart {
|
||||
LinearSolver *context;
|
||||
float *abf_alpha;
|
||||
PVert *pin1, *pin2;
|
||||
PVert *single_pin;
|
||||
float single_pin_area;
|
||||
float single_pin_uv[2];
|
||||
} lscm;
|
||||
struct PChartPack {
|
||||
float rescale, area;
|
||||
@ -467,6 +470,17 @@ static void p_chart_uv_bbox(PChart *chart, float minv[2], float maxv[2])
|
||||
}
|
||||
}
|
||||
|
||||
static float p_chart_uv_area(PChart *chart)
|
||||
{
|
||||
float area = 0.0f;
|
||||
|
||||
for (PFace *f = chart->faces; f; f = f->nextlink) {
|
||||
area += fabsf(p_face_uv_area_signed(f));
|
||||
}
|
||||
|
||||
return area;
|
||||
}
|
||||
|
||||
static void p_chart_uv_scale(PChart *chart, float scale)
|
||||
{
|
||||
PVert *v;
|
||||
@ -3176,7 +3190,7 @@ static void p_chart_lscm_begin(PChart *chart, PBool live, PBool abf)
|
||||
}
|
||||
}
|
||||
|
||||
if ((live && (!select || !deselect)) || (npins == 1)) {
|
||||
if ((live && (!select || !deselect))) {
|
||||
chart->u.lscm.context = NULL;
|
||||
}
|
||||
else {
|
||||
@ -3185,6 +3199,16 @@ static void p_chart_lscm_begin(PChart *chart, PBool live, PBool abf)
|
||||
p_chart_topological_sanity_check(chart);
|
||||
#endif
|
||||
|
||||
if (npins == 1) {
|
||||
chart->u.lscm.single_pin_area = p_chart_uv_area(chart);
|
||||
for (v = chart->verts; v; v = v->nextlink) {
|
||||
if (v->flag & PVERT_PIN) {
|
||||
chart->u.lscm.single_pin = v;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (abf) {
|
||||
if (!p_chart_abf_solve(chart)) {
|
||||
param_warning("ABF solving failed: falling back to LSCM.\n");
|
||||
@ -3192,12 +3216,12 @@ static void p_chart_lscm_begin(PChart *chart, PBool live, PBool abf)
|
||||
}
|
||||
|
||||
if (npins <= 1) {
|
||||
/* not enough pins, lets find some ourself */
|
||||
/* No pins, let's find some ourself. */
|
||||
PEdge *outer;
|
||||
|
||||
p_chart_boundaries(chart, NULL, &outer);
|
||||
|
||||
/* outer can be NULL with non-finite coords. */
|
||||
/* Outer can be NULL with non-finite coords. */
|
||||
if (!(outer && p_chart_symmetry_pins(chart, outer, &pin1, &pin2))) {
|
||||
p_chart_extrema_verts(chart, &pin1, &pin2);
|
||||
}
|
||||
@ -3235,6 +3259,11 @@ static PBool p_chart_lscm_solve(PHandle *handle, PChart *chart)
|
||||
}
|
||||
}
|
||||
|
||||
if (chart->u.lscm.single_pin) {
|
||||
/* If only one pin, save area and pin for transform later. */
|
||||
copy_v2_v2(chart->u.lscm.single_pin_uv, chart->u.lscm.single_pin->uv);
|
||||
}
|
||||
|
||||
if (chart->u.lscm.pin1) {
|
||||
EIG_linear_solver_variable_lock(context, 2 * pin1->u.id);
|
||||
EIG_linear_solver_variable_lock(context, 2 * pin1->u.id + 1);
|
||||
@ -3358,6 +3387,25 @@ static PBool p_chart_lscm_solve(PHandle *handle, PChart *chart)
|
||||
return P_FALSE;
|
||||
}
|
||||
|
||||
static void p_chart_lscm_transform_single_pin(PChart *chart)
|
||||
{
|
||||
PVert *pin = chart->u.lscm.single_pin;
|
||||
|
||||
/* If only one pin, keep UV area the same. */
|
||||
const float new_area = p_chart_uv_area(chart);
|
||||
if (new_area > 0.0f) {
|
||||
const float scale = chart->u.lscm.single_pin_area / new_area;
|
||||
if (scale > 0.0f) {
|
||||
p_chart_uv_scale(chart, sqrtf(scale));
|
||||
}
|
||||
}
|
||||
|
||||
/* Translate to keep the pinned vertex in place. */
|
||||
float offset[2];
|
||||
sub_v2_v2v2(offset, chart->u.lscm.single_pin_uv, pin->uv);
|
||||
p_chart_uv_translate(chart, offset);
|
||||
}
|
||||
|
||||
static void p_chart_lscm_end(PChart *chart)
|
||||
{
|
||||
if (chart->u.lscm.context) {
|
||||
@ -3372,6 +3420,8 @@ static void p_chart_lscm_end(PChart *chart)
|
||||
chart->u.lscm.context = NULL;
|
||||
chart->u.lscm.pin1 = NULL;
|
||||
chart->u.lscm.pin2 = NULL;
|
||||
chart->u.lscm.single_pin = NULL;
|
||||
chart->u.lscm.single_pin_area = 0.0f;
|
||||
}
|
||||
|
||||
/* Stretch */
|
||||
@ -3781,6 +3831,23 @@ static void p_chart_rotate_minimum_area(PChart *chart)
|
||||
}
|
||||
}
|
||||
|
||||
static void p_chart_rotate_fit_aabb(PChart *chart)
|
||||
{
|
||||
float(*points)[2] = MEM_mallocN(sizeof(*points) * chart->nverts, __func__);
|
||||
|
||||
p_chart_uv_to_array(chart, points);
|
||||
|
||||
float angle = BLI_convexhull_aabb_fit_points_2d(points, chart->nverts);
|
||||
|
||||
MEM_freeN(points);
|
||||
|
||||
if (angle != 0.0f) {
|
||||
float mat[2][2];
|
||||
angle_to_mat2(mat, angle);
|
||||
p_chart_uv_transform(chart, mat);
|
||||
}
|
||||
}
|
||||
|
||||
/* Area Smoothing */
|
||||
|
||||
/* 2d bsp tree for inverse mapping - that's a bit silly */
|
||||
@ -4576,8 +4643,12 @@ void param_lscm_solve(ParamHandle *handle)
|
||||
if (result && !(chart->flag & PCHART_HAS_PINS)) {
|
||||
p_chart_rotate_minimum_area(chart);
|
||||
}
|
||||
else if (result && chart->u.lscm.single_pin) {
|
||||
p_chart_rotate_fit_aabb(chart);
|
||||
p_chart_lscm_transform_single_pin(chart);
|
||||
}
|
||||
|
||||
if (!result || (chart->u.lscm.pin1)) {
|
||||
if (!result || !(chart->flag & PCHART_HAS_PINS)) {
|
||||
p_chart_lscm_end(chart);
|
||||
}
|
||||
}
|
||||
@ -4692,28 +4763,13 @@ static void param_pack_rotate(ParamHandle *handle, bool ignore_pinned)
|
||||
PHandle *phandle = (PHandle *)handle;
|
||||
|
||||
for (i = 0; i < phandle->ncharts; i++) {
|
||||
float(*points)[2];
|
||||
float angle;
|
||||
|
||||
chart = phandle->charts[i];
|
||||
|
||||
if (ignore_pinned && (chart->flag & PCHART_HAS_PINS)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
points = MEM_mallocN(sizeof(*points) * chart->nverts, __func__);
|
||||
|
||||
p_chart_uv_to_array(chart, points);
|
||||
|
||||
angle = BLI_convexhull_aabb_fit_points_2d(points, chart->nverts);
|
||||
|
||||
MEM_freeN(points);
|
||||
|
||||
if (angle != 0.0f) {
|
||||
float mat[2][2];
|
||||
angle_to_mat2(mat, angle);
|
||||
p_chart_uv_transform(chart, mat);
|
||||
}
|
||||
p_chart_rotate_fit_aabb(chart);
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user