diff options
Diffstat (limited to 'source/blender/modifiers/intern/MOD_volume_displace.cc')
-rw-r--r-- | source/blender/modifiers/intern/MOD_volume_displace.cc | 348 |
1 files changed, 348 insertions, 0 deletions
diff --git a/source/blender/modifiers/intern/MOD_volume_displace.cc b/source/blender/modifiers/intern/MOD_volume_displace.cc new file mode 100644 index 00000000000..f4e1936713c --- /dev/null +++ b/source/blender/modifiers/intern/MOD_volume_displace.cc @@ -0,0 +1,348 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup modifiers + */ + +#include "BKE_lib_query.h" +#include "BKE_mesh_runtime.h" +#include "BKE_modifier.h" +#include "BKE_object.h" +#include "BKE_texture.h" +#include "BKE_volume.h" + +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_object_types.h" +#include "DNA_screen_types.h" +#include "DNA_texture_types.h" +#include "DNA_volume_types.h" + +#include "DEG_depsgraph_build.h" +#include "DEG_depsgraph_query.h" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "BLO_read_write.h" + +#include "MEM_guardedalloc.h" + +#include "MOD_modifiertypes.h" +#include "MOD_ui_common.h" + +#include "RE_texture.h" + +#include "RNA_access.h" + +#include "BLI_math_vector.h" + +#ifdef WITH_OPENVDB +# include <openvdb/openvdb.h> +# include <openvdb/tools/Interpolation.h> +# include <openvdb/tools/Morphology.h> +# include <openvdb/tools/Prune.h> +# include <openvdb/tools/ValueTransformer.h> +#endif + +static void initData(ModifierData *md) +{ + VolumeDisplaceModifierData *vdmd = reinterpret_cast<VolumeDisplaceModifierData *>(md); + vdmd->texture = nullptr; + vdmd->strength = 0.5f; + copy_v3_fl(vdmd->texture_mid_level, 0.5f); + vdmd->texture_sample_radius = 1.0f; +} + +static void updateDepsgraph(ModifierData *md, const ModifierUpdateDepsgraphContext *ctx) +{ + VolumeDisplaceModifierData *vdmd = reinterpret_cast<VolumeDisplaceModifierData *>(md); + if (vdmd->texture != nullptr) { + DEG_add_generic_id_relation(ctx->node, &vdmd->texture->id, "Volume Displace Modifier"); + } + if (vdmd->texture_map_mode == MOD_VOLUME_DISPLACE_MAP_OBJECT) { + if (vdmd->texture_map_object != nullptr) { + DEG_add_object_relation( + ctx->node, vdmd->texture_map_object, DEG_OB_COMP_TRANSFORM, "Volume Displace Modifier"); + } + } +} + +static void foreachIDLink(ModifierData *md, Object *ob, IDWalkFunc walk, void *userData) +{ + VolumeDisplaceModifierData *vdmd = reinterpret_cast<VolumeDisplaceModifierData *>(md); + walk(userData, ob, (ID **)&vdmd->texture, IDWALK_CB_USER); + walk(userData, ob, (ID **)&vdmd->texture_map_object, IDWALK_CB_USER); +} + +static void foreachTexLink(ModifierData *md, Object *ob, TexWalkFunc walk, void *userData) +{ + walk(userData, ob, md, "texture"); +} + +static bool dependsOnTime(ModifierData *md) +{ + VolumeDisplaceModifierData *vdmd = reinterpret_cast<VolumeDisplaceModifierData *>(md); + if (vdmd->texture) { + return BKE_texture_dependsOnTime(vdmd->texture); + } + return false; +} + +static void panel_draw(const bContext *C, Panel *panel) +{ + uiLayout *layout = panel->layout; + + PointerRNA ob_ptr; + PointerRNA *ptr = modifier_panel_get_property_pointers(panel, &ob_ptr); + VolumeDisplaceModifierData *vdmd = static_cast<VolumeDisplaceModifierData *>(ptr->data); + + uiLayoutSetPropSep(layout, true); + uiLayoutSetPropDecorate(layout, false); + + uiTemplateID(layout, C, ptr, "texture", "texture.new", nullptr, nullptr, 0, ICON_NONE, nullptr); + uiItemR(layout, ptr, "texture_map_mode", 0, "Texture Mapping", ICON_NONE); + + if (vdmd->texture_map_mode == MOD_VOLUME_DISPLACE_MAP_OBJECT) { + uiItemR(layout, ptr, "texture_map_object", 0, "Object", ICON_NONE); + } + + uiItemR(layout, ptr, "strength", 0, nullptr, ICON_NONE); + uiItemR(layout, ptr, "texture_sample_radius", 0, "Sample Radius", ICON_NONE); + uiItemR(layout, ptr, "texture_mid_level", 0, "Mid Level", ICON_NONE); + + modifier_panel_end(layout, ptr); +} + +static void panelRegister(ARegionType *region_type) +{ + modifier_panel_register(region_type, eModifierType_VolumeDisplace, panel_draw); +} + +#ifdef WITH_OPENVDB + +static openvdb::Mat4s matrix_to_openvdb(const float m[4][4]) +{ + /* OpenVDB matrices are transposed Blender matrices, i.e. the translation is in the last row + * instead of in the last column. However, the layout in memory is the same, because OpenVDB + * matrices are row major (compared to Blender's column major matrices). */ + openvdb::Mat4s new_matrix{reinterpret_cast<const float *>(m)}; + return new_matrix; +} + +template<typename GridType> struct DisplaceOp { + /* Has to be copied for each thread. */ + typename GridType::ConstAccessor accessor; + const openvdb::Mat4s index_to_texture; + + Tex *texture; + const double strength; + const openvdb::Vec3d texture_mid_level; + + void operator()(const typename GridType::ValueOnIter &iter) const + { + const openvdb::Coord coord = iter.getCoord(); + const openvdb::Vec3d displace_vector = this->compute_displace_vector(coord); + /* Subtract vector because that makes the result more similar to advection and the mesh + * displace modifier. */ + const openvdb::Vec3d sample_coord = coord.asVec3d() - displace_vector; + const auto new_value = openvdb::tools::BoxSampler::sample(this->accessor, sample_coord); + iter.setValue(new_value); + } + + openvdb::Vec3d compute_displace_vector(const openvdb::Coord &coord) const + { + if (this->texture != nullptr) { + const openvdb::Vec3f texture_pos = coord.asVec3s() * this->index_to_texture; + const openvdb::Vec3d texture_value = this->evaluate_texture(texture_pos); + const openvdb::Vec3d displacement = (texture_value - this->texture_mid_level) * + this->strength; + return displacement; + } + return openvdb::Vec3d{0, 0, 0}; + } + + openvdb::Vec3d evaluate_texture(const openvdb::Vec3f &pos) const + { + TexResult texture_result = {0}; + BKE_texture_get_value( + nullptr, this->texture, const_cast<float *>(pos.asV()), &texture_result, false); + return {texture_result.tr, texture_result.tg, texture_result.tb}; + } +}; + +static float get_max_voxel_side_length(const openvdb::GridBase &grid) +{ + const openvdb::Vec3d voxel_size = grid.voxelSize(); + const float max_voxel_side_length = std::max({voxel_size[0], voxel_size[1], voxel_size[2]}); + return max_voxel_side_length; +} + +struct DisplaceGridOp { + /* This is the grid that will be displaced. The output is copied back to the original grid. */ + openvdb::GridBase &base_grid; + + VolumeDisplaceModifierData &vdmd; + const ModifierEvalContext &ctx; + + template<typename GridType> void operator()() + { + if constexpr (std::is_same_v<GridType, openvdb::points::PointDataGrid> || + std::is_same_v<GridType, openvdb::StringGrid> || + std::is_same_v<GridType, openvdb::MaskGrid>) { + /* We don't support displacing these grid types yet. */ + return; + } + else { + this->displace_grid<GridType>(); + } + } + + template<typename GridType> void displace_grid() + { + GridType &grid = static_cast<GridType &>(base_grid); + + /* Make a copy of the original grid to work on. This will replace the original grid. */ + typename GridType::Ptr temp_grid = grid.deepCopy(); + + /* Dilate grid, because the currently inactive cells might become active during the displace + * operation. The quality of the approximation of the has a big impact on performance. */ + const float max_voxel_side_length = get_max_voxel_side_length(grid); + const float sample_radius = vdmd.texture_sample_radius * std::abs(vdmd.strength) / + max_voxel_side_length / 2.0f; + openvdb::tools::dilateActiveValues(temp_grid->tree(), + static_cast<int>(std::ceil(sample_radius)), + openvdb::tools::NN_FACE_EDGE, + openvdb::tools::EXPAND_TILES); + + const openvdb::Mat4s index_to_texture = this->get_index_to_texture_transform(); + + /* Construct the operator that will be executed on every cell of the dilated grid. */ + DisplaceOp<GridType> displace_op{grid.getConstAccessor(), + index_to_texture, + vdmd.texture, + vdmd.strength / max_voxel_side_length, + openvdb::Vec3d{vdmd.texture_mid_level}}; + + /* Run the operator. This is multi-threaded. It is important that the operator is not shared + * between the threads, because it contains a non-thread-safe accessor for the old grid. */ + openvdb::tools::foreach (temp_grid->beginValueOn(), + displace_op, + true, + /* Disable sharing of the operator. */ false); + + /* It is likely that we produced too many active cells. Those are removed here, to avoid + * slowing down subsequent operations. */ + typename GridType::ValueType prune_tolerance{0}; + openvdb::tools::deactivate(*temp_grid, temp_grid->background(), prune_tolerance); + openvdb::tools::prune(temp_grid->tree()); + + /* Overwrite the old volume grid with the new grid. */ + grid.clear(); + grid.merge(*temp_grid); + } + + openvdb::Mat4s get_index_to_texture_transform() const + { + const openvdb::Mat4s index_to_object{ + base_grid.transform().baseMap()->getAffineMap()->getMat4()}; + + switch (vdmd.texture_map_mode) { + case MOD_VOLUME_DISPLACE_MAP_LOCAL: { + return index_to_object; + } + case MOD_VOLUME_DISPLACE_MAP_GLOBAL: { + const openvdb::Mat4s object_to_world = matrix_to_openvdb(ctx.object->obmat); + return index_to_object * object_to_world; + } + case MOD_VOLUME_DISPLACE_MAP_OBJECT: { + if (vdmd.texture_map_object == nullptr) { + return index_to_object; + } + const openvdb::Mat4s object_to_world = matrix_to_openvdb(ctx.object->obmat); + const openvdb::Mat4s world_to_texture = matrix_to_openvdb(vdmd.texture_map_object->imat); + return index_to_object * object_to_world * world_to_texture; + } + } + BLI_assert(false); + return {}; + } +}; + +#endif + +static Volume *modifyVolume(ModifierData *md, const ModifierEvalContext *ctx, Volume *volume) +{ +#ifdef WITH_OPENVDB + VolumeDisplaceModifierData *vdmd = reinterpret_cast<VolumeDisplaceModifierData *>(md); + + /* Iterate over all grids and displace them one by one. */ + BKE_volume_load(volume, DEG_get_bmain(ctx->depsgraph)); + const int grid_amount = BKE_volume_num_grids(volume); + for (int grid_index = 0; grid_index < grid_amount; grid_index++) { + VolumeGrid *volume_grid = BKE_volume_grid_get(volume, grid_index); + BLI_assert(volume_grid != nullptr); + + openvdb::GridBase::Ptr grid = BKE_volume_grid_openvdb_for_write(volume, volume_grid, false); + VolumeGridType grid_type = BKE_volume_grid_type(volume_grid); + + DisplaceGridOp displace_grid_op{*grid, *vdmd, *ctx}; + BKE_volume_grid_type_operation(grid_type, displace_grid_op); + } + + return volume; +#else + UNUSED_VARS(md, ctx); + BKE_modifier_set_error(ctx->object, md, "Compiled without OpenVDB"); + return volume; +#endif +} + +ModifierTypeInfo modifierType_VolumeDisplace = { + /* name */ "Volume Displace", + /* structName */ "VolumeDisplaceModifierData", + /* structSize */ sizeof(VolumeDisplaceModifierData), + /* srna */ &RNA_VolumeDisplaceModifier, + /* type */ eModifierTypeType_NonGeometrical, + /* flags */ static_cast<ModifierTypeFlag>(0), + /* icon */ ICON_VOLUME_DATA, /* TODO: Use correct icon. */ + + /* copyData */ BKE_modifier_copydata_generic, + + /* deformVerts */ nullptr, + /* deformMatrices */ nullptr, + /* deformVertsEM */ nullptr, + /* deformMatricesEM */ nullptr, + /* modifyMesh */ nullptr, + /* modifyHair */ nullptr, + /* modifyPointCloud */ nullptr, + /* modifyVolume */ modifyVolume, + + /* initData */ initData, + /* requiredDataMask */ nullptr, + /* freeData */ nullptr, + /* isDisabled */ nullptr, + /* updateDepsgraph */ updateDepsgraph, + /* dependsOnTime */ dependsOnTime, + /* dependsOnNormals */ nullptr, + /* foreachIDLink */ foreachIDLink, + /* foreachTexLink */ foreachTexLink, + /* freeRuntimeData */ nullptr, + /* panelRegister */ panelRegister, + /* blendWrite */ nullptr, + /* blendRead */ nullptr, +}; |