Since their introduction, generated UDIM images/tiles would not be
loaded in Cycles. In relative recent history 72ab6faf5d added the
ability to track which tiles were still marked as being "generated" and
unsaved. However, that check was never implemented into the Cycles code.
With this PR, these two additional scenarios should now work:
- Properly load pixels if all tiles are generated
- Properly load pixels if a UDIM image has been loaded from a file and
new generated tiles have been added but not saved yet etc.
Pull Request: https://projects.blender.org/blender/blender/pulls/127673
791 lines
24 KiB
C++
791 lines
24 KiB
C++
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0 */
|
|
|
|
#ifndef __BLENDER_UTIL_H__
|
|
#define __BLENDER_UTIL_H__
|
|
|
|
#include "scene/mesh.h"
|
|
#include "scene/scene.h"
|
|
|
|
#include "util/algorithm.h"
|
|
#include "util/array.h"
|
|
#include "util/map.h"
|
|
#include "util/path.h"
|
|
#include "util/set.h"
|
|
#include "util/transform.h"
|
|
#include "util/types.h"
|
|
#include "util/vector.h"
|
|
|
|
#include "BKE_mesh.hh"
|
|
|
|
/* Hacks to hook into Blender API
|
|
* todo: clean this up ... */
|
|
|
|
extern "C" {
|
|
void BKE_image_user_frame_calc(void *ima, void *iuser, int cfra);
|
|
void BKE_image_user_file_path_ex(void *bmain,
|
|
void *iuser,
|
|
void *ima,
|
|
char *filepath,
|
|
bool resolve_udim,
|
|
bool resolve_multiview);
|
|
unsigned char *BKE_image_get_pixels_for_frame(void *image, int frame, int tile);
|
|
float *BKE_image_get_float_pixels_for_frame(void *image, int frame, int tile);
|
|
}
|
|
|
|
CCL_NAMESPACE_BEGIN
|
|
|
|
struct BObjectInfo {
|
|
/* Object directly provided by the depsgraph iterator. This object is only valid during one
|
|
* iteration and must not be accessed afterwards. Transforms and visibility should be checked on
|
|
* this object. */
|
|
BL::Object iter_object;
|
|
|
|
/* This object remains alive even after the object iterator is done. It corresponds to one
|
|
* original object. It is the object that owns the object data below. */
|
|
BL::Object real_object;
|
|
|
|
/* The object-data referenced by the iter object. This is still valid after the depsgraph
|
|
* iterator is done. It might have a different type compared to real_object.data(). */
|
|
BL::ID object_data;
|
|
|
|
/* True when the current geometry is the data of the referenced object. False when it is a
|
|
* geometry instance that does not have a 1-to-1 relationship with an object. */
|
|
bool is_real_object_data() const
|
|
{
|
|
return const_cast<BL::Object &>(real_object).data() == object_data;
|
|
}
|
|
};
|
|
|
|
typedef BL::ShaderNodeAttribute::attribute_type_enum BlenderAttributeType;
|
|
BlenderAttributeType blender_attribute_name_split_type(ustring name, string *r_real_name);
|
|
|
|
void python_thread_state_save(void **python_thread_state);
|
|
void python_thread_state_restore(void **python_thread_state);
|
|
|
|
static bool mesh_use_corner_normals(BL::Mesh &mesh, Mesh::SubdivisionType subdivision_type)
|
|
{
|
|
return mesh && (subdivision_type == Mesh::SUBDIVISION_NONE) &&
|
|
(static_cast<const ::Mesh *>(mesh.ptr.data)->normals_domain(true) ==
|
|
blender::bke::MeshNormalDomain::Corner);
|
|
}
|
|
|
|
static inline BL::Mesh object_to_mesh(BL::BlendData & /*data*/,
|
|
BObjectInfo &b_ob_info,
|
|
BL::Depsgraph & /*depsgraph*/,
|
|
bool /*calc_undeformed*/,
|
|
Mesh::SubdivisionType subdivision_type)
|
|
{
|
|
/* TODO: make this work with copy-on-evaluation, modifiers are already evaluated. */
|
|
#if 0
|
|
bool subsurf_mod_show_render = false;
|
|
bool subsurf_mod_show_viewport = false;
|
|
|
|
if (subdivision_type != Mesh::SUBDIVISION_NONE) {
|
|
BL::Modifier subsurf_mod = object.modifiers[object.modifiers.length() - 1];
|
|
|
|
subsurf_mod_show_render = subsurf_mod.show_render();
|
|
subsurf_mod_show_viewport = subsurf_mod.show_viewport();
|
|
|
|
subsurf_mod.show_render(false);
|
|
subsurf_mod.show_viewport(false);
|
|
}
|
|
#endif
|
|
|
|
BL::Mesh mesh = (b_ob_info.object_data.is_a(&RNA_Mesh)) ? BL::Mesh(b_ob_info.object_data) :
|
|
BL::Mesh(PointerRNA_NULL);
|
|
|
|
bool use_corner_normals = false;
|
|
|
|
if (b_ob_info.is_real_object_data()) {
|
|
if (mesh) {
|
|
if (mesh.is_editmode()) {
|
|
/* Flush edit-mesh to mesh, including all data layers. */
|
|
BL::Depsgraph depsgraph(PointerRNA_NULL);
|
|
mesh = b_ob_info.real_object.to_mesh(false, depsgraph);
|
|
use_corner_normals = mesh_use_corner_normals(mesh, subdivision_type);
|
|
}
|
|
else if (mesh_use_corner_normals(mesh, subdivision_type)) {
|
|
/* Make a copy to split faces. */
|
|
BL::Depsgraph depsgraph(PointerRNA_NULL);
|
|
mesh = b_ob_info.real_object.to_mesh(false, depsgraph);
|
|
use_corner_normals = true;
|
|
}
|
|
}
|
|
else {
|
|
BL::Depsgraph depsgraph(PointerRNA_NULL);
|
|
mesh = b_ob_info.real_object.to_mesh(false, depsgraph);
|
|
use_corner_normals = mesh_use_corner_normals(mesh, subdivision_type);
|
|
}
|
|
}
|
|
else {
|
|
/* TODO: what to do about non-mesh geometry instances? */
|
|
use_corner_normals = mesh_use_corner_normals(mesh, subdivision_type);
|
|
}
|
|
|
|
#if 0
|
|
if (subdivision_type != Mesh::SUBDIVISION_NONE) {
|
|
BL::Modifier subsurf_mod = object.modifiers[object.modifiers.length() - 1];
|
|
|
|
subsurf_mod.show_render(subsurf_mod_show_render);
|
|
subsurf_mod.show_viewport(subsurf_mod_show_viewport);
|
|
}
|
|
#endif
|
|
|
|
if (mesh) {
|
|
if (use_corner_normals) {
|
|
mesh.split_faces();
|
|
}
|
|
|
|
if (subdivision_type == Mesh::SUBDIVISION_NONE) {
|
|
mesh.calc_loop_triangles();
|
|
}
|
|
}
|
|
|
|
return mesh;
|
|
}
|
|
|
|
static inline void free_object_to_mesh(BL::BlendData & /*data*/,
|
|
BObjectInfo &b_ob_info,
|
|
BL::Mesh &mesh)
|
|
{
|
|
if (!b_ob_info.is_real_object_data()) {
|
|
return;
|
|
}
|
|
/* Free mesh if we didn't just use the existing one. */
|
|
BL::Object object = b_ob_info.real_object;
|
|
if (object.data().ptr.data != mesh.ptr.data) {
|
|
object.to_mesh_clear();
|
|
}
|
|
}
|
|
|
|
static inline void colorramp_to_array(BL::ColorRamp &ramp,
|
|
array<float3> &ramp_color,
|
|
array<float> &ramp_alpha,
|
|
int size)
|
|
{
|
|
const int full_size = size + 1;
|
|
ramp_color.resize(full_size);
|
|
ramp_alpha.resize(full_size);
|
|
|
|
for (int i = 0; i < full_size; i++) {
|
|
float color[4];
|
|
|
|
ramp.evaluate(float(i) / float(size), color);
|
|
ramp_color[i] = make_float3(color[0], color[1], color[2]);
|
|
ramp_alpha[i] = color[3];
|
|
}
|
|
}
|
|
|
|
static inline void curvemap_minmax_curve(/*const*/ BL::CurveMap &curve, float *min_x, float *max_x)
|
|
{
|
|
*min_x = min(*min_x, curve.points[0].location()[0]);
|
|
*max_x = max(*max_x, curve.points[curve.points.length() - 1].location()[0]);
|
|
}
|
|
|
|
static inline void curvemapping_minmax(/*const*/ BL::CurveMapping &cumap,
|
|
int num_curves,
|
|
float *min_x,
|
|
float *max_x)
|
|
{
|
|
// const int num_curves = cumap.curves.length(); /* Gives linking error so far. */
|
|
*min_x = FLT_MAX;
|
|
*max_x = -FLT_MAX;
|
|
for (int i = 0; i < num_curves; ++i) {
|
|
BL::CurveMap map(cumap.curves[i]);
|
|
curvemap_minmax_curve(map, min_x, max_x);
|
|
}
|
|
}
|
|
|
|
static inline void curvemapping_to_array(BL::CurveMapping &cumap, array<float> &data, int size)
|
|
{
|
|
cumap.update();
|
|
BL::CurveMap curve = cumap.curves[0];
|
|
const int full_size = size + 1;
|
|
data.resize(full_size);
|
|
for (int i = 0; i < full_size; i++) {
|
|
float t = float(i) / float(size);
|
|
data[i] = cumap.evaluate(curve, t);
|
|
}
|
|
}
|
|
|
|
static inline void curvemapping_float_to_array(BL::CurveMapping &cumap,
|
|
array<float> &data,
|
|
int size)
|
|
{
|
|
float min = 0.0f, max = 1.0f;
|
|
|
|
curvemapping_minmax(cumap, 1, &min, &max);
|
|
|
|
const float range = max - min;
|
|
|
|
cumap.update();
|
|
|
|
BL::CurveMap map = cumap.curves[0];
|
|
|
|
const int full_size = size + 1;
|
|
data.resize(full_size);
|
|
|
|
for (int i = 0; i < full_size; i++) {
|
|
float t = min + float(i) / float(size) * range;
|
|
data[i] = cumap.evaluate(map, t);
|
|
}
|
|
}
|
|
|
|
static inline void curvemapping_color_to_array(BL::CurveMapping &cumap,
|
|
array<float3> &data,
|
|
int size,
|
|
bool rgb_curve)
|
|
{
|
|
float min_x = 0.0f, max_x = 1.0f;
|
|
|
|
/* TODO(sergey): There is no easy way to automatically guess what is
|
|
* the range to be used here for the case when mapping is applied on
|
|
* top of another mapping (i.e. R curve applied on top of common
|
|
* one).
|
|
*
|
|
* Using largest possible range form all curves works correct for the
|
|
* cases like vector curves and should be good enough heuristic for
|
|
* the color curves as well.
|
|
*
|
|
* There might be some better estimations here tho.
|
|
*/
|
|
const int num_curves = rgb_curve ? 4 : 3;
|
|
curvemapping_minmax(cumap, num_curves, &min_x, &max_x);
|
|
|
|
const float range_x = max_x - min_x;
|
|
|
|
cumap.update();
|
|
|
|
BL::CurveMap mapR = cumap.curves[0];
|
|
BL::CurveMap mapG = cumap.curves[1];
|
|
BL::CurveMap mapB = cumap.curves[2];
|
|
|
|
const int full_size = size + 1;
|
|
data.resize(full_size);
|
|
|
|
if (rgb_curve) {
|
|
BL::CurveMap mapI = cumap.curves[3];
|
|
for (int i = 0; i < full_size; i++) {
|
|
const float t = min_x + float(i) / float(size) * range_x;
|
|
data[i] = make_float3(cumap.evaluate(mapR, cumap.evaluate(mapI, t)),
|
|
cumap.evaluate(mapG, cumap.evaluate(mapI, t)),
|
|
cumap.evaluate(mapB, cumap.evaluate(mapI, t)));
|
|
}
|
|
}
|
|
else {
|
|
for (int i = 0; i < full_size; i++) {
|
|
float t = min_x + float(i) / float(size) * range_x;
|
|
data[i] = make_float3(
|
|
cumap.evaluate(mapR, t), cumap.evaluate(mapG, t), cumap.evaluate(mapB, t));
|
|
}
|
|
}
|
|
}
|
|
|
|
static inline bool BKE_object_is_modified(BL::Object &self, BL::Scene &scene, bool preview)
|
|
{
|
|
return self.is_modified(scene, (preview) ? (1 << 0) : (1 << 1)) ? true : false;
|
|
}
|
|
|
|
static inline bool BKE_object_is_deform_modified(BObjectInfo &self, BL::Scene &scene, bool preview)
|
|
{
|
|
if (!self.is_real_object_data()) {
|
|
/* Comes from geometry nodes, can't use heuristic to guess if it's animated. */
|
|
return true;
|
|
}
|
|
|
|
/* Use heuristic to quickly check if object is potentially animated. */
|
|
return self.real_object.is_deform_modified(scene, (preview) ? (1 << 0) : (1 << 1)) ? true :
|
|
false;
|
|
}
|
|
|
|
static inline int render_resolution_x(BL::RenderSettings &b_render)
|
|
{
|
|
return b_render.resolution_x() * b_render.resolution_percentage() / 100;
|
|
}
|
|
|
|
static inline int render_resolution_y(BL::RenderSettings &b_render)
|
|
{
|
|
return b_render.resolution_y() * b_render.resolution_percentage() / 100;
|
|
}
|
|
|
|
static inline string image_user_file_path(BL::BlendData &data,
|
|
BL::ImageUser &iuser,
|
|
BL::Image &ima,
|
|
int cfra)
|
|
{
|
|
char filepath[1024];
|
|
iuser.tile(0);
|
|
BKE_image_user_frame_calc(ima.ptr.data, iuser.ptr.data, cfra);
|
|
BKE_image_user_file_path_ex(data.ptr.data, iuser.ptr.data, ima.ptr.data, filepath, false, true);
|
|
|
|
return string(filepath);
|
|
}
|
|
|
|
static inline int image_user_frame_number(BL::ImageUser &iuser, BL::Image &ima, int cfra)
|
|
{
|
|
BKE_image_user_frame_calc(ima.ptr.data, iuser.ptr.data, cfra);
|
|
return iuser.frame_current();
|
|
}
|
|
|
|
static inline unsigned char *image_get_pixels_for_frame(BL::Image &image, int frame, int tile)
|
|
{
|
|
return BKE_image_get_pixels_for_frame(image.ptr.data, frame, tile);
|
|
}
|
|
|
|
static inline float *image_get_float_pixels_for_frame(BL::Image &image, int frame, int tile)
|
|
{
|
|
return BKE_image_get_float_pixels_for_frame(image.ptr.data, frame, tile);
|
|
}
|
|
|
|
static inline bool image_is_builtin(BL::Image &ima, BL::RenderEngine &engine)
|
|
{
|
|
const BL::Image::source_enum image_source = ima.source();
|
|
if (image_source == BL::Image::source_TILED) {
|
|
/* If any tile is marked as generated, then treat the entire Image as built-in. */
|
|
for (BL::UDIMTile &tile : ima.tiles) {
|
|
if (tile.is_generated_tile()) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return ima.packed_file() || image_source == BL::Image::source_GENERATED ||
|
|
image_source == BL::Image::source_MOVIE ||
|
|
(engine.is_preview() && image_source != BL::Image::source_SEQUENCE);
|
|
}
|
|
|
|
static inline void render_add_metadata(BL::RenderResult &b_rr, string name, string value)
|
|
{
|
|
b_rr.stamp_data_add_field(name.c_str(), value.c_str());
|
|
}
|
|
|
|
/* Utilities */
|
|
|
|
static inline Transform get_transform(const BL::Array<float, 16> &array)
|
|
{
|
|
ProjectionTransform projection;
|
|
|
|
/* We assume both types to be just 16 floats, and transpose because blender
|
|
* use column major matrix order while we use row major. */
|
|
memcpy((void *)&projection, &array, sizeof(float) * 16);
|
|
projection = projection_transpose(projection);
|
|
|
|
/* Drop last row, matrix is assumed to be affine transform. */
|
|
return projection_to_transform(projection);
|
|
}
|
|
|
|
static inline float2 get_float2(const BL::Array<float, 2> &array)
|
|
{
|
|
return make_float2(array[0], array[1]);
|
|
}
|
|
|
|
static inline float3 get_float3(const BL::Array<float, 2> &array)
|
|
{
|
|
return make_float3(array[0], array[1], 0.0f);
|
|
}
|
|
|
|
static inline float3 get_float3(const BL::Array<float, 3> &array)
|
|
{
|
|
return make_float3(array[0], array[1], array[2]);
|
|
}
|
|
|
|
static inline float3 get_float3(const BL::Array<float, 4> &array)
|
|
{
|
|
return make_float3(array[0], array[1], array[2]);
|
|
}
|
|
|
|
static inline float4 get_float4(const BL::Array<float, 4> &array)
|
|
{
|
|
return make_float4(array[0], array[1], array[2], array[3]);
|
|
}
|
|
|
|
static inline int3 get_int3(const BL::Array<int, 3> &array)
|
|
{
|
|
return make_int3(array[0], array[1], array[2]);
|
|
}
|
|
|
|
static inline int4 get_int4(const BL::Array<int, 4> &array)
|
|
{
|
|
return make_int4(array[0], array[1], array[2], array[3]);
|
|
}
|
|
|
|
static inline float3 get_float3(PointerRNA &ptr, const char *name)
|
|
{
|
|
float3 f;
|
|
RNA_float_get_array(&ptr, name, &f.x);
|
|
return f;
|
|
}
|
|
|
|
static inline void set_float3(PointerRNA &ptr, const char *name, float3 value)
|
|
{
|
|
RNA_float_set_array(&ptr, name, &value.x);
|
|
}
|
|
|
|
static inline float4 get_float4(PointerRNA &ptr, const char *name)
|
|
{
|
|
float4 f;
|
|
RNA_float_get_array(&ptr, name, &f.x);
|
|
return f;
|
|
}
|
|
|
|
static inline void set_float4(PointerRNA &ptr, const char *name, float4 value)
|
|
{
|
|
RNA_float_set_array(&ptr, name, &value.x);
|
|
}
|
|
|
|
static inline bool get_boolean(PointerRNA &ptr, const char *name)
|
|
{
|
|
return RNA_boolean_get(&ptr, name) ? true : false;
|
|
}
|
|
|
|
static inline void set_boolean(PointerRNA &ptr, const char *name, bool value)
|
|
{
|
|
RNA_boolean_set(&ptr, name, (int)value);
|
|
}
|
|
|
|
static inline float get_float(PointerRNA &ptr, const char *name)
|
|
{
|
|
return RNA_float_get(&ptr, name);
|
|
}
|
|
|
|
static inline void set_float(PointerRNA &ptr, const char *name, float value)
|
|
{
|
|
RNA_float_set(&ptr, name, value);
|
|
}
|
|
|
|
static inline int get_int(PointerRNA &ptr, const char *name)
|
|
{
|
|
return RNA_int_get(&ptr, name);
|
|
}
|
|
|
|
static inline void set_int(PointerRNA &ptr, const char *name, int value)
|
|
{
|
|
RNA_int_set(&ptr, name, value);
|
|
}
|
|
|
|
/* Get a RNA enum value with sanity check: if the RNA value is above num_values
|
|
* the function will return a fallback default value.
|
|
*
|
|
* NOTE: This function assumes that RNA enum values are a continuous sequence
|
|
* from 0 to num_values-1. Be careful to use it with enums where some values are
|
|
* deprecated!
|
|
*/
|
|
static inline int get_enum(PointerRNA &ptr,
|
|
const char *name,
|
|
int num_values = -1,
|
|
int default_value = -1)
|
|
{
|
|
int value = RNA_enum_get(&ptr, name);
|
|
if (num_values != -1 && value >= num_values) {
|
|
assert(default_value != -1);
|
|
value = default_value;
|
|
}
|
|
return value;
|
|
}
|
|
|
|
static inline string get_enum_identifier(PointerRNA &ptr, const char *name)
|
|
{
|
|
PropertyRNA *prop = RNA_struct_find_property(&ptr, name);
|
|
const char *identifier = "";
|
|
int value = RNA_property_enum_get(&ptr, prop);
|
|
|
|
RNA_property_enum_identifier(NULL, &ptr, prop, value, &identifier);
|
|
|
|
return string(identifier);
|
|
}
|
|
|
|
static inline void set_enum(PointerRNA &ptr, const char *name, int value)
|
|
{
|
|
RNA_enum_set(&ptr, name, value);
|
|
}
|
|
|
|
static inline void set_enum(PointerRNA &ptr, const char *name, const string &identifier)
|
|
{
|
|
RNA_enum_set_identifier(NULL, &ptr, name, identifier.c_str());
|
|
}
|
|
|
|
static inline string get_string(PointerRNA &ptr, const char *name)
|
|
{
|
|
char cstrbuf[1024];
|
|
char *cstr = RNA_string_get_alloc(&ptr, name, cstrbuf, sizeof(cstrbuf), NULL);
|
|
string str(cstr);
|
|
if (cstr != cstrbuf) {
|
|
MEM_freeN(cstr);
|
|
}
|
|
|
|
return str;
|
|
}
|
|
|
|
static inline void set_string(PointerRNA &ptr, const char *name, const string &value)
|
|
{
|
|
RNA_string_set(&ptr, name, value.c_str());
|
|
}
|
|
|
|
/* Relative Paths */
|
|
|
|
static inline string blender_absolute_path(BL::BlendData &b_data, BL::ID &b_id, const string &path)
|
|
{
|
|
if (path.size() >= 2 && path[0] == '/' && path[1] == '/') {
|
|
string dirname;
|
|
|
|
if (b_id.library()) {
|
|
BL::ID b_library_id(b_id.library());
|
|
dirname = blender_absolute_path(b_data, b_library_id, b_id.library().filepath());
|
|
}
|
|
else {
|
|
dirname = b_data.filepath();
|
|
}
|
|
|
|
return path_join(path_dirname(dirname), path.substr(2));
|
|
}
|
|
|
|
return path;
|
|
}
|
|
|
|
static inline string get_text_datablock_content(const PointerRNA &ptr)
|
|
{
|
|
if (ptr.data == NULL) {
|
|
return "";
|
|
}
|
|
|
|
string content;
|
|
BL::Text::lines_iterator iter;
|
|
for (iter.begin(ptr); iter; ++iter) {
|
|
content += iter->body() + "\n";
|
|
}
|
|
|
|
return content;
|
|
}
|
|
|
|
/* Texture Space */
|
|
|
|
static inline void mesh_texture_space(const ::Mesh &b_mesh, float3 &loc, float3 &size)
|
|
{
|
|
float texspace_location[3], texspace_size[3];
|
|
BKE_mesh_texspace_get(const_cast<::Mesh *>(&b_mesh), texspace_location, texspace_size);
|
|
|
|
loc = make_float3(texspace_location[0], texspace_location[1], texspace_location[2]);
|
|
size = make_float3(texspace_size[0], texspace_size[1], texspace_size[2]);
|
|
|
|
if (size.x != 0.0f) {
|
|
size.x = 0.5f / size.x;
|
|
}
|
|
if (size.y != 0.0f) {
|
|
size.y = 0.5f / size.y;
|
|
}
|
|
if (size.z != 0.0f) {
|
|
size.z = 0.5f / size.z;
|
|
}
|
|
|
|
loc = loc * size - make_float3(0.5f, 0.5f, 0.5f);
|
|
}
|
|
|
|
/* Object motion steps, returns 0 if no motion blur needed. */
|
|
static inline uint object_motion_steps(BL::Object &b_parent,
|
|
BL::Object &b_ob,
|
|
const int max_steps = INT_MAX)
|
|
{
|
|
/* Get motion enabled and steps from object itself. */
|
|
PointerRNA cobject = RNA_pointer_get(&b_ob.ptr, "cycles");
|
|
bool use_motion = get_boolean(cobject, "use_motion_blur");
|
|
if (!use_motion) {
|
|
return 0;
|
|
}
|
|
|
|
int steps = max(1, get_int(cobject, "motion_steps"));
|
|
|
|
/* Also check parent object, so motion blur and steps can be
|
|
* controlled by dupli-group duplicator for linked groups. */
|
|
if (b_parent.ptr.data != b_ob.ptr.data) {
|
|
PointerRNA parent_cobject = RNA_pointer_get(&b_parent.ptr, "cycles");
|
|
use_motion &= get_boolean(parent_cobject, "use_motion_blur");
|
|
|
|
if (!use_motion) {
|
|
return 0;
|
|
}
|
|
|
|
steps = max(steps, get_int(parent_cobject, "motion_steps"));
|
|
}
|
|
|
|
/* Use uneven number of steps so we get one keyframe at the current frame,
|
|
* and use 2^(steps - 1) so objects with more/fewer steps still have samples
|
|
* at the same times, to avoid sampling at many different times. */
|
|
return min((2 << (steps - 1)) + 1, max_steps);
|
|
}
|
|
|
|
/* object uses deformation motion blur */
|
|
static inline bool object_use_deform_motion(BL::Object &b_parent, BL::Object &b_ob)
|
|
{
|
|
PointerRNA cobject = RNA_pointer_get(&b_ob.ptr, "cycles");
|
|
bool use_deform_motion = get_boolean(cobject, "use_deform_motion");
|
|
/* If motion blur is enabled for the object we also check
|
|
* whether it's enabled for the parent object as well.
|
|
*
|
|
* This way we can control motion blur from the dupli-group
|
|
* duplicator much easier. */
|
|
if (use_deform_motion && b_parent.ptr.data != b_ob.ptr.data) {
|
|
PointerRNA parent_cobject = RNA_pointer_get(&b_parent.ptr, "cycles");
|
|
use_deform_motion &= get_boolean(parent_cobject, "use_deform_motion");
|
|
}
|
|
return use_deform_motion;
|
|
}
|
|
|
|
static inline BL::FluidDomainSettings object_fluid_gas_domain_find(BL::Object &b_ob)
|
|
{
|
|
for (BL::Modifier &b_mod : b_ob.modifiers) {
|
|
if (b_mod.is_a(&RNA_FluidModifier)) {
|
|
BL::FluidModifier b_mmd(b_mod);
|
|
|
|
if (b_mmd.fluid_type() == BL::FluidModifier::fluid_type_DOMAIN &&
|
|
b_mmd.domain_settings().domain_type() == BL::FluidDomainSettings::domain_type_GAS)
|
|
{
|
|
return b_mmd.domain_settings();
|
|
}
|
|
}
|
|
}
|
|
|
|
return BL::FluidDomainSettings(PointerRNA_NULL);
|
|
}
|
|
|
|
static inline BL::MeshSequenceCacheModifier object_mesh_cache_find(BL::Object &b_ob,
|
|
bool *has_subdivision_modifier)
|
|
{
|
|
for (int i = b_ob.modifiers.length() - 1; i >= 0; --i) {
|
|
BL::Modifier b_mod = b_ob.modifiers[i];
|
|
|
|
if (b_mod.type() == BL::Modifier::type_MESH_SEQUENCE_CACHE) {
|
|
BL::MeshSequenceCacheModifier mesh_cache = BL::MeshSequenceCacheModifier(b_mod);
|
|
return mesh_cache;
|
|
}
|
|
|
|
/* Skip possible particles system modifiers as they do not modify the geometry. */
|
|
if (b_mod.type() == BL::Modifier::type_PARTICLE_SYSTEM) {
|
|
continue;
|
|
}
|
|
|
|
if (b_mod.type() == BL::Modifier::type_SUBSURF) {
|
|
if (has_subdivision_modifier) {
|
|
*has_subdivision_modifier = true;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
return BL::MeshSequenceCacheModifier(PointerRNA_NULL);
|
|
}
|
|
|
|
static inline Mesh::SubdivisionType object_subdivision_type(BL::Object &b_ob,
|
|
bool preview,
|
|
bool experimental)
|
|
{
|
|
PointerRNA cobj = RNA_pointer_get(&b_ob.ptr, "cycles");
|
|
|
|
if (cobj.data && !b_ob.modifiers.empty() && experimental) {
|
|
BL::Modifier mod = b_ob.modifiers[b_ob.modifiers.length() - 1];
|
|
bool enabled = preview ? mod.show_viewport() : mod.show_render();
|
|
|
|
if (enabled && mod.type() == BL::Modifier::type_SUBSURF &&
|
|
RNA_boolean_get(&cobj, "use_adaptive_subdivision"))
|
|
{
|
|
BL::SubsurfModifier subsurf(mod);
|
|
|
|
if (subsurf.subdivision_type() == BL::SubsurfModifier::subdivision_type_CATMULL_CLARK) {
|
|
return Mesh::SUBDIVISION_CATMULL_CLARK;
|
|
}
|
|
else {
|
|
return Mesh::SUBDIVISION_LINEAR;
|
|
}
|
|
}
|
|
}
|
|
|
|
return Mesh::SUBDIVISION_NONE;
|
|
}
|
|
|
|
static inline uint object_ray_visibility(BL::Object &b_ob)
|
|
{
|
|
uint flag = 0;
|
|
|
|
flag |= b_ob.visible_camera() ? PATH_RAY_CAMERA : 0;
|
|
flag |= b_ob.visible_diffuse() ? PATH_RAY_DIFFUSE : 0;
|
|
flag |= b_ob.visible_glossy() ? PATH_RAY_GLOSSY : 0;
|
|
flag |= b_ob.visible_transmission() ? PATH_RAY_TRANSMIT : 0;
|
|
flag |= b_ob.visible_shadow() ? PATH_RAY_SHADOW : 0;
|
|
flag |= b_ob.visible_volume_scatter() ? PATH_RAY_VOLUME_SCATTER : 0;
|
|
|
|
return flag;
|
|
}
|
|
|
|
/* Check whether some of "built-in" motion-related attributes are needed to be exported (includes
|
|
* things like velocity from cache modifier, fluid simulation).
|
|
*
|
|
* NOTE: This code is run prior to object motion blur initialization. so can not access properties
|
|
* set by `sync_object_motion_init()`. */
|
|
static inline bool object_need_motion_attribute(BObjectInfo &b_ob_info, Scene *scene)
|
|
{
|
|
const Scene::MotionType need_motion = scene->need_motion();
|
|
if (need_motion == Scene::MOTION_NONE) {
|
|
/* Simple case: neither motion pass nor motion blur is needed, no need in the motion related
|
|
* attributes. */
|
|
return false;
|
|
}
|
|
|
|
if (need_motion == Scene::MOTION_BLUR) {
|
|
/* A bit tricky and implicit case:
|
|
* - Motion blur is enabled in the scene, which implies specific number of time steps for
|
|
* objects.
|
|
* - If the object has motion blur disabled on it, it will have 0 time steps.
|
|
* - Motion attribute expects non-zero time steps.
|
|
*
|
|
* Avoid adding motion attributes if the motion blur will enforce 0 motion steps. */
|
|
PointerRNA cobject = RNA_pointer_get(&b_ob_info.real_object.ptr, "cycles");
|
|
const bool use_motion = get_boolean(cobject, "use_motion_blur");
|
|
if (!use_motion) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/* Motion pass which implies 3 motion steps, or motion blur which is not disabled on object
|
|
* level. */
|
|
return true;
|
|
}
|
|
|
|
class EdgeMap {
|
|
public:
|
|
EdgeMap() {}
|
|
|
|
void clear()
|
|
{
|
|
edges_.clear();
|
|
}
|
|
|
|
void insert(int v0, int v1)
|
|
{
|
|
get_sorted_verts(v0, v1);
|
|
edges_.insert(std::pair<int, int>(v0, v1));
|
|
}
|
|
|
|
bool exists(int v0, int v1)
|
|
{
|
|
get_sorted_verts(v0, v1);
|
|
return edges_.find(std::pair<int, int>(v0, v1)) != edges_.end();
|
|
}
|
|
|
|
protected:
|
|
void get_sorted_verts(int &v0, int &v1)
|
|
{
|
|
if (v0 > v1) {
|
|
swap(v0, v1);
|
|
}
|
|
}
|
|
|
|
set<std::pair<int, int>> edges_;
|
|
};
|
|
|
|
CCL_NAMESPACE_END
|
|
|
|
#endif /* __BLENDER_UTIL_H__ */
|