Blender V5.0
MOD_volume_displace.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
8
9#include "BKE_geometry_set.hh"
10#include "BKE_lib_query.hh"
11#include "BKE_modifier.hh"
12#include "BKE_texture.h"
13#include "BKE_volume.hh"
14#include "BKE_volume_grid.hh"
16#include "BKE_volume_openvdb.hh"
17
18#include "BLT_translation.hh"
19
20#include "DNA_object_types.h"
21#include "DNA_screen_types.h"
22#include "DNA_texture_types.h"
23
26
27#include "UI_interface.hh"
29#include "UI_resources.hh"
30
31#include "MOD_ui_common.hh"
32
33#include "RE_texture.h"
34
35#include "RNA_access.hh"
36#include "RNA_prototypes.hh"
37
38#include "BLI_math_vector.h"
39
40#ifdef WITH_OPENVDB
41# include <openvdb/openvdb.h>
42# include <openvdb/tools/Interpolation.h>
43# include <openvdb/tools/Morphology.h>
44# include <openvdb/tools/Prune.h>
45# include <openvdb/tools/ValueTransformer.h>
46#endif
47
48static void init_data(ModifierData *md)
49{
50 VolumeDisplaceModifierData *vdmd = reinterpret_cast<VolumeDisplaceModifierData *>(md);
51 vdmd->texture = nullptr;
52 vdmd->strength = 0.5f;
53 copy_v3_fl(vdmd->texture_mid_level, 0.5f);
54 vdmd->texture_sample_radius = 1.0f;
55}
56
58{
59 VolumeDisplaceModifierData *vdmd = reinterpret_cast<VolumeDisplaceModifierData *>(md);
60 if (vdmd->texture != nullptr) {
61 DEG_add_generic_id_relation(ctx->node, &vdmd->texture->id, "Volume Displace Modifier");
62 }
64 if (vdmd->texture_map_object != nullptr) {
66 ctx->node, vdmd->texture_map_object, DEG_OB_COMP_TRANSFORM, "Volume Displace Modifier");
67 }
68 }
69}
70
71static void foreach_ID_link(ModifierData *md, Object *ob, IDWalkFunc walk, void *user_data)
72{
73 VolumeDisplaceModifierData *vdmd = reinterpret_cast<VolumeDisplaceModifierData *>(md);
74 walk(user_data, ob, (ID **)&vdmd->texture, IDWALK_CB_USER);
75 walk(user_data, ob, (ID **)&vdmd->texture_map_object, IDWALK_CB_USER);
76}
77
78static void foreach_tex_link(ModifierData *md, Object *ob, TexWalkFunc walk, void *user_data)
79{
80 PointerRNA ptr = RNA_pointer_create_discrete(&ob->id, &RNA_Modifier, md);
81 PropertyRNA *prop = RNA_struct_find_property(&ptr, "texture");
82 walk(user_data, ob, md, &ptr, prop);
83}
84
85static bool depends_on_time(Scene * /*scene*/, ModifierData *md)
86{
87 VolumeDisplaceModifierData *vdmd = reinterpret_cast<VolumeDisplaceModifierData *>(md);
88 if (vdmd->texture) {
90 }
91 return false;
92}
93
94static void panel_draw(const bContext *C, Panel *panel)
95{
96 uiLayout *layout = panel->layout;
97
98 PointerRNA ob_ptr;
100 VolumeDisplaceModifierData *vdmd = static_cast<VolumeDisplaceModifierData *>(ptr->data);
101
102 layout->use_property_split_set(true);
103
104 uiTemplateID(layout, C, ptr, "texture", "texture.new", nullptr, nullptr);
105 layout->prop(ptr, "texture_map_mode", UI_ITEM_NONE, IFACE_("Texture Mapping"), ICON_NONE);
106
108 layout->prop(ptr, "texture_map_object", UI_ITEM_NONE, IFACE_("Object"), ICON_NONE);
109 }
110
111 layout->prop(ptr, "strength", UI_ITEM_NONE, std::nullopt, ICON_NONE);
112 layout->prop(ptr, "texture_sample_radius", UI_ITEM_NONE, IFACE_("Sample Radius"), ICON_NONE);
113 layout->prop(ptr, "texture_mid_level", UI_ITEM_NONE, IFACE_("Mid Level"), ICON_NONE);
114
116}
117
118static void panel_register(ARegionType *region_type)
119{
121}
122
123#ifdef WITH_OPENVDB
124
125static openvdb::Mat4s matrix_to_openvdb(const blender::float4x4 &m)
126{
127 /* OpenVDB matrices are transposed Blender matrices, i.e. the translation is in the last row
128 * instead of in the last column. However, the layout in memory is the same, because OpenVDB
129 * matrices are row major (compared to Blender's column major matrices). */
130 openvdb::Mat4s new_matrix{m.base_ptr()};
131 return new_matrix;
132}
133
134template<typename GridType> struct DisplaceOp {
135 /* Has to be copied for each thread. */
136 typename GridType::ConstAccessor accessor;
137 const openvdb::Mat4s index_to_texture;
138
139 Tex *texture;
140 const double strength;
141 const openvdb::Vec3d texture_mid_level;
142
143 void operator()(const typename GridType::ValueOnIter &iter) const
144 {
145 const openvdb::Coord coord = iter.getCoord();
146 const openvdb::Vec3d displace_vector = this->compute_displace_vector(coord);
147 /* Subtract vector because that makes the result more similar to advection and the mesh
148 * displace modifier. */
149 const openvdb::Vec3d sample_coord = coord.asVec3d() - displace_vector;
150 const auto new_value = openvdb::tools::BoxSampler::sample(this->accessor, sample_coord);
151 iter.setValue(new_value);
152 }
153
154 openvdb::Vec3d compute_displace_vector(const openvdb::Coord &coord) const
155 {
156 if (this->texture != nullptr) {
157 const openvdb::Vec3f texture_pos = coord.asVec3s() * this->index_to_texture;
158 const openvdb::Vec3d texture_value = this->evaluate_texture(texture_pos);
159 const openvdb::Vec3d displacement = (texture_value - this->texture_mid_level) *
160 this->strength;
161 return displacement;
162 }
163 return openvdb::Vec3d{0, 0, 0};
164 }
165
166 openvdb::Vec3d evaluate_texture(const openvdb::Vec3f &pos) const
167 {
168 TexResult texture_result = {0};
169 BKE_texture_get_value(this->texture, const_cast<float *>(pos.asV()), &texture_result, false);
170 return {texture_result.trgba[0], texture_result.trgba[1], texture_result.trgba[2]};
171 }
172};
173
174static float get_max_voxel_side_length(const openvdb::GridBase &grid)
175{
176 const openvdb::Vec3d voxel_size = grid.voxelSize();
177 const float max_voxel_side_length = std::max({voxel_size[0], voxel_size[1], voxel_size[2]});
178 return max_voxel_side_length;
179}
180
181struct DisplaceGridOp {
182 /* This is the grid that will be displaced. The output is copied back to the original grid. */
183 openvdb::GridBase &base_grid;
184
186 const ModifierEvalContext &ctx;
187
188 template<typename GridType> void operator()()
189 {
190 if constexpr (blender::
191 is_same_any_v<GridType, openvdb::points::PointDataGrid, openvdb::MaskGrid>)
192 {
193 /* We don't support displacing these grid types yet. */
194 return;
195 }
196 else {
197 this->displace_grid<GridType>();
198 }
199 }
200
201 template<typename GridType> void displace_grid()
202 {
203 GridType &grid = static_cast<GridType &>(base_grid);
204
205 /* Make a copy of the original grid to work on. This will replace the original grid. */
206 typename GridType::Ptr temp_grid = grid.deepCopy();
207
208 /* Dilate grid, because the currently inactive cells might become active during the displace
209 * operation. The quality of the approximation of this has a big impact on performance. */
210 const float max_voxel_side_length = get_max_voxel_side_length(grid);
211 const float sample_radius = vdmd.texture_sample_radius * std::abs(vdmd.strength) /
212 max_voxel_side_length / 2.0f;
213 openvdb::tools::dilateActiveValues(temp_grid->tree(),
214 int(std::ceil(sample_radius)),
215 openvdb::tools::NN_FACE_EDGE,
216 openvdb::tools::EXPAND_TILES);
217
218 const openvdb::Mat4s index_to_texture = this->get_index_to_texture_transform();
219
220 /* Construct the operator that will be executed on every cell of the dilated grid. */
221 DisplaceOp<GridType> displace_op{grid.getConstAccessor(),
222 index_to_texture,
223 vdmd.texture,
224 vdmd.strength / max_voxel_side_length,
225 openvdb::Vec3d{vdmd.texture_mid_level}};
226
227 /* Run the operator. This is multi-threaded. It is important that the operator is not shared
228 * between the threads, because it contains a non-thread-safe accessor for the old grid. */
229 openvdb::tools::foreach(temp_grid->beginValueOn(),
230 displace_op,
231 true,
232 /* Disable sharing of the operator. */
233 false);
234
235 /* It is likely that we produced too many active cells. Those are removed here, to avoid
236 * slowing down subsequent operations. */
237 typename GridType::ValueType prune_tolerance{0};
238 openvdb::tools::deactivate(*temp_grid, temp_grid->background(), prune_tolerance);
239 blender::bke::volume_grid::prune_inactive(*temp_grid);
240
241 /* Overwrite the old volume grid with the new grid. */
242 grid.clear();
243 grid.merge(*temp_grid);
244 }
245
246 openvdb::Mat4s get_index_to_texture_transform() const
247 {
248 const openvdb::Mat4s index_to_object{
249 base_grid.transform().baseMap()->getAffineMap()->getMat4()};
250
251 switch (vdmd.texture_map_mode) {
253 return index_to_object;
254 }
256 const openvdb::Mat4s object_to_world = matrix_to_openvdb(ctx.object->object_to_world());
257 return index_to_object * object_to_world;
258 }
260 if (vdmd.texture_map_object == nullptr) {
261 return index_to_object;
262 }
263 const openvdb::Mat4s object_to_world = matrix_to_openvdb(ctx.object->object_to_world());
264 const openvdb::Mat4s world_to_texture = matrix_to_openvdb(
265 vdmd.texture_map_object->world_to_object());
266 return index_to_object * object_to_world * world_to_texture;
267 }
268 }
269 BLI_assert(false);
270 return {};
271 }
272};
273
274#endif
275
276static void displace_volume(ModifierData *md, const ModifierEvalContext *ctx, Volume *volume)
277{
278#ifdef WITH_OPENVDB
279 VolumeDisplaceModifierData *vdmd = reinterpret_cast<VolumeDisplaceModifierData *>(md);
280
281 /* Iterate over all grids and displace them one by one. */
283 const int grid_amount = BKE_volume_num_grids(volume);
284 for (int grid_index = 0; grid_index < grid_amount; grid_index++) {
285 blender::bke::VolumeGridData *volume_grid = BKE_volume_grid_get_for_write(volume, grid_index);
286 BLI_assert(volume_grid);
287
288 blender::bke::VolumeTreeAccessToken tree_token;
289 openvdb::GridBase &grid = volume_grid->grid_for_write(tree_token);
290 VolumeGridType grid_type = volume_grid->grid_type();
291
292 DisplaceGridOp displace_grid_op{grid, *vdmd, *ctx};
293 BKE_volume_grid_type_operation(grid_type, displace_grid_op);
294 volume_grid->tag_tree_modified();
295 }
296
297#else
298 UNUSED_VARS(md, volume, ctx);
299 BKE_modifier_set_error(ctx->object, md, "Compiled without OpenVDB");
300#endif
301}
302
304 const ModifierEvalContext *ctx,
305 blender::bke::GeometrySet *geometry_set)
306{
307 Volume *input_volume = geometry_set->get_volume_for_write();
308 if (input_volume != nullptr) {
309 displace_volume(md, ctx, input_volume);
310 }
311}
312
314 /*idname*/ "Volume Displace",
315 /*name*/ N_("Volume Displace"),
316 /*struct_name*/ "VolumeDisplaceModifierData",
317 /*struct_size*/ sizeof(VolumeDisplaceModifierData),
318 /*srna*/ &RNA_VolumeDisplaceModifier,
320 /*flags*/ static_cast<ModifierTypeFlag>(0),
321 /*icon*/ ICON_VOLUME_DATA, /* TODO: Use correct icon. */
322
323 /*copy_data*/ BKE_modifier_copydata_generic,
324
325 /*deform_verts*/ nullptr,
326 /*deform_matrices*/ nullptr,
327 /*deform_verts_EM*/ nullptr,
328 /*deform_matrices_EM*/ nullptr,
329 /*modify_mesh*/ nullptr,
330 /*modify_geometry_set*/ modify_geometry_set,
331
332 /*init_data*/ init_data,
333 /*required_data_mask*/ nullptr,
334 /*free_data*/ nullptr,
335 /*is_disabled*/ nullptr,
336 /*update_depsgraph*/ update_depsgraph,
337 /*depends_on_time*/ depends_on_time,
338 /*depends_on_normals*/ nullptr,
339 /*foreach_ID_link*/ foreach_ID_link,
340 /*foreach_tex_link*/ foreach_tex_link,
341 /*free_runtime_data*/ nullptr,
342 /*panel_register*/ panel_register,
343 /*blend_write*/ nullptr,
344 /*blend_read*/ nullptr,
345 /*foreach_cache*/ nullptr,
346 /*foreach_working_space_color*/ nullptr,
347};
@ IDWALK_CB_USER
void(*)(void *user_data, Object *ob, ID **idpoin, LibraryForeachIDCallbackFlag cb_flag) IDWalkFunc
void BKE_modifier_copydata_generic(const ModifierData *md, ModifierData *md_dst, int flag)
ModifierTypeFlag
void BKE_modifier_set_error(const Object *ob, ModifierData *md, const char *format,...) ATTR_PRINTF_FORMAT(3
void(*)(void *user_data, Object *ob, ModifierData *md, const PointerRNA *ptr, PropertyRNA *texture_prop) TexWalkFunc
void BKE_texture_get_value(struct Tex *texture, const float *tex_co, struct TexResult *texres, bool use_color_management)
Definition texture.cc:638
bool BKE_texture_dependsOnTime(const struct Tex *texture)
Volume data-block.
int BKE_volume_num_grids(const Volume *volume)
bool BKE_volume_load(const Volume *volume, const Main *bmain)
blender::bke::VolumeGridData * BKE_volume_grid_get_for_write(Volume *volume, int grid_index)
VolumeGridType
#define BLI_assert(a)
Definition BLI_assert.h:46
MINLINE void copy_v3_fl(float r[3], float f)
#define UNUSED_VARS(...)
#define IFACE_(msgid)
void DEG_add_generic_id_relation(DepsNodeHandle *node_handle, ID *id, const char *description)
void DEG_add_object_relation(DepsNodeHandle *node_handle, Object *object, eDepsObjectComponentType component, const char *description)
@ DEG_OB_COMP_TRANSFORM
Main * DEG_get_bmain(const Depsgraph *graph)
struct VolumeDisplaceModifierData VolumeDisplaceModifierData
@ eModifierType_VolumeDisplace
@ MOD_VOLUME_DISPLACE_MAP_GLOBAL
@ MOD_VOLUME_DISPLACE_MAP_LOCAL
@ MOD_VOLUME_DISPLACE_MAP_OBJECT
Object is a sort of wrapper for general info.
struct Tex Tex
static void init_data(ModifierData *md)
static void panel_register(ARegionType *region_type)
static void panel_draw(const bContext *, Panel *panel)
static void foreach_ID_link(ModifierData *md, Object *ob, IDWalkFunc walk, void *user_data)
static bool depends_on_time(Scene *, ModifierData *)
Definition MOD_build.cc:47
static void foreach_tex_link(ModifierData *md, Object *ob, TexWalkFunc walk, void *user_data)
static void modify_geometry_set(ModifierData *md, const ModifierEvalContext *ctx, blender::bke::GeometrySet *geometry_set)
PanelType * modifier_panel_register(ARegionType *region_type, ModifierType type, PanelDrawFn draw)
PointerRNA * modifier_panel_get_property_pointers(Panel *panel, PointerRNA *r_ob_ptr)
void modifier_error_message_draw(uiLayout *layout, PointerRNA *ptr)
static void init_data(ModifierData *md)
static void panel_register(ARegionType *region_type)
ModifierTypeInfo modifierType_VolumeDisplace
static bool depends_on_time(Scene *, ModifierData *md)
static void displace_volume(ModifierData *md, const ModifierEvalContext *ctx, Volume *volume)
static void foreach_tex_link(ModifierData *md, Object *ob, TexWalkFunc walk, void *user_data)
static void foreach_ID_link(ModifierData *md, Object *ob, IDWalkFunc walk, void *user_data)
static void update_depsgraph(ModifierData *md, const ModifierUpdateDepsgraphContext *ctx)
static void modify_geometry_set(ModifierData *md, const ModifierEvalContext *ctx, blender::bke::GeometrySet *geometry_set)
static void panel_draw(const bContext *C, Panel *panel)
#define C
Definition RandGen.cpp:29
void uiTemplateID(uiLayout *layout, const bContext *C, PointerRNA *ptr, blender::StringRefNull propname, const char *newop, const char *openop, const char *unlinkop, int filter=UI_TEMPLATE_ID_FILTER_ALL, bool live_icon=false, std::optional< blender::StringRef > text=std::nullopt)
#define UI_ITEM_NONE
SIMD_FORCE_INLINE btVector3 operator()(const btVector3 &x) const
Return the transform of the vector.
Definition btTransform.h:90
uint pos
TEX_TEMPLATE DataVec texture(T, FltCoord, float=0.0f) RET
static void update_depsgraph(tGraphSliderOp *gso)
MatBase< float, 4, 4 > float4x4
PropertyRNA * RNA_struct_find_property(PointerRNA *ptr, const char *identifier)
PointerRNA RNA_pointer_create_discrete(ID *id, StructRNA *type, void *data)
Definition DNA_ID.h:414
struct uiLayout * layout
float trgba[4]
Definition RE_texture.h:60
const T * base_ptr() const
void use_property_split_set(bool value)
void prop(PointerRNA *ptr, PropertyRNA *prop, int index, int value, eUI_Item_Flag flag, std::optional< blender::StringRef > name_opt, int icon, std::optional< blender::StringRef > placeholder=std::nullopt)
#define N_(msgid)
PointerRNA * ptr
Definition wm_files.cc:4238