diff options
32 files changed, 511 insertions, 48 deletions
diff --git a/intern/cycles/blender/addon/ui.py b/intern/cycles/blender/addon/ui.py index 739a555f037..8a7b0635ed7 100644 --- a/intern/cycles/blender/addon/ui.py +++ b/intern/cycles/blender/addon/ui.py @@ -1034,7 +1034,7 @@ class CYCLES_OBJECT_PT_motion_blur(CyclesButtonsPanel, Panel): def poll(cls, context): ob = context.object if CyclesButtonsPanel.poll(context) and ob: - if ob.type in {'MESH', 'CURVE', 'CURVE', 'SURFACE', 'FONT', 'META', 'CAMERA', 'CURVES', 'POINTCLOUD'}: + if ob.type in {'MESH', 'CURVE', 'CURVE', 'SURFACE', 'FONT', 'META', 'CAMERA', 'CURVES', 'POINTCLOUD', 'VOLUME'}: return True if ob.instance_type == 'COLLECTION' and ob.instance_collection: return True diff --git a/intern/cycles/blender/camera.cpp b/intern/cycles/blender/camera.cpp index cdd19341534..402fd7c4ec6 100644 --- a/intern/cycles/blender/camera.cpp +++ b/intern/cycles/blender/camera.cpp @@ -23,7 +23,7 @@ struct BlenderCamera { float lens; float shuttertime; - Camera::MotionPosition motion_position; + MotionPosition motion_position; array<float> shutter_curve; Camera::RollingShutterType rolling_shutter_type; @@ -114,7 +114,7 @@ static void blender_camera_init(BlenderCamera *bcam, BL::RenderSettings &b_rende bcam->sensor_width = 36.0f; bcam->sensor_height = 24.0f; bcam->sensor_fit = BlenderCamera::AUTO; - bcam->motion_position = Camera::MOTION_POSITION_CENTER; + bcam->motion_position = MOTION_POSITION_CENTER; bcam->border.right = 1.0f; bcam->border.top = 1.0f; bcam->viewport_camera_border.right = 1.0f; @@ -555,10 +555,8 @@ void BlenderSync::sync_camera(BL::RenderSettings &b_render, curvemapping_to_array(b_shutter_curve, bcam.shutter_curve, RAMP_TABLE_SIZE); PointerRNA cscene = RNA_pointer_get(&b_scene.ptr, "cycles"); - bcam.motion_position = (Camera::MotionPosition)get_enum(cscene, - "motion_blur_position", - Camera::MOTION_NUM_POSITIONS, - Camera::MOTION_POSITION_CENTER); + bcam.motion_position = (MotionPosition)get_enum( + cscene, "motion_blur_position", MOTION_NUM_POSITIONS, MOTION_POSITION_CENTER); bcam.rolling_shutter_type = (Camera::RollingShutterType)get_enum( cscene, "rolling_shutter_type", diff --git a/intern/cycles/blender/object.cpp b/intern/cycles/blender/object.cpp index f77cbdf847d..9b08b564b25 100644 --- a/intern/cycles/blender/object.cpp +++ b/intern/cycles/blender/object.cpp @@ -16,6 +16,7 @@ #include "scene/shader.h" #include "scene/shader_graph.h" #include "scene/shader_nodes.h" +#include "scene/volume.h" #include "util/foreach.h" #include "util/hash.h" @@ -715,13 +716,13 @@ void BlenderSync::sync_motion(BL::RenderSettings &b_render, float frame_center_delta = 0.0f; if (scene->need_motion() != Scene::MOTION_PASS && - scene->camera->get_motion_position() != Camera::MOTION_POSITION_CENTER) { + scene->camera->get_motion_position() != MOTION_POSITION_CENTER) { float shuttertime = scene->camera->get_shuttertime(); - if (scene->camera->get_motion_position() == Camera::MOTION_POSITION_END) { + if (scene->camera->get_motion_position() == MOTION_POSITION_END) { frame_center_delta = -shuttertime * 0.5f; } else { - assert(scene->camera->get_motion_position() == Camera::MOTION_POSITION_START); + assert(scene->camera->get_motion_position() == MOTION_POSITION_START); frame_center_delta = shuttertime * 0.5f; } diff --git a/intern/cycles/blender/sync.cpp b/intern/cycles/blender/sync.cpp index bd6bfafedeb..1028c940772 100644 --- a/intern/cycles/blender/sync.cpp +++ b/intern/cycles/blender/sync.cpp @@ -272,7 +272,7 @@ void BlenderSync::sync_data(BL::RenderSettings &b_render, geometry_synced.clear(); /* use for objects and motion sync */ if (scene->need_motion() == Scene::MOTION_PASS || scene->need_motion() == Scene::MOTION_NONE || - scene->camera->get_motion_position() == Camera::MOTION_POSITION_CENTER) { + scene->camera->get_motion_position() == MOTION_POSITION_CENTER) { sync_objects(b_depsgraph, b_v3d); } sync_motion(b_render, b_depsgraph, b_v3d, b_override, width, height, python_thread_state); diff --git a/intern/cycles/blender/volume.cpp b/intern/cycles/blender/volume.cpp index 381b3385a5a..8dd2d45c0b6 100644 --- a/intern/cycles/blender/volume.cpp +++ b/intern/cycles/blender/volume.cpp @@ -168,7 +168,8 @@ class BlenderSmokeLoader : public ImageLoader { AttributeStandard attribute; }; -static void sync_smoke_volume(Scene *scene, BObjectInfo &b_ob_info, Volume *volume, float frame) +static void sync_smoke_volume( + BL::Scene &b_scene, Scene *scene, BObjectInfo &b_ob_info, Volume *volume, float frame) { if (!b_ob_info.is_real_object_data()) { return; @@ -178,6 +179,18 @@ static void sync_smoke_volume(Scene *scene, BObjectInfo &b_ob_info, Volume *volu return; } + float velocity_scale = b_domain.velocity_scale(); + /* Motion blur attribute is relative to seconds, we need it relative to frames. */ + const bool need_motion = object_need_motion_attribute(b_ob_info, scene); + const float motion_scale = (need_motion) ? + scene->motion_shutter_time() / + (b_scene.render().fps() / b_scene.render().fps_base()) : + 0.0f; + + velocity_scale *= motion_scale; + + volume->set_velocity_scale(velocity_scale); + AttributeStandard attributes[] = {ATTR_STD_VOLUME_DENSITY, ATTR_STD_VOLUME_COLOR, ATTR_STD_VOLUME_FLAME, @@ -234,6 +247,7 @@ class BlenderVolumeLoader : public VDBImageLoader { }; static void sync_volume_object(BL::BlendData &b_data, + BL::Scene &b_scene, BObjectInfo &b_ob_info, Scene *scene, Volume *volume) @@ -247,6 +261,20 @@ static void sync_volume_object(BL::BlendData &b_data, volume->set_step_size(b_render.step_size()); volume->set_object_space((b_render.space() == BL::VolumeRender::space_OBJECT)); + float velocity_scale = b_volume.velocity_scale(); + if (b_volume.velocity_unit() == BL::Volume::velocity_unit_SECOND) { + /* Motion blur attribute is relative to seconds, we need it relative to frames. */ + const bool need_motion = object_need_motion_attribute(b_ob_info, scene); + const float motion_scale = (need_motion) ? + scene->motion_shutter_time() / + (b_scene.render().fps() / b_scene.render().fps_base()) : + 0.0f; + + velocity_scale *= motion_scale; + } + + volume->set_velocity_scale(velocity_scale); + /* Find grid with matching name. */ for (BL::VolumeGrid &b_grid : b_volume.grids) { ustring name = ustring(b_grid.name()); @@ -267,9 +295,22 @@ static void sync_volume_object(BL::BlendData &b_data, else if (name == Attribute::standard_name(ATTR_STD_VOLUME_TEMPERATURE)) { std = ATTR_STD_VOLUME_TEMPERATURE; } - else if (name == Attribute::standard_name(ATTR_STD_VOLUME_VELOCITY)) { + else if (name == Attribute::standard_name(ATTR_STD_VOLUME_VELOCITY) || + name == b_volume.velocity_grid()) { std = ATTR_STD_VOLUME_VELOCITY; } + else if (name == Attribute::standard_name(ATTR_STD_VOLUME_VELOCITY_X) || + name == b_volume.velocity_x_grid()) { + std = ATTR_STD_VOLUME_VELOCITY_X; + } + else if (name == Attribute::standard_name(ATTR_STD_VOLUME_VELOCITY_Y) || + name == b_volume.velocity_y_grid()) { + std = ATTR_STD_VOLUME_VELOCITY_Y; + } + else if (name == Attribute::standard_name(ATTR_STD_VOLUME_VELOCITY_Z) || + name == b_volume.velocity_z_grid()) { + std = ATTR_STD_VOLUME_VELOCITY_Z; + } if ((std != ATTR_STD_NONE && volume->need_attribute(scene, std)) || volume->need_attribute(scene, name)) { @@ -294,11 +335,11 @@ void BlenderSync::sync_volume(BObjectInfo &b_ob_info, Volume *volume) if (b_ob_info.object_data.is_a(&RNA_Volume)) { /* Volume object. Create only attributes, bounding mesh will then * be automatically generated later. */ - sync_volume_object(b_data, b_ob_info, scene, volume); + sync_volume_object(b_data, b_scene, b_ob_info, scene, volume); } else { /* Smoke domain. */ - sync_smoke_volume(scene, b_ob_info, volume, b_scene.frame_current()); + sync_smoke_volume(b_scene, scene, b_ob_info, volume, b_scene.frame_current()); } } diff --git a/intern/cycles/kernel/integrator/shader_eval.h b/intern/cycles/kernel/integrator/shader_eval.h index 3066fb661a1..3ea53b3e647 100644 --- a/intern/cycles/kernel/integrator/shader_eval.h +++ b/intern/cycles/kernel/integrator/shader_eval.h @@ -831,6 +831,65 @@ ccl_device_inline void shader_eval_volume(KernelGlobals kg, /* todo: this is inefficient for motion blur, we should be * caching matrices instead of recomputing them each step */ shader_setup_object_transforms(kg, sd, sd->time); + + if ((sd->object_flag & SD_OBJECT_HAS_VOLUME_MOTION) != 0) { + AttributeDescriptor v_desc = find_attribute(kg, sd, ATTR_STD_VOLUME_VELOCITY); + kernel_assert(v_desc.offset != ATTR_STD_NOT_FOUND); + + const float3 P = sd->P; + const float velocity_scale = kernel_tex_fetch(__objects, sd->object).velocity_scale; + const float time_offset = kernel_data.cam.motion_position == MOTION_POSITION_CENTER ? + 0.5f : + 0.0f; + const float time = kernel_data.cam.motion_position == MOTION_POSITION_END ? + (1.0f - kernel_data.cam.shuttertime) + sd->time : + sd->time; + + /* Use a 1st order semi-lagrangian advection scheme to estimate what volume quantity + * existed, or will exist, at the given time: + * + * `phi(x, T) = phi(x - (T - t) * u(x, T), t)` + * + * where + * + * x : position + * T : super-sampled time (or ray time) + * t : current time of the simulation (in rendering we assume this is center frame with + * relative time = 0) + * phi : the volume quantity + * u : the velocity field + * + * But first we need to determine the velocity field `u(x, T)`, which we can estimate also + * using semi-lagrangian advection. + * + * `u(x, T) = u(x - (T - t) * u(x, T), t)` + * + * This is the typical way to model self-advection in fluid dynamics, however, we do not + * account for other forces affecting the velocity during simulation (pressure, buyoancy, + * etc.): this gives a linear interpolation when fluid are mostly "curvy". For better + * results, a higher order interpolation scheme can be used (at the cost of more lookups), + * or an interpolation of the velocity fields for the previous and next frames could also + * be used to estimate `u(x, T)` (which will cost more memory and lookups). + * + * References: + * "Eulerian Motion Blur", Kim and Ko, 2007 + * "Production Volume Rendering", Wreninge et al., 2012 + */ + + /* Find velocity. */ + float3 velocity = primitive_volume_attribute_float3(kg, sd, v_desc); + object_dir_transform(kg, sd, &velocity); + + /* Find advected P. */ + sd->P = P - (time - time_offset) * velocity_scale * velocity; + + /* Find advected velocity. */ + velocity = primitive_volume_attribute_float3(kg, sd, v_desc); + object_dir_transform(kg, sd, &velocity); + + /* Find advected P. */ + sd->P = P - (time - time_offset) * velocity_scale * velocity; + } # endif } diff --git a/intern/cycles/kernel/types.h b/intern/cycles/kernel/types.h index 422285cd346..01df7948241 100644 --- a/intern/cycles/kernel/types.h +++ b/intern/cycles/kernel/types.h @@ -489,6 +489,18 @@ enum PanoramaType { PANORAMA_NUM_TYPES, }; +/* Specifies an offset for the shutter's time interval. */ +enum MotionPosition { + /* Shutter opens at the current frame. */ + MOTION_POSITION_START = 0, + /* Shutter is fully open at the current frame. */ + MOTION_POSITION_CENTER = 1, + /* Shutter closes at the current frame. */ + MOTION_POSITION_END = 2, + + MOTION_NUM_POSITIONS, +}; + /* Direct Light Sampling */ enum DirectLightSamplingType { @@ -635,6 +647,9 @@ typedef enum AttributeStandard { ATTR_STD_VOLUME_HEAT, ATTR_STD_VOLUME_TEMPERATURE, ATTR_STD_VOLUME_VELOCITY, + ATTR_STD_VOLUME_VELOCITY_X, + ATTR_STD_VOLUME_VELOCITY_Y, + ATTR_STD_VOLUME_VELOCITY_Z, ATTR_STD_POINTINESS, ATTR_STD_RANDOM_PER_ISLAND, ATTR_STD_SHADOW_TRANSPARENCY, @@ -808,6 +823,8 @@ enum ShaderDataObjectFlag { SD_OBJECT_CAUSTICS_CASTER = (1 << 9), /* object is caustics receiver */ SD_OBJECT_CAUSTICS_RECEIVER = (1 << 10), + /* object has attribute for volume motion */ + SD_OBJECT_HAS_VOLUME_MOTION = (1 << 11), /* object is using caustics */ SD_OBJECT_CAUSTICS = (SD_OBJECT_CAUSTICS_CASTER | SD_OBJECT_CAUSTICS_RECEIVER), @@ -815,7 +832,8 @@ enum ShaderDataObjectFlag { SD_OBJECT_FLAGS = (SD_OBJECT_HOLDOUT_MASK | SD_OBJECT_MOTION | SD_OBJECT_TRANSFORM_APPLIED | SD_OBJECT_NEGATIVE_SCALE_APPLIED | SD_OBJECT_HAS_VOLUME | SD_OBJECT_INTERSECTS_VOLUME | SD_OBJECT_SHADOW_CATCHER | - SD_OBJECT_HAS_VOLUME_ATTRIBUTES | SD_OBJECT_CAUSTICS) + SD_OBJECT_HAS_VOLUME_ATTRIBUTES | SD_OBJECT_CAUSTICS | + SD_OBJECT_HAS_VOLUME_MOTION) }; typedef struct ccl_align(16) ShaderData @@ -1040,7 +1058,7 @@ typedef struct KernelCamera { int rolling_shutter_type; float rolling_shutter_duration; - int pad; + int motion_position; } KernelCamera; static_assert_align(KernelCamera, 16); @@ -1386,7 +1404,8 @@ typedef struct KernelObject { uint visibility; int primitive_type; - int pad1; + /* Volume velocity scale. */ + float velocity_scale; } KernelObject; static_assert_align(KernelObject, 16); diff --git a/intern/cycles/scene/attribute.cpp b/intern/cycles/scene/attribute.cpp index 0ca602362bc..df01189a54b 100644 --- a/intern/cycles/scene/attribute.cpp +++ b/intern/cycles/scene/attribute.cpp @@ -360,6 +360,12 @@ const char *Attribute::standard_name(AttributeStandard std) return "temperature"; case ATTR_STD_VOLUME_VELOCITY: return "velocity"; + case ATTR_STD_VOLUME_VELOCITY_X: + return "velocity_x"; + case ATTR_STD_VOLUME_VELOCITY_Y: + return "velocity_y"; + case ATTR_STD_VOLUME_VELOCITY_Z: + return "velocity_z"; case ATTR_STD_POINTINESS: return "pointiness"; case ATTR_STD_RANDOM_PER_ISLAND: @@ -587,6 +593,9 @@ Attribute *AttributeSet::add(AttributeStandard std, ustring name) case ATTR_STD_VOLUME_FLAME: case ATTR_STD_VOLUME_HEAT: case ATTR_STD_VOLUME_TEMPERATURE: + case ATTR_STD_VOLUME_VELOCITY_X: + case ATTR_STD_VOLUME_VELOCITY_Y: + case ATTR_STD_VOLUME_VELOCITY_Z: attr = add(name, TypeDesc::TypeFloat, ATTR_ELEMENT_VOXEL); break; case ATTR_STD_VOLUME_COLOR: diff --git a/intern/cycles/scene/camera.cpp b/intern/cycles/scene/camera.cpp index 6aca2fcbb81..710f1c5ee90 100644 --- a/intern/cycles/scene/camera.cpp +++ b/intern/cycles/scene/camera.cpp @@ -397,6 +397,7 @@ void Camera::update(Scene *scene) /* motion blur */ kcam->shuttertime = (need_motion == Scene::MOTION_BLUR) ? shuttertime : -1.0f; + kcam->motion_position = motion_position; /* type */ kcam->type = camera_type; diff --git a/intern/cycles/scene/camera.h b/intern/cycles/scene/camera.h index 97bee430588..c150405acc2 100644 --- a/intern/cycles/scene/camera.h +++ b/intern/cycles/scene/camera.h @@ -30,18 +30,6 @@ class Camera : public Node { public: NODE_DECLARE - /* Specifies an offset for the shutter's time interval. */ - enum MotionPosition { - /* Shutter opens at the current frame. */ - MOTION_POSITION_START = 0, - /* Shutter is fully open at the current frame. */ - MOTION_POSITION_CENTER = 1, - /* Shutter closes at the current frame. */ - MOTION_POSITION_END = 2, - - MOTION_NUM_POSITIONS, - }; - /* Specifies rolling shutter effect. */ enum RollingShutterType { /* No rolling shutter effect. */ diff --git a/intern/cycles/scene/geometry.cpp b/intern/cycles/scene/geometry.cpp index 351ec4f09ae..349d8ad39c7 100644 --- a/intern/cycles/scene/geometry.cpp +++ b/intern/cycles/scene/geometry.cpp @@ -1541,7 +1541,7 @@ void GeometryManager::device_update_preprocess(Device *device, Scene *scene, Pro } Volume *volume = static_cast<Volume *>(geom); - create_volume_mesh(volume, progress); + create_volume_mesh(scene, volume, progress); /* always reallocate when we have a volume, as we need to rebuild the BVH */ device_update_flags |= DEVICE_MESH_DATA_NEEDS_REALLOC; diff --git a/intern/cycles/scene/geometry.h b/intern/cycles/scene/geometry.h index 0c2e70d483d..6210a64509a 100644 --- a/intern/cycles/scene/geometry.h +++ b/intern/cycles/scene/geometry.h @@ -216,7 +216,7 @@ class GeometryManager { protected: bool displace(Device *device, Scene *scene, Mesh *mesh, Progress &progress); - void create_volume_mesh(Volume *volume, Progress &progress); + void create_volume_mesh(const Scene *scene, Volume *volume, Progress &progress); /* Attributes */ void update_osl_attributes(Device *device, diff --git a/intern/cycles/scene/image_vdb.cpp b/intern/cycles/scene/image_vdb.cpp index 9906606a959..8f30433c229 100644 --- a/intern/cycles/scene/image_vdb.cpp +++ b/intern/cycles/scene/image_vdb.cpp @@ -66,6 +66,11 @@ struct ToNanoOp { # endif #endif +VDBImageLoader::VDBImageLoader(openvdb::GridBase::ConstPtr grid_, const string &grid_name) + : grid_name(grid_name), grid(grid_) +{ +} + VDBImageLoader::VDBImageLoader(const string &grid_name) : grid_name(grid_name) { } diff --git a/intern/cycles/scene/image_vdb.h b/intern/cycles/scene/image_vdb.h index c851ef6250d..4c1aba80af8 100644 --- a/intern/cycles/scene/image_vdb.h +++ b/intern/cycles/scene/image_vdb.h @@ -17,6 +17,7 @@ CCL_NAMESPACE_BEGIN class VDBImageLoader : public ImageLoader { public: + VDBImageLoader(openvdb::GridBase::ConstPtr grid_, const string &grid_name); VDBImageLoader(const string &grid_name); ~VDBImageLoader(); diff --git a/intern/cycles/scene/object.cpp b/intern/cycles/scene/object.cpp index 55d89fc3673..c57d26464ed 100644 --- a/intern/cycles/scene/object.cpp +++ b/intern/cycles/scene/object.cpp @@ -439,6 +439,14 @@ void ObjectManager::device_update_object_transform(UpdateObjectTransformState *s flag |= SD_OBJECT_HAS_VERTEX_MOTION; } } + else if (geom->is_volume()) { + Volume *volume = static_cast<Volume *>(geom); + if (volume->attributes.find(ATTR_STD_VOLUME_VELOCITY) && + volume->get_velocity_scale() != 0.0f) { + flag |= SD_OBJECT_HAS_VOLUME_MOTION; + kobject.velocity_scale = volume->get_velocity_scale(); + } + } if (state->need_motion == Scene::MOTION_PASS) { /* Clear motion array if there is no actual motion. */ @@ -488,7 +496,7 @@ void ObjectManager::device_update_object_transform(UpdateObjectTransformState *s kobject.dupli_generated[2] = ob->dupli_generated[2]; kobject.numkeys = (geom->geometry_type == Geometry::HAIR) ? static_cast<Hair *>(geom)->get_curve_keys().size() : - (geom->geometry_type == Geometry::POINTCLOUD) ? + (geom->geometry_type == Geometry::POINTCLOUD) ? static_cast<PointCloud *>(geom)->num_points() : 0; kobject.dupli_uv[0] = ob->dupli_uv[0]; diff --git a/intern/cycles/scene/scene.cpp b/intern/cycles/scene/scene.cpp index b6b53004816..b35242139ea 100644 --- a/intern/cycles/scene/scene.cpp +++ b/intern/cycles/scene/scene.cpp @@ -381,7 +381,7 @@ void Scene::device_update(Device *device_, Progress &progress) } } -Scene::MotionType Scene::need_motion() +Scene::MotionType Scene::need_motion() const { if (integrator->get_motion_blur()) return MOTION_BLUR; @@ -407,6 +407,10 @@ bool Scene::need_global_attribute(AttributeStandard std) return need_motion() != MOTION_NONE; else if (std == ATTR_STD_MOTION_VERTEX_NORMAL) return need_motion() == MOTION_BLUR; + else if (std == ATTR_STD_VOLUME_VELOCITY || std == ATTR_STD_VOLUME_VELOCITY_X || + std == ATTR_STD_VOLUME_VELOCITY_Y || std == ATTR_STD_VOLUME_VELOCITY_Z) { + return need_motion() != MOTION_NONE; + } return false; } diff --git a/intern/cycles/scene/scene.h b/intern/cycles/scene/scene.h index 54c7d9d93ea..a0d2f4a6c06 100644 --- a/intern/cycles/scene/scene.h +++ b/intern/cycles/scene/scene.h @@ -257,7 +257,7 @@ class Scene : public NodeOwner { void need_global_attributes(AttributeRequestSet &attributes); enum MotionType { MOTION_NONE = 0, MOTION_PASS, MOTION_BLUR }; - MotionType need_motion(); + MotionType need_motion() const; float motion_shutter_time(); bool need_update(); diff --git a/intern/cycles/scene/volume.cpp b/intern/cycles/scene/volume.cpp index 0555fdd2fad..1357d5ed762 100644 --- a/intern/cycles/scene/volume.cpp +++ b/intern/cycles/scene/volume.cpp @@ -10,9 +10,9 @@ # include <openvdb/tools/Dense.h> # include <openvdb/tools/GridTransformer.h> # include <openvdb/tools/Morphology.h> +# include <openvdb/tools/Statistics.h> #endif -#include "util/foreach.h" #include "util/hash.h" #include "util/log.h" #include "util/openvdb.h" @@ -28,6 +28,7 @@ NODE_DEFINE(Volume) SOCKET_FLOAT(clipping, "Clipping", 0.001f); SOCKET_FLOAT(step_size, "Step Size", 0.0f); SOCKET_BOOLEAN(object_space, "Object Space", false); + SOCKET_FLOAT(velocity_scale, "Velocity Scale", 1.0f); return type; } @@ -482,11 +483,141 @@ static openvdb::GridBase::ConstPtr openvdb_grid_from_device_texture(device_textu return sparse; } + +static int estimate_required_velocity_padding(openvdb::GridBase::ConstPtr grid, + float velocity_scale) +{ + /* TODO: we may need to also find outliers and clamp them to avoid adding too much padding. */ + openvdb::math::Extrema extrema; + openvdb::Vec3d voxel_size; + + /* External .vdb files have a vec3 type for velocity, but the Blender exporter creates a vec4. */ + if (grid->isType<openvdb::Vec3fGrid>()) { + openvdb::Vec3fGrid::ConstPtr vel_grid = openvdb::gridConstPtrCast<openvdb::Vec3fGrid>(grid); + extrema = openvdb::tools::extrema(vel_grid->cbeginValueOn()); + voxel_size = vel_grid->voxelSize(); + } + else if (grid->isType<openvdb::Vec4fGrid>()) { + openvdb::Vec4fGrid::ConstPtr vel_grid = openvdb::gridConstPtrCast<openvdb::Vec4fGrid>(grid); + extrema = openvdb::tools::extrema(vel_grid->cbeginValueOn()); + voxel_size = vel_grid->voxelSize(); + } + else { + assert(0); + return 0; + } + + /* We should only have uniform grids, so x = y = z, but we never know. */ + const double max_voxel_size = openvdb::math::Max(voxel_size.x(), voxel_size.y(), voxel_size.z()); + if (max_voxel_size == 0.0) { + return 0; + } + + const double estimated_padding = extrema.max() * static_cast<double>(velocity_scale) / + max_voxel_size; + + return static_cast<int>(std::ceil(estimated_padding)); +} + +static openvdb::FloatGrid::ConstPtr get_vdb_for_attribute(Volume *volume, AttributeStandard std) +{ + Attribute *attr = volume->attributes.find(std); + if (!attr) { + return nullptr; + } + + ImageHandle &handle = attr->data_voxel(); + VDBImageLoader *vdb_loader = handle.vdb_loader(); + if (!vdb_loader) { + return nullptr; + } + + openvdb::GridBase::ConstPtr grid = vdb_loader->get_grid(); + if (!grid) { + return nullptr; + } + + if (!grid->isType<openvdb::FloatGrid>()) { + return nullptr; + } + + return openvdb::gridConstPtrCast<openvdb::FloatGrid>(grid); +} + +class MergeScalarGrids { + typedef openvdb::FloatTree ScalarTree; + + openvdb::tree::ValueAccessor<const ScalarTree> m_acc_x, m_acc_y, m_acc_z; + + public: + MergeScalarGrids(const ScalarTree *x_tree, const ScalarTree *y_tree, const ScalarTree *z_tree) + : m_acc_x(*x_tree), m_acc_y(*y_tree), m_acc_z(*z_tree) + { + } + + MergeScalarGrids(const MergeScalarGrids &other) + : m_acc_x(other.m_acc_x), m_acc_y(other.m_acc_y), m_acc_z(other.m_acc_z) + { + } + + void operator()(const openvdb::Vec3STree::ValueOnIter &it) const + { + using namespace openvdb; + + const math::Coord xyz = it.getCoord(); + float x = m_acc_x.getValue(xyz); + float y = m_acc_y.getValue(xyz); + float z = m_acc_z.getValue(xyz); + + it.setValue(math::Vec3s(x, y, z)); + } +}; + +static void merge_scalar_grids_for_velocity(const Scene *scene, Volume *volume) +{ + if (volume->attributes.find(ATTR_STD_VOLUME_VELOCITY)) { + /* A vector grid for velocity is already available. */ + return; + } + + openvdb::FloatGrid::ConstPtr vel_x_grid = get_vdb_for_attribute(volume, + ATTR_STD_VOLUME_VELOCITY_X); + openvdb::FloatGrid::ConstPtr vel_y_grid = get_vdb_for_attribute(volume, + ATTR_STD_VOLUME_VELOCITY_Y); + openvdb::FloatGrid::ConstPtr vel_z_grid = get_vdb_for_attribute(volume, + ATTR_STD_VOLUME_VELOCITY_Z); + + if (!(vel_x_grid && vel_y_grid && vel_z_grid)) { + return; + } + + openvdb::Vec3fGrid::Ptr vecgrid = openvdb::Vec3SGrid::create(openvdb::Vec3s(0.0f)); + + /* Activate voxels in the vector grid based on the scalar grids to ensure thread safety during + * the merge. */ + vecgrid->tree().topologyUnion(vel_x_grid->tree()); + vecgrid->tree().topologyUnion(vel_y_grid->tree()); + vecgrid->tree().topologyUnion(vel_z_grid->tree()); + + MergeScalarGrids op(&vel_x_grid->tree(), &vel_y_grid->tree(), &vel_z_grid->tree()); + openvdb::tools::foreach (vecgrid->beginValueOn(), op, true, false); + + /* Assume all grids have the same transformation. */ + openvdb::math::Transform::Ptr transform = openvdb::ConstPtrCast<openvdb::math::Transform>( + vel_x_grid->transformPtr()); + vecgrid->setTransform(transform); + + /* Make an attribute for it. */ + Attribute *attr = volume->attributes.add(ATTR_STD_VOLUME_VELOCITY); + ImageLoader *loader = new VDBImageLoader(vecgrid, "merged_velocity"); + ImageParams params; + attr->data_voxel() = scene->image_manager->add_image(loader, params); +} #endif /* ************************************************************************** */ -void GeometryManager::create_volume_mesh(Volume *volume, Progress &progress) +void GeometryManager::create_volume_mesh(const Scene *scene, Volume *volume, Progress &progress) { string msg = string_printf("Computing Volume Mesh %s", volume->name.c_str()); progress.set_status("Updating Mesh", msg); @@ -495,7 +626,7 @@ void GeometryManager::create_volume_mesh(Volume *volume, Progress &progress) Shader *volume_shader = NULL; int pad_size = 0; - foreach (Node *node, volume->get_used_shaders()) { + for (Node *node : volume->get_used_shaders()) { Shader *shader = static_cast<Shader *>(node); if (!shader->has_volume) { @@ -529,7 +660,9 @@ void GeometryManager::create_volume_mesh(Volume *volume, Progress &progress) VolumeMeshBuilder builder; #ifdef WITH_OPENVDB - foreach (Attribute &attr, volume->attributes.attributes) { + merge_scalar_grids_for_velocity(scene, volume); + + for (Attribute &attr : volume->attributes.attributes) { if (attr.element != ATTR_ELEMENT_VOXEL) { continue; } @@ -567,9 +700,17 @@ void GeometryManager::create_volume_mesh(Volume *volume, Progress &progress) } if (grid) { + /* Add padding based on the maximum velocity vector. */ + if (attr.std == ATTR_STD_VOLUME_VELOCITY && scene->need_motion() != Scene::MOTION_NONE) { + pad_size = max(pad_size, + estimate_required_velocity_padding(grid, volume->get_velocity_scale())); + } + builder.add_grid(grid, do_clipping, volume->get_clipping()); } } +#else + (void)scene; #endif /* If nothing to build, early out. */ diff --git a/intern/cycles/scene/volume.h b/intern/cycles/scene/volume.h index cae0f5f5bce..2b94aaf5253 100644 --- a/intern/cycles/scene/volume.h +++ b/intern/cycles/scene/volume.h @@ -18,6 +18,7 @@ class Volume : public Mesh { NODE_SOCKET_API(float, clipping) NODE_SOCKET_API(float, step_size) NODE_SOCKET_API(bool, object_space) + NODE_SOCKET_API(float, velocity_scale) virtual void clear(bool preserve_shaders = false) override; }; diff --git a/release/scripts/startup/bl_ui/properties_data_volume.py b/release/scripts/startup/bl_ui/properties_data_volume.py index 9fdcd4c1faa..678972a677c 100644 --- a/release/scripts/startup/bl_ui/properties_data_volume.py +++ b/release/scripts/startup/bl_ui/properties_data_volume.py @@ -115,6 +115,12 @@ class DATA_PT_volume_render(DataButtonsPanel, Panel): col = layout.column(align=True) col.prop(render, "clipping") + col = layout.column(align=False) + col.prop(volume, "velocity_grid") + + col.prop(volume, "velocity_unit") + col.prop(volume, "velocity_scale") + class DATA_PT_volume_viewport_display(DataButtonsPanel, Panel): bl_label = "Viewport Display" diff --git a/release/scripts/startup/bl_ui/properties_physics_fluid.py b/release/scripts/startup/bl_ui/properties_physics_fluid.py index 1b03da490b9..88f8658035b 100644 --- a/release/scripts/startup/bl_ui/properties_physics_fluid.py +++ b/release/scripts/startup/bl_ui/properties_physics_fluid.py @@ -1485,6 +1485,27 @@ class PHYSICS_PT_viewport_display_advanced(PhysicButtonsPanel, Panel): note.label(icon='INFO', text="Range highlighting for flags is not available!") +class PHYSICS_PT_fluid_domain_render(PhysicButtonsPanel, Panel): + bl_label = "Render" + bl_parent_id = 'PHYSICS_PT_fluid' + bl_options = {'DEFAULT_CLOSED'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + + @classmethod + def poll(cls, context): + if not PhysicButtonsPanel.poll_gas_domain(context): + return False + + return (context.engine in cls.COMPAT_ENGINES) + + def draw(self, context): + layout = self.layout + layout.use_property_split = True + + domain = context.fluid.domain_settings + layout.prop(domain, "velocity_scale") + + classes = ( FLUID_PT_presets, PHYSICS_PT_fluid, @@ -1513,6 +1534,7 @@ classes = ( PHYSICS_PT_viewport_display_color, PHYSICS_PT_viewport_display_debug, PHYSICS_PT_viewport_display_advanced, + PHYSICS_PT_fluid_domain_render, ) diff --git a/source/blender/blenkernel/BKE_volume.h b/source/blender/blenkernel/BKE_volume.h index 77f01e7919d..8791469360a 100644 --- a/source/blender/blenkernel/BKE_volume.h +++ b/source/blender/blenkernel/BKE_volume.h @@ -77,6 +77,11 @@ const VolumeGrid *BKE_volume_grid_active_get_for_read(const struct Volume *volum /* Tries to find a grid with the given name. Make sure that the volume has been loaded. */ const VolumeGrid *BKE_volume_grid_find_for_read(const struct Volume *volume, const char *name); +/* Tries to set the name of the velocity field. If no such grid exists with the given base name, + * this will try common postfixes in order to detect velocity fields split into multiple grids. + * Return false if neither finding with the base name nor with the postfixes succeeded. */ +bool BKE_volume_set_velocity_grid_by_name(struct Volume *volume, const char *base_name); + /* Grid * * By default only grid metadata is loaded, for access to the tree and voxels diff --git a/source/blender/blenkernel/intern/fluid.c b/source/blender/blenkernel/intern/fluid.c index 81e73b6cf2c..efb33294efd 100644 --- a/source/blender/blenkernel/intern/fluid.c +++ b/source/blender/blenkernel/intern/fluid.c @@ -5071,6 +5071,9 @@ void BKE_fluid_modifier_copy(const struct FluidModifierData *fmd, tfds->openvdb_compression = fds->openvdb_compression; tfds->clipping = fds->clipping; tfds->openvdb_data_depth = fds->openvdb_data_depth; + + /* Render options. */ + tfds->velocity_scale = fds->velocity_scale; } else if (tfmd->flow) { FluidFlowSettings *tffs = tfmd->flow; diff --git a/source/blender/blenkernel/intern/volume.cc b/source/blender/blenkernel/intern/volume.cc index 0c131863edd..307466d7dc9 100644 --- a/source/blender/blenkernel/intern/volume.cc +++ b/source/blender/blenkernel/intern/volume.cc @@ -60,6 +60,7 @@ using blender::float3; using blender::float4x4; using blender::IndexRange; using blender::StringRef; +using blender::StringRefNull; #ifdef WITH_OPENVDB # include <atomic> @@ -517,6 +518,8 @@ static void volume_init_data(ID *id) MEMCPY_STRUCT_AFTER(volume, DNA_struct_default_get(Volume), id); BKE_volume_init_grids(volume); + + BLI_strncpy(volume->velocity_grid, "velocity", sizeof(volume->velocity_grid)); } static void volume_copy_data(Main *UNUSED(bmain), @@ -794,6 +797,57 @@ bool BKE_volume_is_loaded(const Volume *volume) #endif } +bool BKE_volume_set_velocity_grid_by_name(Volume *volume, const char *base_name) +{ + const StringRefNull ref_base_name = base_name; + + if (BKE_volume_grid_find_for_read(volume, base_name)) { + BLI_strncpy(volume->velocity_grid, base_name, sizeof(volume->velocity_grid)); + volume->runtime.velocity_x_grid[0] = '\0'; + volume->runtime.velocity_y_grid[0] = '\0'; + volume->runtime.velocity_z_grid[0] = '\0'; + return true; + } + + /* It could be that the velocity grid is split in multiple grids, try with known postfixes. */ + const StringRefNull postfixes[][3] = {{"x", "y", "z"}, {".x", ".y", ".z"}, {"_x", "_y", "_z"}}; + + for (const StringRefNull *postfix : postfixes) { + bool found = true; + for (int i = 0; i < 3; i++) { + std::string post_fixed_name = ref_base_name + postfix[i]; + if (!BKE_volume_grid_find_for_read(volume, post_fixed_name.c_str())) { + found = false; + break; + } + } + + if (!found) { + continue; + } + + /* Save the base name as well. */ + BLI_strncpy(volume->velocity_grid, base_name, sizeof(volume->velocity_grid)); + BLI_strncpy(volume->runtime.velocity_x_grid, + (ref_base_name + postfix[0]).c_str(), + sizeof(volume->runtime.velocity_x_grid)); + BLI_strncpy(volume->runtime.velocity_y_grid, + (ref_base_name + postfix[1]).c_str(), + sizeof(volume->runtime.velocity_y_grid)); + BLI_strncpy(volume->runtime.velocity_z_grid, + (ref_base_name + postfix[2]).c_str(), + sizeof(volume->runtime.velocity_z_grid)); + return true; + } + + /* Reset to avoid potential issues. */ + volume->velocity_grid[0] = '\0'; + volume->runtime.velocity_x_grid[0] = '\0'; + volume->runtime.velocity_y_grid[0] = '\0'; + volume->runtime.velocity_z_grid[0] = '\0'; + return false; +} + bool BKE_volume_load(const Volume *volume, const Main *bmain) { #ifdef WITH_OPENVDB @@ -857,6 +911,14 @@ bool BKE_volume_load(const Volume *volume, const Main *bmain) } } + /* Try to detect the velocity grid. */ + const char *common_velocity_names[] = {"velocity", "vel", "v"}; + for (const char *common_velocity_name : common_velocity_names) { + if (BKE_volume_set_velocity_grid_by_name(const_cast<Volume *>(volume), common_velocity_name)) { + break; + } + } + BLI_strncpy(grids.filepath, filepath, FILE_MAX); return grids.error_msg.empty(); diff --git a/source/blender/makesdna/DNA_fluid_defaults.h b/source/blender/makesdna/DNA_fluid_defaults.h index fd48585792f..90a91c6c995 100644 --- a/source/blender/makesdna/DNA_fluid_defaults.h +++ b/source/blender/makesdna/DNA_fluid_defaults.h @@ -187,6 +187,7 @@ .cache_comp = SM_CACHE_LIGHT, \ .cache_high_comp = SM_CACHE_LIGHT, \ .cache_file_format = 0, \ + .velocity_scale = 1.0f, \ } /** \} */ diff --git a/source/blender/makesdna/DNA_fluid_types.h b/source/blender/makesdna/DNA_fluid_types.h index 11780d99af8..5a1636879bb 100644 --- a/source/blender/makesdna/DNA_fluid_types.h +++ b/source/blender/makesdna/DNA_fluid_types.h @@ -670,7 +670,10 @@ typedef struct FluidDomainSettings { char interp_method; char gridlines_color_field; /* Simulation field used to color map onto gridlines. */ char gridlines_cell_filter; - char _pad10[7]; /* Unused. */ + char _pad10[3]; /* Unused. */ + + /* Velocity factor for motion blur rendering. */ + float velocity_scale; /* OpenVDB cache options. */ int openvdb_compression; diff --git a/source/blender/makesdna/DNA_volume_defaults.h b/source/blender/makesdna/DNA_volume_defaults.h index 2025d664d40..ee98f0ea4fd 100644 --- a/source/blender/makesdna/DNA_volume_defaults.h +++ b/source/blender/makesdna/DNA_volume_defaults.h @@ -36,7 +36,8 @@ .frame_duration = 0, \ .display = _DNA_DEFAULT_VolumeDisplay, \ .render = _DNA_DEFAULT_VolumeRender, \ - } + .velocity_scale = 1.0f, \ +} /** \} */ diff --git a/source/blender/makesdna/DNA_volume_types.h b/source/blender/makesdna/DNA_volume_types.h index f2f53bc910f..a2e558aa790 100644 --- a/source/blender/makesdna/DNA_volume_types.h +++ b/source/blender/makesdna/DNA_volume_types.h @@ -24,6 +24,11 @@ typedef struct Volume_Runtime { /** Default simplify level for volume grids loaded from files. */ int default_simplify_level; + + /* Names for scalar grids which would need to be merged to recompose the velocity grid. */ + char velocity_x_grid[64]; + char velocity_y_grid[64]; + char velocity_z_grid[64]; } Volume_Runtime; typedef struct VolumeDisplay { @@ -75,6 +80,18 @@ typedef struct Volume { VolumeRender render; VolumeDisplay display; + /* Velocity field name. */ + char velocity_grid[64]; + + char _pad3[3]; + + /* Unit of time the velocity vectors are expressed in. + * This uses the same enumeration values as #CacheFile.velocity_unit. */ + char velocity_unit; + + /* Factor for velocity vector for artistic control. */ + float velocity_scale; + /* Draw Cache */ void *batch_cache; diff --git a/source/blender/makesrna/RNA_enum_items.h b/source/blender/makesrna/RNA_enum_items.h index 6e2c898d691..6539d636697 100644 --- a/source/blender/makesrna/RNA_enum_items.h +++ b/source/blender/makesrna/RNA_enum_items.h @@ -212,6 +212,8 @@ DEF_ENUM(rna_enum_subdivision_boundary_smooth_items) DEF_ENUM(rna_enum_transform_orientation_items) +DEF_ENUM(rna_enum_velocity_unit_items) + /* Not available to RNA pre-processing (`makesrna`). * Defined in editors for example. */ #ifndef RNA_MAKESRNA diff --git a/source/blender/makesrna/intern/rna_cachefile.c b/source/blender/makesrna/intern/rna_cachefile.c index 62b08ebb281..c8b154b9b04 100644 --- a/source/blender/makesrna/intern/rna_cachefile.c +++ b/source/blender/makesrna/intern/rna_cachefile.c @@ -14,6 +14,12 @@ #include "rna_internal.h" +const EnumPropertyItem rna_enum_velocity_unit_items[] = { + {CACHEFILE_VELOCITY_UNIT_SECOND, "SECOND", 0, "Second", ""}, + {CACHEFILE_VELOCITY_UNIT_FRAME, "FRAME", 0, "Frame", ""}, + {0, NULL, 0, NULL, NULL}, +}; + #ifdef RNA_RUNTIME # include "BLI_math.h" @@ -350,15 +356,9 @@ static void rna_def_cachefile(BlenderRNA *brna) RNA_def_property_update(prop, 0, "rna_CacheFile_update"); RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); - static const EnumPropertyItem velocity_unit_items[] = { - {CACHEFILE_VELOCITY_UNIT_SECOND, "SECOND", 0, "Second", ""}, - {CACHEFILE_VELOCITY_UNIT_FRAME, "FRAME", 0, "Frame", ""}, - {0, NULL, 0, NULL, NULL}, - }; - prop = RNA_def_property(srna, "velocity_unit", PROP_ENUM, PROP_NONE); RNA_def_property_enum_sdna(prop, NULL, "velocity_unit"); - RNA_def_property_enum_items(prop, velocity_unit_items); + RNA_def_property_enum_items(prop, rna_enum_velocity_unit_items); RNA_def_property_ui_text( prop, "Velocity Unit", diff --git a/source/blender/makesrna/intern/rna_fluid.c b/source/blender/makesrna/intern/rna_fluid.c index dab3cd68d4c..e0ec146a248 100644 --- a/source/blender/makesrna/intern/rna_fluid.c +++ b/source/blender/makesrna/intern/rna_fluid.c @@ -2644,6 +2644,12 @@ static void rna_def_fluid_domain_settings(BlenderRNA *brna) RNA_def_property_enum_items(prop, gridlines_cell_filter_items); RNA_def_property_ui_text(prop, "Cell Type", "Cell type to be highlighted"); RNA_def_property_update(prop, NC_OBJECT | ND_DRAW, NULL); + + prop = RNA_def_property(srna, "velocity_scale", PROP_FLOAT, PROP_NONE); + RNA_def_property_float_sdna(prop, NULL, "velocity_scale"); + RNA_def_property_range(prop, 0.0f, FLT_MAX); + RNA_def_property_ui_text(prop, "Velocity Scale", "Factor to control the amount of motion blur"); + RNA_def_property_update(prop, NC_OBJECT | ND_MODIFIER, "rna_Fluid_update"); } static void rna_def_fluid_flow_settings(BlenderRNA *brna) diff --git a/source/blender/makesrna/intern/rna_volume.c b/source/blender/makesrna/intern/rna_volume.c index 5b323629a80..12cb35b239d 100644 --- a/source/blender/makesrna/intern/rna_volume.c +++ b/source/blender/makesrna/intern/rna_volume.c @@ -78,6 +78,15 @@ static void rna_Volume_update_is_sequence(Main *bmain, Scene *scene, PointerRNA DEG_relations_tag_update(bmain); } +static void rna_Volume_velocity_grid_set(PointerRNA *ptr, const char *value) +{ + Volume *volume = (Volume *)ptr->data; + if (!BKE_volume_set_velocity_grid_by_name(volume, value)) { + WM_reportf(RPT_ERROR, "Could not find grid with name %s", value); + } + WM_main_add_notifier(NC_GEOM | ND_DATA, volume); +} + /* Grid */ static void rna_VolumeGrid_name_get(PointerRNA *ptr, char *value) @@ -248,6 +257,7 @@ static void rna_def_volume_grid(BlenderRNA *brna) RNA_def_property_string_funcs( prop, "rna_VolumeGrid_name_get", "rna_VolumeGrid_name_length", NULL); RNA_def_property_ui_text(prop, "Name", "Volume grid name"); + RNA_def_struct_name_property(srna, prop); prop = RNA_def_property(srna, "data_type", PROP_ENUM, PROP_NONE); RNA_def_property_clear_flag(prop, PROP_EDITABLE); @@ -619,6 +629,55 @@ static void rna_def_volume(BlenderRNA *brna) RNA_def_property_struct_type(prop, "VolumeRender"); RNA_def_property_ui_text(prop, "Render", "Volume render settings for 3D viewport"); + /* Velocity. */ + prop = RNA_def_property(srna, "velocity_grid", PROP_STRING, PROP_NONE); + RNA_def_property_string_sdna(prop, NULL, "velocity_grid"); + RNA_def_property_string_funcs(prop, NULL, NULL, "rna_Volume_velocity_grid_set"); + RNA_def_property_ui_text( + prop, + "Velocity Grid", + "Name of the velocity field, or the base name if the velocity is split into multiple grids"); + + prop = RNA_def_property(srna, "velocity_unit", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, NULL, "velocity_unit"); + RNA_def_property_enum_items(prop, rna_enum_velocity_unit_items); + RNA_def_property_ui_text( + prop, + "Velocity Unit", + "Define how the velocity vectors are interpreted with regard to time, 'frame' means " + "the delta time is 1 frame, 'second' means the delta time is 1 / FPS"); + RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); + + prop = RNA_def_property(srna, "velocity_scale", PROP_FLOAT, PROP_NONE); + RNA_def_property_float_sdna(prop, NULL, "velocity_scale"); + RNA_def_property_range(prop, 0.0f, FLT_MAX); + RNA_def_property_ui_text(prop, "Velocity Scale", "Factor to control the amount of motion blur"); + + /* Scalar grids for velocity. */ + prop = RNA_def_property(srna, "velocity_x_grid", PROP_STRING, PROP_NONE); + RNA_def_property_string_sdna(prop, NULL, "runtime.velocity_x_grid"); + RNA_def_property_clear_flag(prop, PROP_EDITABLE); + RNA_def_property_ui_text(prop, + "Velocity X Grid", + "Name of the grid for the X axis component of the velocity field if it " + "was split into multiple grids"); + + prop = RNA_def_property(srna, "velocity_y_grid", PROP_STRING, PROP_NONE); + RNA_def_property_string_sdna(prop, NULL, "runtime.velocity_y_grid"); + RNA_def_property_clear_flag(prop, PROP_EDITABLE); + RNA_def_property_ui_text(prop, + "Velocity Y Grid", + "Name of the grid for the Y axis component of the velocity field if it " + "was split into multiple grids"); + + prop = RNA_def_property(srna, "velocity_z_grid", PROP_STRING, PROP_NONE); + RNA_def_property_string_sdna(prop, NULL, "runtime.velocity_z_grid"); + RNA_def_property_clear_flag(prop, PROP_EDITABLE); + RNA_def_property_ui_text(prop, + "Velocity Z Grid", + "Name of the grid for the Z axis component of the velocity field if it " + "was split into multiple grids"); + /* Common */ rna_def_animdata_common(srna); } |