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