/* SPDX-License-Identifier: GPL-2.0-or-later */ /** \file * \ingroup bke */ #include "MEM_guardedalloc.h" #include "BLI_array.hh" #include "BLI_math_matrix.h" #include "BLI_math_vec_types.hh" #include "BLI_math_vector.h" #include "BLI_vector.hh" #include "DNA_volume_types.h" #include "BKE_volume.h" #include "BKE_volume_render.h" #ifdef WITH_OPENVDB # include # include #endif /* Dense Voxels */ #ifdef WITH_OPENVDB template static void extract_dense_voxels(const openvdb::GridBase &grid, const openvdb::CoordBBox bbox, VoxelType *r_voxels) { BLI_assert(grid.isType()); openvdb::tools::Dense dense(bbox, r_voxels); openvdb::tools::copyToDense(static_cast(grid), dense); } static void extract_dense_float_voxels(const VolumeGridType grid_type, const openvdb::GridBase &grid, const openvdb::CoordBBox &bbox, float *r_voxels) { switch (grid_type) { case VOLUME_GRID_BOOLEAN: return extract_dense_voxels(grid, bbox, r_voxels); case VOLUME_GRID_FLOAT: return extract_dense_voxels(grid, bbox, r_voxels); case VOLUME_GRID_DOUBLE: return extract_dense_voxels(grid, bbox, r_voxels); case VOLUME_GRID_INT: return extract_dense_voxels(grid, bbox, r_voxels); case VOLUME_GRID_INT64: return extract_dense_voxels(grid, bbox, r_voxels); case VOLUME_GRID_MASK: return extract_dense_voxels(grid, bbox, r_voxels); case VOLUME_GRID_VECTOR_FLOAT: return extract_dense_voxels( grid, bbox, reinterpret_cast(r_voxels)); case VOLUME_GRID_VECTOR_DOUBLE: return extract_dense_voxels( grid, bbox, reinterpret_cast(r_voxels)); case VOLUME_GRID_VECTOR_INT: return extract_dense_voxels( grid, bbox, reinterpret_cast(r_voxels)); case VOLUME_GRID_POINTS: case VOLUME_GRID_UNKNOWN: /* Zero channels to copy. */ break; } } static void create_texture_to_object_matrix(const openvdb::Mat4d &grid_transform, const openvdb::CoordBBox &bbox, float r_texture_to_object[4][4]) { float index_to_object[4][4]; memcpy(index_to_object, openvdb::Mat4s(grid_transform).asPointer(), sizeof(index_to_object)); float texture_to_index[4][4]; const openvdb::Vec3f loc = bbox.min().asVec3s(); const openvdb::Vec3f size = bbox.dim().asVec3s(); size_to_mat4(texture_to_index, size.asV()); copy_v3_v3(texture_to_index[3], loc.asV()); mul_m4_m4m4(r_texture_to_object, index_to_object, texture_to_index); } #endif bool BKE_volume_grid_dense_floats(const Volume *volume, const VolumeGrid *volume_grid, DenseFloatVolumeGrid *r_dense_grid) { #ifdef WITH_OPENVDB const VolumeGridType grid_type = BKE_volume_grid_type(volume_grid); openvdb::GridBase::ConstPtr grid = BKE_volume_grid_openvdb_for_read(volume, volume_grid); const openvdb::CoordBBox bbox = grid->evalActiveVoxelBoundingBox(); if (bbox.empty()) { return false; } const openvdb::Vec3i resolution = bbox.dim().asVec3i(); const int64_t num_voxels = int64_t(resolution[0]) * int64_t(resolution[1]) * int64_t(resolution[2]); const int channels = BKE_volume_grid_channels(volume_grid); const int elem_size = sizeof(float) * channels; float *voxels = static_cast(MEM_malloc_arrayN(num_voxels, elem_size, __func__)); if (voxels == nullptr) { return false; } extract_dense_float_voxels(grid_type, *grid, bbox, voxels); create_texture_to_object_matrix(grid->transform().baseMap()->getAffineMap()->getMat4(), bbox, r_dense_grid->texture_to_object); r_dense_grid->voxels = voxels; r_dense_grid->channels = channels; copy_v3_v3_int(r_dense_grid->resolution, resolution.asV()); return true; #endif UNUSED_VARS(volume, volume_grid, r_dense_grid); return false; } void BKE_volume_dense_float_grid_clear(DenseFloatVolumeGrid *dense_grid) { if (dense_grid->voxels != nullptr) { MEM_freeN(dense_grid->voxels); } } /* Wireframe */ #ifdef WITH_OPENVDB /** Returns bounding boxes that approximate the shape of the volume stored in the grid. */ template static blender::Vector get_bounding_boxes(const GridType &grid, const bool coarse) { using TreeType = typename GridType::TreeType; using Depth2Type = typename TreeType::RootNodeType::ChildNodeType::ChildNodeType; using NodeCIter = typename TreeType::NodeCIter; blender::Vector boxes; const int depth = coarse ? 2 : 3; NodeCIter iter = grid.tree().cbeginNode(); iter.setMaxDepth(depth); for (; iter; ++iter) { if (iter.getDepth() != depth) { continue; } openvdb::CoordBBox box; if (depth == 2) { /* Internal node at depth 2. */ const Depth2Type *node = nullptr; iter.getNode(node); if (node) { node->evalActiveBoundingBox(box, false); } else { continue; } } else { /* Leaf node. */ if (!iter.getBoundingBox(box)) { continue; } } /* +1 to convert from exclusive to inclusive bounds. */ box.max() = box.max().offsetBy(1); boxes.append(box); } return boxes; } struct GetBoundingBoxesOp { const openvdb::GridBase &grid; const bool coarse; template blender::Vector operator()() { return get_bounding_boxes(static_cast(grid), coarse); } }; static blender::Vector get_bounding_boxes(VolumeGridType grid_type, const openvdb::GridBase &grid, const bool coarse) { GetBoundingBoxesOp op{grid, coarse}; return BKE_volume_grid_type_operation(grid_type, op); } static void boxes_to_center_points(blender::Span boxes, const openvdb::math::Transform &transform, blender::MutableSpan r_verts) { BLI_assert(boxes.size() == r_verts.size()); for (const int i : boxes.index_range()) { openvdb::Vec3d center = transform.indexToWorld(boxes[i].getCenter()); r_verts[i] = blender::float3(center[0], center[1], center[2]); } } static void boxes_to_corner_points(blender::Span boxes, const openvdb::math::Transform &transform, blender::MutableSpan r_verts) { BLI_assert(boxes.size() * 8 == r_verts.size()); for (const int i : boxes.index_range()) { const openvdb::CoordBBox &box = boxes[i]; /* The ordering of the corner points is lexicographic. */ std::array corners; box.getCornerPoints(corners.data()); for (int j = 0; j < 8; j++) { openvdb::Coord corner_i = corners[j]; openvdb::Vec3d corner_d = transform.indexToWorld(corner_i); r_verts[8 * i + j] = blender::float3(corner_d[0], corner_d[1], corner_d[2]); } } } static void boxes_to_edge_mesh(blender::Span boxes, const openvdb::math::Transform &transform, blender::Vector &r_verts, blender::Vector> &r_edges) { /* TODO: Deduplicate edges, hide flat edges? */ const int box_edges[12][2] = { {0, 1}, {0, 2}, {0, 4}, {1, 3}, {1, 5}, {2, 3}, {2, 6}, {3, 7}, {4, 5}, {4, 6}, {5, 7}, {6, 7}, }; int vert_offset = r_verts.size(); int edge_offset = r_edges.size(); const int vert_amount = 8 * boxes.size(); const int edge_amount = 12 * boxes.size(); r_verts.resize(r_verts.size() + vert_amount); r_edges.resize(r_edges.size() + edge_amount); boxes_to_corner_points(boxes, transform, r_verts.as_mutable_span().take_back(vert_amount)); for (int i = 0; i < boxes.size(); i++) { for (int j = 0; j < 12; j++) { r_edges[edge_offset + j] = {vert_offset + box_edges[j][0], vert_offset + box_edges[j][1]}; } vert_offset += 8; edge_offset += 12; } } static void boxes_to_cube_mesh(blender::Span boxes, const openvdb::math::Transform &transform, blender::Vector &r_verts, blender::Vector> &r_tris) { const int box_tris[12][3] = { {0, 1, 4}, {4, 1, 5}, {0, 2, 1}, {1, 2, 3}, {1, 3, 5}, {5, 3, 7}, {6, 4, 5}, {7, 5, 6}, {2, 0, 4}, {2, 4, 6}, {3, 7, 2}, {6, 2, 7}, }; int vert_offset = r_verts.size(); int tri_offset = r_tris.size(); const int vert_amount = 8 * boxes.size(); const int tri_amount = 12 * boxes.size(); r_verts.resize(r_verts.size() + vert_amount); r_tris.resize(r_tris.size() + tri_amount); boxes_to_corner_points(boxes, transform, r_verts.as_mutable_span().take_back(vert_amount)); for (int i = 0; i < boxes.size(); i++) { for (int j = 0; j < 12; j++) { r_tris[tri_offset + j] = {vert_offset + box_tris[j][0], vert_offset + box_tris[j][1], vert_offset + box_tris[j][2]}; } vert_offset += 8; tri_offset += 12; } } #endif void BKE_volume_grid_wireframe(const Volume *volume, const VolumeGrid *volume_grid, BKE_volume_wireframe_cb cb, void *cb_userdata) { if (volume->display.wireframe_type == VOLUME_WIREFRAME_NONE) { cb(cb_userdata, nullptr, nullptr, 0, 0); return; } #ifdef WITH_OPENVDB openvdb::GridBase::ConstPtr grid = BKE_volume_grid_openvdb_for_read(volume, volume_grid); if (volume->display.wireframe_type == VOLUME_WIREFRAME_BOUNDS) { /* Bounding box. */ openvdb::CoordBBox box; blender::Vector verts; blender::Vector> edges; if (grid->baseTree().evalLeafBoundingBox(box)) { boxes_to_edge_mesh({box}, grid->transform(), verts, edges); } cb(cb_userdata, (float(*)[3])verts.data(), (int(*)[2])edges.data(), verts.size(), edges.size()); } else { blender::Vector boxes = get_bounding_boxes( BKE_volume_grid_type(volume_grid), *grid, volume->display.wireframe_detail == VOLUME_WIREFRAME_COARSE); blender::Vector verts; blender::Vector> edges; if (volume->display.wireframe_type == VOLUME_WIREFRAME_POINTS) { verts.resize(boxes.size()); boxes_to_center_points(boxes, grid->transform(), verts); } else { boxes_to_edge_mesh(boxes, grid->transform(), verts, edges); } cb(cb_userdata, (float(*)[3])verts.data(), (int(*)[2])edges.data(), verts.size(), edges.size()); } #else UNUSED_VARS(volume, volume_grid); cb(cb_userdata, nullptr, nullptr, 0, 0); #endif } #ifdef WITH_OPENVDB static void grow_triangles(blender::MutableSpan verts, blender::Span> tris, const float factor) { /* Compute the offset for every vertex based on the connected edges. * This formula simply tries increases the length of all edges. */ blender::Array offsets(verts.size(), {0, 0, 0}); for (const std::array &tri : tris) { offsets[tri[0]] += factor * (2 * verts[tri[0]] - verts[tri[1]] - verts[tri[2]]); offsets[tri[1]] += factor * (2 * verts[tri[1]] - verts[tri[0]] - verts[tri[2]]); offsets[tri[2]] += factor * (2 * verts[tri[2]] - verts[tri[0]] - verts[tri[1]]); } /* Apply the computed offsets. */ for (const int i : verts.index_range()) { verts[i] += offsets[i]; } } #endif /* WITH_OPENVDB */ void BKE_volume_grid_selection_surface(const Volume *volume, const VolumeGrid *volume_grid, BKE_volume_selection_surface_cb cb, void *cb_userdata) { #ifdef WITH_OPENVDB openvdb::GridBase::ConstPtr grid = BKE_volume_grid_openvdb_for_read(volume, volume_grid); blender::Vector boxes = get_bounding_boxes( BKE_volume_grid_type(volume_grid), *grid, true); blender::Vector verts; blender::Vector> tris; boxes_to_cube_mesh(boxes, grid->transform(), verts, tris); /* By slightly scaling the individual boxes up, we can avoid some artifacts when drawing the * selection outline. */ const float offset_factor = 0.01f; grow_triangles(verts, tris, offset_factor); cb(cb_userdata, (float(*)[3])verts.data(), (int(*)[3])tris.data(), verts.size(), tris.size()); #else UNUSED_VARS(volume, volume_grid); cb(cb_userdata, nullptr, nullptr, 0, 0); #endif } /* Render */ float BKE_volume_density_scale(const Volume *volume, const float matrix[4][4]) { if (volume->render.space == VOLUME_SPACE_OBJECT) { float unit[3] = {1.0f, 1.0f, 1.0f}; normalize_v3(unit); mul_mat3_m4_v3(matrix, unit); return 1.0f / len_v3(unit); } return 1.0f; }