diff options
author | Jacques Lucke <jacques@blender.org> | 2021-05-07 11:36:27 +0300 |
---|---|---|
committer | Jacques Lucke <jacques@blender.org> | 2021-05-07 11:36:27 +0300 |
commit | 619b015a46786a4f4057b0dd4d20d27b61d13c0f (patch) | |
tree | bc6c5701becc47ae77e40e4f6c77365c06f2458c | |
parent | ab200c6eddc6494b5d95c98966969b2001d6db89 (diff) | |
parent | 42e350b9a5fc32940b05fce4115ed76898862604 (diff) |
Merge branch 'master' into profiler-editor
170 files changed, 6253 insertions, 1967 deletions
diff --git a/intern/cycles/app/CMakeLists.txt b/intern/cycles/app/CMakeLists.txt index 67b852013f3..7a1e5d62dd2 100644 --- a/intern/cycles/app/CMakeLists.txt +++ b/intern/cycles/app/CMakeLists.txt @@ -71,6 +71,16 @@ if(WITH_CYCLES_STANDALONE) target_link_libraries(cycles ${LIBRARIES}) cycles_target_link_libraries(cycles) + if(APPLE) + if(WITH_OPENCOLORIO) + set_property(TARGET cycles APPEND_STRING PROPERTY LINK_FLAGS " -framework IOKit") + endif() + if(WITH_OPENIMAGEDENOISE AND "${CMAKE_OSX_ARCHITECTURES}" STREQUAL "arm64") + # OpenImageDenoise uses BNNS from the Accelerate framework. + set_property(TARGET cycles APPEND_STRING PROPERTY LINK_FLAGS " -framework Accelerate") + endif() + endif() + if(UNIX AND NOT APPLE) set_target_properties(cycles PROPERTIES INSTALL_RPATH $ORIGIN/lib) endif() diff --git a/intern/cycles/blender/blender_object.cpp b/intern/cycles/blender/blender_object.cpp index 54128cf82fc..19f49acddd7 100644 --- a/intern/cycles/blender/blender_object.cpp +++ b/intern/cycles/blender/blender_object.cpp @@ -560,10 +560,12 @@ void BlenderSync::sync_objects(BL::Depsgraph &b_depsgraph, if (!cancel && !motion) { sync_background_light(b_v3d, use_portal); - /* handle removed data and modified pointers */ + /* Handle removed data and modified pointers, as this may free memory, delete Nodes in the + * right order to ensure that dependent data is freed after their users. Objects should be + * freed before particle systems and geometries. */ light_map.post_sync(); - geometry_map.post_sync(); object_map.post_sync(); + geometry_map.post_sync(); particle_system_map.post_sync(); } diff --git a/intern/cycles/blender/blender_python.cpp b/intern/cycles/blender/blender_python.cpp index 0daad310543..785ca6b4e01 100644 --- a/intern/cycles/blender/blender_python.cpp +++ b/intern/cycles/blender/blender_python.cpp @@ -35,6 +35,7 @@ #include "util/util_path.h" #include "util/util_string.h" #include "util/util_task.h" +#include "util/util_tbb.h" #include "util/util_types.h" #ifdef WITH_OSL @@ -288,9 +289,11 @@ static PyObject *render_func(PyObject * /*self*/, PyObject *args) RNA_pointer_create(NULL, &RNA_Depsgraph, (ID *)PyLong_AsVoidPtr(pydepsgraph), &depsgraphptr); BL::Depsgraph b_depsgraph(depsgraphptr); + /* Allow Blender to execute other Python scripts, and isolate TBB tasks so we + * don't get deadlocks with Blender threads accessing shared data like images. */ python_thread_state_save(&session->python_thread_state); - session->render(b_depsgraph); + tbb::this_task_arena::isolate([&] { session->render(b_depsgraph); }); python_thread_state_restore(&session->python_thread_state); @@ -327,7 +330,8 @@ static PyObject *bake_func(PyObject * /*self*/, PyObject *args) python_thread_state_save(&session->python_thread_state); - session->bake(b_depsgraph, b_object, pass_type, pass_filter, width, height); + tbb::this_task_arena::isolate( + [&] { session->bake(b_depsgraph, b_object, pass_type, pass_filter, width, height); }); python_thread_state_restore(&session->python_thread_state); @@ -373,7 +377,7 @@ static PyObject *reset_func(PyObject * /*self*/, PyObject *args) python_thread_state_save(&session->python_thread_state); - session->reset_session(b_data, b_depsgraph); + tbb::this_task_arena::isolate([&] { session->reset_session(b_data, b_depsgraph); }); python_thread_state_restore(&session->python_thread_state); @@ -395,7 +399,7 @@ static PyObject *sync_func(PyObject * /*self*/, PyObject *args) python_thread_state_save(&session->python_thread_state); - session->synchronize(b_depsgraph); + tbb::this_task_arena::isolate([&] { session->synchronize(b_depsgraph); }); python_thread_state_restore(&session->python_thread_state); diff --git a/intern/cycles/blender/blender_session.cpp b/intern/cycles/blender/blender_session.cpp index ab081ada3dc..89854f6a0e5 100644 --- a/intern/cycles/blender/blender_session.cpp +++ b/intern/cycles/blender/blender_session.cpp @@ -237,6 +237,9 @@ void BlenderSession::reset_session(BL::BlendData &b_data, BL::Depsgraph &b_depsg sync->sync_recalc(b_depsgraph, b_v3d); } + BL::Object b_camera_override(b_engine.camera_override()); + sync->sync_camera(b_render, b_camera_override, width, height, ""); + BL::SpaceView3D b_null_space_view3d(PointerRNA_NULL); BL::RegionView3D b_null_region_view3d(PointerRNA_NULL); BufferParams buffer_params = BlenderSync::get_buffer_params(b_render, diff --git a/intern/cycles/graph/node.cpp b/intern/cycles/graph/node.cpp index c926f6ab8ef..57f25283f85 100644 --- a/intern/cycles/graph/node.cpp +++ b/intern/cycles/graph/node.cpp @@ -367,9 +367,17 @@ void Node::copy_value(const SocketType &socket, const Node &other, const SocketT case SocketType::TRANSFORM_ARRAY: copy_array<Transform>(this, socket, &other, other_socket); break; - case SocketType::NODE_ARRAY: + case SocketType::NODE_ARRAY: { copy_array<void *>(this, socket, &other, other_socket); + + array<Node *> &node_array = get_socket_value<array<Node *>>(this, socket); + + for (Node *node : node_array) { + node->reference(); + } + break; + } default: assert(0); break; @@ -379,6 +387,14 @@ void Node::copy_value(const SocketType &socket, const Node &other, const SocketT const void *src = ((char *)&other) + other_socket.struct_offset; void *dst = ((char *)this) + socket.struct_offset; memcpy(dst, src, socket.size()); + + if (socket.type == SocketType::NODE) { + Node *node = get_socket_value<Node *>(this, socket); + + if (node) { + node->reference(); + } + } } } @@ -773,6 +789,26 @@ void Node::set_owner(const NodeOwner *owner_) owner = owner_; } +void Node::dereference_all_used_nodes() +{ + foreach (const SocketType &socket, type->inputs) { + if (socket.type == SocketType::NODE) { + Node *node = get_socket_value<Node *>(this, socket); + + if (node) { + node->dereference(); + } + } + else if (socket.type == SocketType::NODE_ARRAY) { + const array<Node *> &nodes = get_socket_value<array<Node *>>(this, socket); + + for (Node *node : nodes) { + node->dereference(); + } + } + } +} + bool Node::socket_is_modified(const SocketType &input) const { return (socket_modified & input.modified_flag_bit) != 0; @@ -803,6 +839,25 @@ template<typename T> void Node::set_if_different(const SocketType &input, T valu socket_modified |= input.modified_flag_bit; } +void Node::set_if_different(const SocketType &input, Node *value) +{ + if (get_socket_value<Node *>(this, input) == value) { + return; + } + + Node *old_node = get_socket_value<Node *>(this, input); + if (old_node) { + old_node->dereference(); + } + + if (value) { + value->reference(); + } + + get_socket_value<Node *>(this, input) = value; + socket_modified |= input.modified_flag_bit; +} + template<typename T> void Node::set_if_different(const SocketType &input, array<T> &value) { if (!socket_is_modified(input)) { @@ -815,6 +870,27 @@ template<typename T> void Node::set_if_different(const SocketType &input, array< socket_modified |= input.modified_flag_bit; } +void Node::set_if_different(const SocketType &input, array<Node *> &value) +{ + if (!socket_is_modified(input)) { + if (get_socket_value<array<Node *>>(this, input) == value) { + return; + } + } + + array<Node *> &old_nodes = get_socket_value<array<Node *>>(this, input); + for (Node *old_node : old_nodes) { + old_node->dereference(); + } + + for (Node *new_node : value) { + new_node->reference(); + } + + get_socket_value<array<Node *>>(this, input).steal_data(value); + socket_modified |= input.modified_flag_bit; +} + void Node::print_modified_sockets() const { printf("Node : %s\n", name.c_str()); diff --git a/intern/cycles/graph/node.h b/intern/cycles/graph/node.h index 2fc9a1e0281..aa365baeccd 100644 --- a/intern/cycles/graph/node.h +++ b/intern/cycles/graph/node.h @@ -177,8 +177,32 @@ struct Node { const NodeOwner *get_owner() const; void set_owner(const NodeOwner *owner_); + int reference_count() const + { + return ref_count; + } + + void reference() + { + ref_count += 1; + } + + void dereference() + { + ref_count -= 1; + } + + /* Set the reference count to zero. This should only be called when we know for sure that the + * Node is not used by anyone else. For now, this is only the case when "deleting" shaders, as + * they are never actually deleted. */ + void clear_reference_count() + { + ref_count = 0; + } + protected: const NodeOwner *owner; + int ref_count{0}; template<typename T> static T &get_socket_value(const Node *node, const SocketType &socket) { @@ -189,7 +213,19 @@ struct Node { template<typename T> void set_if_different(const SocketType &input, T value); + /* Explicit overload for Node sockets so we can handle reference counting. The old Node is + * dereferenced, and the new one is referenced. */ + void set_if_different(const SocketType &input, Node *value); + template<typename T> void set_if_different(const SocketType &input, array<T> &value); + + /* Explicit overload for Node sockets so we can handle reference counting. The old Nodes are + * dereferenced, and the new ones are referenced. */ + void set_if_different(const SocketType &input, array<Node *> &value); + + /* Call this function in derived classes' destructors to ensure that used Nodes are dereferenced + * properly. */ + void dereference_all_used_nodes(); }; CCL_NAMESPACE_END diff --git a/intern/cycles/kernel/kernel_subsurface.h b/intern/cycles/kernel/kernel_subsurface.h index 0a56f867158..b70df747a73 100644 --- a/intern/cycles/kernel/kernel_subsurface.h +++ b/intern/cycles/kernel/kernel_subsurface.h @@ -606,10 +606,10 @@ ccl_device_noinline t = ray->t; } else if (bounce == 0) { - /* Restore original position if nothing was hit after the first bounce. - * Otherwise if the ray_offset() to avoid self-intersection is relatively - * large compared to the scattering radius, we go never backup high enough - * to exit the surface. */ + /* Restore original position if nothing was hit after the first bounce, + * without the ray_offset() that was added to avoid self-intersection. + * Otherwise if that offset is relatively large compared to the scattering + * radius, we never go back up high enough to exit the surface. */ ray->P = sd->P; } diff --git a/intern/cycles/render/CMakeLists.txt b/intern/cycles/render/CMakeLists.txt index c67919b375a..feead27c5ca 100644 --- a/intern/cycles/render/CMakeLists.txt +++ b/intern/cycles/render/CMakeLists.txt @@ -24,6 +24,7 @@ set(INC_SYS set(SRC alembic.cpp + alembic_read.cpp attribute.cpp background.cpp bake.cpp @@ -67,6 +68,7 @@ set(SRC set(SRC_HEADERS alembic.h + alembic_read.h attribute.h bake.h background.h diff --git a/intern/cycles/render/alembic.cpp b/intern/cycles/render/alembic.cpp index c3a7b20f512..5842f4c313d 100644 --- a/intern/cycles/render/alembic.cpp +++ b/intern/cycles/render/alembic.cpp @@ -16,6 +16,7 @@ #include "render/alembic.h" +#include "render/alembic_read.h" #include "render/camera.h" #include "render/curves.h" #include "render/mesh.h" @@ -34,7 +35,48 @@ using namespace Alembic::AbcGeom; CCL_NAMESPACE_BEGIN -/* TODO(@kevindietrich): motion blur support */ +/* TODO(kevindietrich): motion blur support. */ + +template<typename SchemaType> +static vector<FaceSetShaderIndexPair> parse_face_sets_for_shader_assignment( + SchemaType &schema, const array<Node *> &used_shaders) +{ + vector<FaceSetShaderIndexPair> result; + + std::vector<std::string> face_set_names; + schema.getFaceSetNames(face_set_names); + + if (face_set_names.empty()) { + return result; + } + + for (const std::string &face_set_name : face_set_names) { + int shader_index = 0; + + for (Node *node : used_shaders) { + if (node->name == face_set_name) { + break; + } + + ++shader_index; + } + + if (shader_index >= used_shaders.size()) { + /* use the first shader instead if none was found */ + shader_index = 0; + } + + const Alembic::AbcGeom::IFaceSet face_set = schema.getFaceSet(face_set_name); + + if (!face_set.valid()) { + continue; + } + + result.push_back({face_set, shader_index}); + } + + return result; +} void CachedData::clear() { @@ -54,7 +96,7 @@ void CachedData::clear() subd_start_corner.clear(); transforms.clear(); triangles.clear(); - triangles_loops.clear(); + uv_loops.clear(); vertices.clear(); for (CachedAttribute &attr : attributes) { @@ -101,7 +143,7 @@ bool CachedData::is_constant() const CHECK_IF_CONSTANT(subd_start_corner) CHECK_IF_CONSTANT(transforms) CHECK_IF_CONSTANT(triangles) - CHECK_IF_CONSTANT(triangles_loops) + CHECK_IF_CONSTANT(uv_loops) CHECK_IF_CONSTANT(vertices) for (const CachedAttribute &attr : attributes) { @@ -140,7 +182,7 @@ void CachedData::invalidate_last_loaded_time(bool attributes_only) subd_start_corner.invalidate_last_loaded_time(); transforms.invalidate_last_loaded_time(); triangles.invalidate_last_loaded_time(); - triangles_loops.invalidate_last_loaded_time(); + uv_loops.invalidate_last_loaded_time(); vertices.invalidate_last_loaded_time(); } @@ -161,7 +203,7 @@ void CachedData::set_time_sampling(TimeSampling time_sampling) subd_start_corner.set_time_sampling(time_sampling); transforms.set_time_sampling(time_sampling); triangles.set_time_sampling(time_sampling); - triangles_loops.set_time_sampling(time_sampling); + uv_loops.set_time_sampling(time_sampling); vertices.set_time_sampling(time_sampling); for (CachedAttribute &attr : attributes) { @@ -169,36 +211,6 @@ void CachedData::set_time_sampling(TimeSampling time_sampling) } } -/* get the sample times to load data for the given the start and end frame of the procedural */ -static set<chrono_t> get_relevant_sample_times(AlembicProcedural *proc, - const TimeSampling &time_sampling, - size_t num_samples) -{ - set<chrono_t> result; - - if (num_samples < 2) { - result.insert(0.0); - return result; - } - - double start_frame = (double)(proc->get_start_frame() / proc->get_frame_rate()); - double end_frame = (double)((proc->get_end_frame() + 1) / proc->get_frame_rate()); - - size_t start_index = time_sampling.getFloorIndex(start_frame, num_samples).first; - size_t end_index = time_sampling.getCeilIndex(end_frame, num_samples).first; - - for (size_t i = start_index; i < end_index; ++i) { - result.insert(time_sampling.getSampleTime(i)); - } - - return result; -} - -static float3 make_float3_from_yup(const V3f &v) -{ - return make_float3(v.x, -v.z, v.y); -} - static M44d convert_yup_zup(const M44d &mtx, float scale_mult) { V3d scale, shear, rotation, translation; @@ -366,249 +378,6 @@ static Transform make_transform(const M44d &a, float scale) return trans; } -static void add_uvs(AlembicProcedural *proc, - const IV2fGeomParam &uvs, - CachedData &cached_data, - Progress &progress) -{ - if (uvs.getScope() != kFacevaryingScope) { - return; - } - - const TimeSamplingPtr time_sampling_ptr = uvs.getTimeSampling(); - - TimeSampling time_sampling; - if (time_sampling_ptr) { - time_sampling = *time_sampling_ptr; - } - - std::string name = Alembic::Abc::GetSourceName(uvs.getMetaData()); - - /* According to the convention, primary UVs should have had their name - * set using Alembic::Abc::SetSourceName, but you can't expect everyone - * to follow it! :) */ - if (name.empty()) { - name = uvs.getName(); - } - - CachedData::CachedAttribute &attr = cached_data.add_attribute(ustring(name), time_sampling); - attr.std = ATTR_STD_UV; - - ccl::set<chrono_t> times = get_relevant_sample_times(proc, time_sampling, uvs.getNumSamples()); - - /* Keys used to determine if the UVs do actually change over time. */ - ArraySample::Key previous_indices_key; - ArraySample::Key previous_values_key; - - foreach (chrono_t time, times) { - if (progress.get_cancel()) { - return; - } - - const ISampleSelector iss = ISampleSelector(time); - const IV2fGeomParam::Sample uvsample = uvs.getIndexedValue(iss); - - if (!uvsample.valid()) { - continue; - } - - const array<int3> *triangles = - cached_data.triangles.data_for_time_no_check(time).get_data_or_null(); - const array<int3> *triangles_loops = - cached_data.triangles_loops.data_for_time_no_check(time).get_data_or_null(); - - if (!triangles || !triangles_loops) { - continue; - } - - array<char> data; - data.resize(triangles->size() * 3 * sizeof(float2)); - - float2 *data_float2 = reinterpret_cast<float2 *>(data.data()); - - const ArraySample::Key indices_key = uvsample.getIndices()->getKey(); - const ArraySample::Key values_key = uvsample.getVals()->getKey(); - - if (indices_key == previous_indices_key && values_key == previous_values_key) { - attr.data.reuse_data_for_last_time(time); - } - else { - const unsigned int *indices = uvsample.getIndices()->get(); - const V2f *values = uvsample.getVals()->get(); - - for (const int3 &loop : *triangles_loops) { - unsigned int v0 = indices[loop.x]; - unsigned int v1 = indices[loop.y]; - unsigned int v2 = indices[loop.z]; - - data_float2[0] = make_float2(values[v0][0], values[v0][1]); - data_float2[1] = make_float2(values[v1][0], values[v1][1]); - data_float2[2] = make_float2(values[v2][0], values[v2][1]); - data_float2 += 3; - } - - attr.data.add_data(data, time); - } - - previous_indices_key = indices_key; - previous_values_key = values_key; - } -} - -static void add_normals(const Int32ArraySamplePtr face_indices, - const IN3fGeomParam &normals, - double time, - CachedData &cached_data) -{ - switch (normals.getScope()) { - case kFacevaryingScope: { - const ISampleSelector iss = ISampleSelector(time); - const IN3fGeomParam::Sample sample = normals.getExpandedValue(iss); - - if (!sample.valid()) { - return; - } - - CachedData::CachedAttribute &attr = cached_data.add_attribute(ustring(normals.getName()), - *normals.getTimeSampling()); - attr.std = ATTR_STD_VERTEX_NORMAL; - - const array<float3> *vertices = - cached_data.vertices.data_for_time_no_check(time).get_data_or_null(); - - if (!vertices) { - return; - } - - array<char> data; - data.resize(vertices->size() * sizeof(float3)); - - float3 *data_float3 = reinterpret_cast<float3 *>(data.data()); - - const int *face_indices_array = face_indices->get(); - const N3fArraySamplePtr values = sample.getVals(); - - for (size_t i = 0; i < face_indices->size(); ++i) { - int point_index = face_indices_array[i]; - data_float3[point_index] = make_float3_from_yup(values->get()[i]); - } - - attr.data.add_data(data, time); - break; - } - case kVaryingScope: - case kVertexScope: { - const ISampleSelector iss = ISampleSelector(time); - const IN3fGeomParam::Sample sample = normals.getExpandedValue(iss); - - if (!sample.valid()) { - return; - } - - CachedData::CachedAttribute &attr = cached_data.add_attribute(ustring(normals.getName()), - *normals.getTimeSampling()); - attr.std = ATTR_STD_VERTEX_NORMAL; - - const array<float3> *vertices = - cached_data.vertices.data_for_time_no_check(time).get_data_or_null(); - - if (!vertices) { - return; - } - - array<char> data; - data.resize(vertices->size() * sizeof(float3)); - - float3 *data_float3 = reinterpret_cast<float3 *>(data.data()); - - const Imath::V3f *values = sample.getVals()->get(); - - for (size_t i = 0; i < vertices->size(); ++i) { - data_float3[i] = make_float3_from_yup(values[i]); - } - - attr.data.add_data(data, time); - - break; - } - default: { - break; - } - } -} - -static void add_positions(const P3fArraySamplePtr positions, double time, CachedData &cached_data) -{ - if (!positions) { - return; - } - - array<float3> vertices; - vertices.reserve(positions->size()); - - for (size_t i = 0; i < positions->size(); i++) { - V3f f = positions->get()[i]; - vertices.push_back_reserved(make_float3_from_yup(f)); - } - - cached_data.vertices.add_data(vertices, time); -} - -static void add_triangles(const Int32ArraySamplePtr face_counts, - const Int32ArraySamplePtr face_indices, - double time, - CachedData &cached_data, - const array<int> &polygon_to_shader) -{ - if (!face_counts || !face_indices) { - return; - } - - const size_t num_faces = face_counts->size(); - const int *face_counts_array = face_counts->get(); - const int *face_indices_array = face_indices->get(); - - size_t num_triangles = 0; - for (size_t i = 0; i < face_counts->size(); i++) { - num_triangles += face_counts_array[i] - 2; - } - - array<int> shader; - array<int3> triangles; - array<int3> triangles_loops; - shader.reserve(num_triangles); - triangles.reserve(num_triangles); - triangles_loops.reserve(num_triangles); - int index_offset = 0; - - for (size_t i = 0; i < num_faces; i++) { - int current_shader = 0; - - if (!polygon_to_shader.empty()) { - current_shader = polygon_to_shader[i]; - } - - for (int j = 0; j < face_counts_array[i] - 2; j++) { - int v0 = face_indices_array[index_offset]; - int v1 = face_indices_array[index_offset + j + 1]; - int v2 = face_indices_array[index_offset + j + 2]; - - shader.push_back_reserved(current_shader); - - /* Alembic orders the loops following the RenderMan convention, so need to go in reverse. */ - triangles.push_back_reserved(make_int3(v2, v1, v0)); - triangles_loops.push_back_reserved( - make_int3(index_offset + j + 2, index_offset + j + 1, index_offset)); - } - - index_offset += face_counts_array[i]; - } - - cached_data.triangles.add_data(triangles, time); - cached_data.triangles_loops.add_data(triangles_loops, time); - cached_data.shader.add_data(shader, time); -} - NODE_DEFINE(AlembicObject) { NodeType *type = NodeType::add("alembic_object", create); @@ -648,398 +417,141 @@ bool AlembicObject::has_data_loaded() const return data_loaded; } -void AlembicObject::update_shader_attributes(const ICompoundProperty &arb_geom_params, - Progress &progress) -{ - AttributeRequestSet requested_attributes = get_requested_attributes(); - - foreach (const AttributeRequest &attr, requested_attributes.requests) { - if (progress.get_cancel()) { - return; - } - - bool attr_exists = false; - foreach (CachedData::CachedAttribute &cached_attr, cached_data.attributes) { - if (cached_attr.name == attr.name) { - attr_exists = true; - break; - } - } - - if (attr_exists) { - continue; - } - - read_attribute(arb_geom_params, attr.name, progress); - } - - cached_data.invalidate_last_loaded_time(true); - need_shader_update = false; -} - -template<typename SchemaType> -void AlembicObject::read_face_sets(SchemaType &schema, - array<int> &polygon_to_shader, - ISampleSelector sample_sel) +void AlembicObject::load_data_in_cache(CachedData &cached_data, + AlembicProcedural *proc, + IPolyMeshSchema &schema, + Progress &progress) { - std::vector<std::string> face_sets; - schema.getFaceSetNames(face_sets); - - if (face_sets.empty()) { - return; - } - - const Int32ArraySamplePtr face_counts = schema.getFaceCountsProperty().getValue(); - - polygon_to_shader.resize(face_counts->size()); - - foreach (const std::string &face_set_name, face_sets) { - int shader_index = 0; - - foreach (Node *node, get_used_shaders()) { - if (node->name == face_set_name) { - break; - } - - ++shader_index; - } - - if (shader_index >= get_used_shaders().size()) { - /* use the first shader instead if none was found */ - shader_index = 0; - } - - const IFaceSet face_set = schema.getFaceSet(face_set_name); - - if (!face_set.valid()) { - continue; - } - - const IFaceSetSchema face_schem = face_set.getSchema(); - const IFaceSetSchema::Sample face_sample = face_schem.getValue(sample_sel); - const Int32ArraySamplePtr group_faces = face_sample.getFaces(); - const size_t num_group_faces = group_faces->size(); - - for (size_t l = 0; l < num_group_faces; l++) { - size_t pos = (*group_faces)[l]; - - if (pos >= polygon_to_shader.size()) { - continue; - } - - polygon_to_shader[pos] = shader_index; - } - } -} - -void AlembicObject::load_all_data(AlembicProcedural *proc, - IPolyMeshSchema &schema, - Progress &progress) -{ - cached_data.clear(); - /* Only load data for the original Geometry. */ if (instance_of) { return; } - const TimeSamplingPtr time_sampling = schema.getTimeSampling(); - cached_data.set_time_sampling(*time_sampling); - - const IN3fGeomParam &normals = schema.getNormalsParam(); - - ccl::set<chrono_t> times = get_relevant_sample_times( - proc, *time_sampling, schema.getNumSamples()); - - /* Key used to determine if the triangles change over time, if the key is the same as the - * last one, we can avoid creating a new entry in the cache and simply point to the last - * frame. */ - ArraySample::Key previous_key; - - /* read topology */ - foreach (chrono_t time, times) { - if (progress.get_cancel()) { - return; - } - - const ISampleSelector iss = ISampleSelector(time); - const IPolyMeshSchema::Sample sample = schema.getValue(iss); - - add_positions(sample.getPositions(), time, cached_data); - - /* Only copy triangles for other frames if the topology is changing over time as well. */ - if (schema.getTopologyVariance() != kHomogenousTopology || cached_data.triangles.size() == 0) { - const ArraySample::Key key = sample.getFaceIndices()->getKey(); - - if (key == previous_key) { - cached_data.triangles.reuse_data_for_last_time(time); - cached_data.triangles_loops.reuse_data_for_last_time(time); - cached_data.shader.reuse_data_for_last_time(time); - } - else { - /* start by reading the face sets (per face shader), as we directly split polygons to - * triangles - */ - array<int> polygon_to_shader; - read_face_sets(schema, polygon_to_shader, iss); - - add_triangles( - sample.getFaceCounts(), sample.getFaceIndices(), time, cached_data, polygon_to_shader); - } + cached_data.clear(); - previous_key = key; - } + PolyMeshSchemaData data; + data.topology_variance = schema.getTopologyVariance(); + data.time_sampling = schema.getTimeSampling(); + data.positions = schema.getPositionsProperty(); + data.face_counts = schema.getFaceCountsProperty(); + data.face_indices = schema.getFaceIndicesProperty(); + data.normals = schema.getNormalsParam(); + data.num_samples = schema.getNumSamples(); + data.shader_face_sets = parse_face_sets_for_shader_assignment(schema, get_used_shaders()); - if (normals.valid()) { - add_normals(sample.getFaceIndices(), normals, time, cached_data); - } - } + read_geometry_data(proc, cached_data, data, progress); if (progress.get_cancel()) { return; } - update_shader_attributes(schema.getArbGeomParams(), progress); + /* Use the schema as the base compound property to also be able to look for top level properties. + */ + read_attributes( + proc, cached_data, schema, schema.getUVsParam(), get_requested_attributes(), progress); if (progress.get_cancel()) { return; } - const IV2fGeomParam &uvs = schema.getUVsParam(); - - if (uvs.valid()) { - add_uvs(proc, uvs, cached_data, progress); - } - + cached_data.invalidate_last_loaded_time(true); data_loaded = true; } -void AlembicObject::load_all_data(AlembicProcedural *proc, ISubDSchema &schema, Progress &progress) +void AlembicObject::load_data_in_cache(CachedData &cached_data, + AlembicProcedural *proc, + ISubDSchema &schema, + Progress &progress) { - cached_data.clear(); - /* Only load data for the original Geometry. */ if (instance_of) { return; } - AttributeRequestSet requested_attributes = get_requested_attributes(); - - const TimeSamplingPtr time_sampling = schema.getTimeSampling(); - cached_data.set_time_sampling(*time_sampling); - - ccl::set<chrono_t> times = get_relevant_sample_times( - proc, *time_sampling, schema.getNumSamples()); - - /* read topology */ - foreach (chrono_t time, times) { - if (progress.get_cancel()) { - return; - } - - const ISampleSelector iss = ISampleSelector(time); - const ISubDSchema::Sample sample = schema.getValue(iss); - - add_positions(sample.getPositions(), time, cached_data); - - const Int32ArraySamplePtr face_counts = sample.getFaceCounts(); - const Int32ArraySamplePtr face_indices = sample.getFaceIndices(); - - /* start by reading the face sets (per face shader) */ - array<int> polygon_to_shader; - read_face_sets(schema, polygon_to_shader, iss); - - /* read faces */ - array<int> subd_start_corner; - array<int> shader; - array<int> subd_num_corners; - array<bool> subd_smooth; - array<int> subd_ptex_offset; - array<int> subd_face_corners; - - const size_t num_faces = face_counts->size(); - const int *face_counts_array = face_counts->get(); - const int *face_indices_array = face_indices->get(); - - int num_ngons = 0; - int num_corners = 0; - for (size_t i = 0; i < face_counts->size(); i++) { - num_ngons += (face_counts_array[i] == 4 ? 0 : 1); - num_corners += face_counts_array[i]; - } - - subd_start_corner.reserve(num_faces); - subd_num_corners.reserve(num_faces); - subd_smooth.reserve(num_faces); - subd_ptex_offset.reserve(num_faces); - shader.reserve(num_faces); - subd_face_corners.reserve(num_corners); - - int start_corner = 0; - int current_shader = 0; - int ptex_offset = 0; - - for (size_t i = 0; i < face_counts->size(); i++) { - num_corners = face_counts_array[i]; - - if (!polygon_to_shader.empty()) { - current_shader = polygon_to_shader[i]; - } - - subd_start_corner.push_back_reserved(start_corner); - subd_num_corners.push_back_reserved(num_corners); - - for (int j = 0; j < num_corners; ++j) { - subd_face_corners.push_back_reserved(face_indices_array[start_corner + j]); - } - - shader.push_back_reserved(current_shader); - subd_smooth.push_back_reserved(1); - subd_ptex_offset.push_back_reserved(ptex_offset); - - ptex_offset += (num_corners == 4 ? 1 : num_corners); - - start_corner += num_corners; - } - - cached_data.shader.add_data(shader, time); - cached_data.subd_start_corner.add_data(subd_start_corner, time); - cached_data.subd_num_corners.add_data(subd_num_corners, time); - cached_data.subd_smooth.add_data(subd_smooth, time); - cached_data.subd_ptex_offset.add_data(subd_ptex_offset, time); - cached_data.subd_face_corners.add_data(subd_face_corners, time); - cached_data.num_ngons.add_data(num_ngons, time); - - /* read creases */ - Int32ArraySamplePtr creases_length = sample.getCreaseLengths(); - Int32ArraySamplePtr creases_indices = sample.getCreaseIndices(); - FloatArraySamplePtr creases_sharpnesses = sample.getCreaseSharpnesses(); - - if (creases_length && creases_indices && creases_sharpnesses) { - array<int> creases_edge; - array<float> creases_weight; - - creases_edge.reserve(creases_sharpnesses->size() * 2); - creases_weight.reserve(creases_sharpnesses->size()); - - int length_offset = 0; - int weight_offset = 0; - for (size_t c = 0; c < creases_length->size(); ++c) { - const int crease_length = creases_length->get()[c]; - - for (size_t j = 0; j < crease_length - 1; ++j) { - creases_edge.push_back_reserved(creases_indices->get()[length_offset + j]); - creases_edge.push_back_reserved(creases_indices->get()[length_offset + j + 1]); - creases_weight.push_back_reserved(creases_sharpnesses->get()[weight_offset++]); - } - - length_offset += crease_length; - } - - cached_data.subd_creases_edge.add_data(creases_edge, time); - cached_data.subd_creases_weight.add_data(creases_weight, time); - } - } + cached_data.clear(); - /* TODO(@kevindietrich) : attributes, need test files */ + SubDSchemaData data; + data.time_sampling = schema.getTimeSampling(); + data.num_samples = schema.getNumSamples(); + data.topology_variance = schema.getTopologyVariance(); + data.face_counts = schema.getFaceCountsProperty(); + data.face_indices = schema.getFaceIndicesProperty(); + data.positions = schema.getPositionsProperty(); + data.face_varying_interpolate_boundary = schema.getFaceVaryingInterpolateBoundaryProperty(); + data.face_varying_propagate_corners = schema.getFaceVaryingPropagateCornersProperty(); + data.interpolate_boundary = schema.getInterpolateBoundaryProperty(); + data.crease_indices = schema.getCreaseIndicesProperty(); + data.crease_lengths = schema.getCreaseLengthsProperty(); + data.crease_sharpnesses = schema.getCreaseSharpnessesProperty(); + data.corner_indices = schema.getCornerIndicesProperty(); + data.corner_sharpnesses = schema.getCornerSharpnessesProperty(); + data.holes = schema.getHolesProperty(); + data.subdivision_scheme = schema.getSubdivisionSchemeProperty(); + data.velocities = schema.getVelocitiesProperty(); + data.shader_face_sets = parse_face_sets_for_shader_assignment(schema, get_used_shaders()); + + read_geometry_data(proc, cached_data, data, progress); if (progress.get_cancel()) { return; } + /* Use the schema as the base compound property to also be able to look for top level properties. + */ + read_attributes(proc, + cached_data, + schema.getArbGeomParams(), + schema.getUVsParam(), + get_requested_attributes(), + progress); + + cached_data.invalidate_last_loaded_time(true); data_loaded = true; } -void AlembicObject::load_all_data(AlembicProcedural *proc, - const ICurvesSchema &schema, - Progress &progress, - float default_radius) +void AlembicObject::load_data_in_cache(CachedData &cached_data, + AlembicProcedural *proc, + const ICurvesSchema &schema, + Progress &progress) { - cached_data.clear(); - /* Only load data for the original Geometry. */ if (instance_of) { return; } - const TimeSamplingPtr time_sampling = schema.getTimeSampling(); - cached_data.set_time_sampling(*time_sampling); - - ccl::set<chrono_t> times = get_relevant_sample_times( - proc, *time_sampling, schema.getNumSamples()); - - foreach (chrono_t time, times) { - if (progress.get_cancel()) { - return; - } - - const ISampleSelector iss = ISampleSelector(time); - const ICurvesSchema::Sample sample = schema.getValue(iss); - - const Int32ArraySamplePtr curves_num_vertices = sample.getCurvesNumVertices(); - const P3fArraySamplePtr position = sample.getPositions(); - - const IFloatGeomParam widths_param = schema.getWidthsParam(); - FloatArraySamplePtr radiuses; - - if (widths_param.valid()) { - IFloatGeomParam::Sample wsample = widths_param.getExpandedValue(iss); - radiuses = wsample.getVals(); - } - - const bool do_radius = (radiuses != nullptr) && (radiuses->size() > 1); - float radius = (radiuses && radiuses->size() == 1) ? (*radiuses)[0] : default_radius; - - array<float3> curve_keys; - array<float> curve_radius; - array<int> curve_first_key; - array<int> curve_shader; - - const bool is_homogenous = schema.getTopologyVariance() == kHomogenousTopology; - - curve_keys.reserve(position->size()); - curve_radius.reserve(position->size()); - curve_first_key.reserve(curves_num_vertices->size()); - curve_shader.reserve(curves_num_vertices->size()); - - int offset = 0; - for (size_t i = 0; i < curves_num_vertices->size(); i++) { - const int num_vertices = curves_num_vertices->get()[i]; - - for (int j = 0; j < num_vertices; j++) { - const V3f &f = position->get()[offset + j]; - curve_keys.push_back_reserved(make_float3_from_yup(f)); - - if (do_radius) { - radius = (*radiuses)[offset + j]; - } - - curve_radius.push_back_reserved(radius * radius_scale); - } - - if (!is_homogenous || cached_data.curve_first_key.size() == 0) { - curve_first_key.push_back_reserved(offset); - curve_shader.push_back_reserved(0); - } - - offset += num_vertices; - } + cached_data.clear(); - cached_data.curve_keys.add_data(curve_keys, time); - cached_data.curve_radius.add_data(curve_radius, time); + CurvesSchemaData data; + data.positions = schema.getPositionsProperty(); + data.position_weights = schema.getPositionWeightsProperty(); + data.normals = schema.getNormalsParam(); + data.knots = schema.getKnotsProperty(); + data.orders = schema.getOrdersProperty(); + data.widths = schema.getWidthsParam(); + data.velocities = schema.getVelocitiesProperty(); + data.time_sampling = schema.getTimeSampling(); + data.topology_variance = schema.getTopologyVariance(); + data.num_samples = schema.getNumSamples(); + data.num_vertices = schema.getNumVerticesProperty(); + data.default_radius = proc->get_default_radius(); + data.radius_scale = get_radius_scale(); + + read_geometry_data(proc, cached_data, data, progress); - if (!is_homogenous || cached_data.curve_first_key.size() == 0) { - cached_data.curve_first_key.add_data(curve_first_key, time); - cached_data.curve_shader.add_data(curve_shader, time); - } + if (progress.get_cancel()) { + return; } - // TODO(@kevindietrich): attributes, need example files + /* Use the schema as the base compound property to also be able to look for top level properties. + */ + read_attributes( + proc, cached_data, schema, schema.getUVsParam(), get_requested_attributes(), progress); + cached_data.invalidate_last_loaded_time(true); data_loaded = true; } -void AlembicObject::setup_transform_cache(float scale) +void AlembicObject::setup_transform_cache(CachedData &cached_data, float scale) { cached_data.transforms.clear(); cached_data.transforms.invalidate_last_loaded_time(); @@ -1102,188 +614,6 @@ AttributeRequestSet AlembicObject::get_requested_attributes() return requested_attributes; } -void AlembicObject::read_attribute(const ICompoundProperty &arb_geom_params, - const ustring &attr_name, - Progress &progress) -{ - const PropertyHeader *prop = arb_geom_params.getPropertyHeader(attr_name.c_str()); - - if (prop == nullptr) { - return; - } - - if (IV2fProperty::matches(prop->getMetaData()) && Alembic::AbcGeom::isUV(*prop)) { - const IV2fGeomParam ¶m = IV2fGeomParam(arb_geom_params, prop->getName()); - - CachedData::CachedAttribute &attribute = cached_data.add_attribute(attr_name, - *param.getTimeSampling()); - - for (size_t i = 0; i < param.getNumSamples(); ++i) { - if (progress.get_cancel()) { - return; - } - - ISampleSelector iss = ISampleSelector(index_t(i)); - - IV2fGeomParam::Sample sample; - param.getIndexed(sample, iss); - - const chrono_t time = param.getTimeSampling()->getSampleTime(index_t(i)); - - if (param.getScope() == kFacevaryingScope) { - V2fArraySamplePtr values = sample.getVals(); - UInt32ArraySamplePtr indices = sample.getIndices(); - - attribute.std = ATTR_STD_NONE; - attribute.element = ATTR_ELEMENT_CORNER; - attribute.type_desc = TypeFloat2; - - const array<int3> *triangles = - cached_data.triangles.data_for_time_no_check(time).get_data_or_null(); - const array<int3> *triangles_loops = - cached_data.triangles_loops.data_for_time_no_check(time).get_data_or_null(); - - if (!triangles || !triangles_loops) { - return; - } - - array<char> data; - data.resize(triangles->size() * 3 * sizeof(float2)); - - float2 *data_float2 = reinterpret_cast<float2 *>(data.data()); - - for (const int3 &loop : *triangles_loops) { - unsigned int v0 = (*indices)[loop.x]; - unsigned int v1 = (*indices)[loop.y]; - unsigned int v2 = (*indices)[loop.z]; - - data_float2[0] = make_float2((*values)[v0][0], (*values)[v0][1]); - data_float2[1] = make_float2((*values)[v1][0], (*values)[v1][1]); - data_float2[2] = make_float2((*values)[v2][0], (*values)[v2][1]); - data_float2 += 3; - } - - attribute.data.set_time_sampling(*param.getTimeSampling()); - attribute.data.add_data(data, time); - } - } - } - else if (IC3fProperty::matches(prop->getMetaData())) { - const IC3fGeomParam ¶m = IC3fGeomParam(arb_geom_params, prop->getName()); - - CachedData::CachedAttribute &attribute = cached_data.add_attribute(attr_name, - *param.getTimeSampling()); - - for (size_t i = 0; i < param.getNumSamples(); ++i) { - if (progress.get_cancel()) { - return; - } - - ISampleSelector iss = ISampleSelector(index_t(i)); - - IC3fGeomParam::Sample sample; - param.getIndexed(sample, iss); - - const chrono_t time = param.getTimeSampling()->getSampleTime(index_t(i)); - - C3fArraySamplePtr values = sample.getVals(); - - attribute.std = ATTR_STD_NONE; - - if (param.getScope() == kVaryingScope) { - attribute.element = ATTR_ELEMENT_CORNER_BYTE; - attribute.type_desc = TypeRGBA; - - const array<int3> *triangles = - cached_data.triangles.data_for_time_no_check(time).get_data_or_null(); - - if (!triangles) { - return; - } - - array<char> data; - data.resize(triangles->size() * 3 * sizeof(uchar4)); - - uchar4 *data_uchar4 = reinterpret_cast<uchar4 *>(data.data()); - - int offset = 0; - for (const int3 &tri : *triangles) { - Imath::C3f v = (*values)[tri.x]; - data_uchar4[offset + 0] = color_float_to_byte(make_float3(v.x, v.y, v.z)); - - v = (*values)[tri.y]; - data_uchar4[offset + 1] = color_float_to_byte(make_float3(v.x, v.y, v.z)); - - v = (*values)[tri.z]; - data_uchar4[offset + 2] = color_float_to_byte(make_float3(v.x, v.y, v.z)); - - offset += 3; - } - - attribute.data.set_time_sampling(*param.getTimeSampling()); - attribute.data.add_data(data, time); - } - } - } - else if (IC4fProperty::matches(prop->getMetaData())) { - const IC4fGeomParam ¶m = IC4fGeomParam(arb_geom_params, prop->getName()); - - CachedData::CachedAttribute &attribute = cached_data.add_attribute(attr_name, - *param.getTimeSampling()); - - for (size_t i = 0; i < param.getNumSamples(); ++i) { - if (progress.get_cancel()) { - return; - } - - ISampleSelector iss = ISampleSelector(index_t(i)); - - IC4fGeomParam::Sample sample; - param.getIndexed(sample, iss); - - const chrono_t time = param.getTimeSampling()->getSampleTime(index_t(i)); - - C4fArraySamplePtr values = sample.getVals(); - - attribute.std = ATTR_STD_NONE; - - if (param.getScope() == kVaryingScope) { - attribute.element = ATTR_ELEMENT_CORNER_BYTE; - attribute.type_desc = TypeRGBA; - - const array<int3> *triangles = - cached_data.triangles.data_for_time_no_check(time).get_data_or_null(); - - if (!triangles) { - return; - } - - array<char> data; - data.resize(triangles->size() * 3 * sizeof(uchar4)); - - uchar4 *data_uchar4 = reinterpret_cast<uchar4 *>(data.data()); - - int offset = 0; - for (const int3 &tri : *triangles) { - Imath::C4f v = (*values)[tri.x]; - data_uchar4[offset + 0] = color_float4_to_uchar4(make_float4(v.r, v.g, v.b, v.a)); - - v = (*values)[tri.y]; - data_uchar4[offset + 1] = color_float4_to_uchar4(make_float4(v.r, v.g, v.b, v.a)); - - v = (*values)[tri.z]; - data_uchar4[offset + 2] = color_float4_to_uchar4(make_float4(v.r, v.g, v.b, v.a)); - - offset += 3; - } - - attribute.data.set_time_sampling(*param.getTimeSampling()); - attribute.data.add_data(data, time); - } - } - } -} - /* Update existing attributes and remove any attribute not in the cached_data, those attributes * were added by Cycles (e.g. face normals) */ static void update_attributes(AttributeSet &attributes, CachedData &cached_data, double frame_time) @@ -1291,7 +621,11 @@ static void update_attributes(AttributeSet &attributes, CachedData &cached_data, set<Attribute *> cached_attributes; for (CachedData::CachedAttribute &attribute : cached_data.attributes) { - const array<char> *attr_data = attribute.data.data_for_time(frame_time).get_data_or_null(); + const CacheLookupResult<array<char>> result = attribute.data.data_for_time(frame_time); + + if (result.has_no_data_for_time()) { + continue; + } Attribute *attr = nullptr; if (attribute.std != ATTR_STD_NONE) { @@ -1304,18 +638,19 @@ static void update_attributes(AttributeSet &attributes, CachedData &cached_data, cached_attributes.insert(attr); - if (!attr_data) { - /* no new data */ + if (!result.has_new_data()) { continue; } + const ccl::array<char> &attr_data = result.get_data(); + /* weak way of detecting if the topology has changed * todo: reuse code from device_update patch */ - if (attr->buffer.size() != attr_data->size()) { - attr->buffer.resize(attr_data->size()); + if (attr->buffer.size() != attr_data.size()) { + attr->buffer.resize(attr_data.size()); } - memcpy(attr->data(), attr_data->data(), attr_data->size()); + memcpy(attr->data(), attr_data.data(), attr_data.size()); attr->modified = true; } @@ -1476,6 +811,7 @@ void AlembicProcedural::generate(Scene *scene, Progress &progress) read_subd(object, frame_time); } + object->need_shader_update = false; object->clear_modified(); } @@ -1960,12 +1296,17 @@ void AlembicProcedural::build_caches(Progress &progress) if (!object->has_data_loaded()) { IPolyMesh polymesh(object->iobject, Alembic::Abc::kWrapExisting); IPolyMeshSchema schema = polymesh.getSchema(); - object->load_all_data(this, schema, progress); + object->load_data_in_cache(object->get_cached_data(), this, schema, progress); } else if (object->need_shader_update) { IPolyMesh polymesh(object->iobject, Alembic::Abc::kWrapExisting); IPolyMeshSchema schema = polymesh.getSchema(); - object->update_shader_attributes(schema.getArbGeomParams(), progress); + read_attributes(this, + object->get_cached_data(), + schema, + schema.getUVsParam(), + object->get_requested_attributes(), + progress); } } else if (object->schema_type == AlembicObject::CURVES) { @@ -1973,24 +1314,29 @@ void AlembicProcedural::build_caches(Progress &progress) object->radius_scale_is_modified()) { ICurves curves(object->iobject, Alembic::Abc::kWrapExisting); ICurvesSchema schema = curves.getSchema(); - object->load_all_data(this, schema, progress, default_radius); + object->load_data_in_cache(object->get_cached_data(), this, schema, progress); } } else if (object->schema_type == AlembicObject::SUBD) { if (!object->has_data_loaded()) { ISubD subd_mesh(object->iobject, Alembic::Abc::kWrapExisting); ISubDSchema schema = subd_mesh.getSchema(); - object->load_all_data(this, schema, progress); + object->load_data_in_cache(object->get_cached_data(), this, schema, progress); } else if (object->need_shader_update) { ISubD subd_mesh(object->iobject, Alembic::Abc::kWrapExisting); ISubDSchema schema = subd_mesh.getSchema(); - object->update_shader_attributes(schema.getArbGeomParams(), progress); + read_attributes(this, + object->get_cached_data(), + schema, + schema.getUVsParam(), + object->get_requested_attributes(), + progress); } } if (scale_is_modified() || object->get_cached_data().transforms.size() == 0) { - object->setup_transform_cache(scale); + object->setup_transform_cache(object->get_cached_data(), scale); } } } diff --git a/intern/cycles/render/alembic.h b/intern/cycles/render/alembic.h index 3bbd10fad61..61c0e40fe4a 100644 --- a/intern/cycles/render/alembic.h +++ b/intern/cycles/render/alembic.h @@ -152,6 +152,10 @@ template<typename T> class DataStore { double last_loaded_time = std::numeric_limits<double>::max(); public: + /* Keys used to compare values. */ + Alembic::AbcCoreAbstract::ArraySample::Key key1; + Alembic::AbcCoreAbstract::ArraySample::Key key2; + void set_time_sampling(Alembic::AbcCoreAbstract::TimeSampling time_sampling_) { time_sampling = time_sampling_; @@ -225,6 +229,11 @@ template<typename T> class DataStore { index_data_map.push_back({time, data_index.source_time, data_index.index}); } + void add_no_data(double time) + { + index_data_map.push_back({time, time, -1ul}); + } + bool is_constant() const { return data.size() <= 1; @@ -284,7 +293,7 @@ struct CachedData { DataStore<array<int3>> triangles{}; /* triangle "loops" are the polygons' vertices indices used for indexing face varying attributes * (like UVs) */ - DataStore<array<int3>> triangles_loops{}; + DataStore<array<int>> uv_loops{}; DataStore<array<int>> shader{}; /* subd data */ @@ -362,16 +371,18 @@ class AlembicObject : public Node { void set_object(Object *object); Object *get_object(); - void load_all_data(AlembicProcedural *proc, - Alembic::AbcGeom::IPolyMeshSchema &schema, - Progress &progress); - void load_all_data(AlembicProcedural *proc, - Alembic::AbcGeom::ISubDSchema &schema, - Progress &progress); - void load_all_data(AlembicProcedural *proc, - const Alembic::AbcGeom::ICurvesSchema &schema, - Progress &progress, - float default_radius); + void load_data_in_cache(CachedData &cached_data, + AlembicProcedural *proc, + Alembic::AbcGeom::IPolyMeshSchema &schema, + Progress &progress); + void load_data_in_cache(CachedData &cached_data, + AlembicProcedural *proc, + Alembic::AbcGeom::ISubDSchema &schema, + Progress &progress); + void load_data_in_cache(CachedData &cached_data, + AlembicProcedural *proc, + const Alembic::AbcGeom::ICurvesSchema &schema, + Progress &progress); bool has_data_loaded() const; @@ -397,33 +408,21 @@ class AlembicObject : public Node { CachedData &get_cached_data() { - return cached_data; + return cached_data_; } bool is_constant() const { - return cached_data.is_constant(); + return cached_data_.is_constant(); } Object *object = nullptr; bool data_loaded = false; - CachedData cached_data; - - void update_shader_attributes(const Alembic::AbcGeom::ICompoundProperty &arb_geom_params, - Progress &progress); - - void read_attribute(const Alembic::AbcGeom::ICompoundProperty &arb_geom_params, - const ustring &attr_name, - Progress &progress); - - template<typename SchemaType> - void read_face_sets(SchemaType &schema, - array<int> &polygon_to_shader, - Alembic::AbcGeom::ISampleSelector sample_sel); + CachedData cached_data_; - void setup_transform_cache(float scale); + void setup_transform_cache(CachedData &cached_data, float scale); AttributeRequestSet get_requested_attributes(); }; diff --git a/intern/cycles/render/alembic_read.cpp b/intern/cycles/render/alembic_read.cpp new file mode 100644 index 00000000000..9fdc7561957 --- /dev/null +++ b/intern/cycles/render/alembic_read.cpp @@ -0,0 +1,1003 @@ +/* + * Copyright 2021 Blender Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "render/alembic_read.h" + +#include "render/alembic.h" +#include "render/mesh.h" + +#include "util/util_progress.h" + +#ifdef WITH_ALEMBIC + +using namespace Alembic::AbcGeom; + +CCL_NAMESPACE_BEGIN + +static float3 make_float3_from_yup(const V3f &v) +{ + return make_float3(v.x, -v.z, v.y); +} + +/* get the sample times to load data for the given the start and end frame of the procedural */ +static set<chrono_t> get_relevant_sample_times(AlembicProcedural *proc, + const TimeSampling &time_sampling, + size_t num_samples) +{ + set<chrono_t> result; + + if (num_samples < 2) { + result.insert(0.0); + return result; + } + + // load the data for the entire animation + const double start_frame = static_cast<double>(proc->get_start_frame()); + const double end_frame = static_cast<double>(proc->get_end_frame()); + + const double frame_rate = static_cast<double>(proc->get_frame_rate()); + const double start_time = start_frame / frame_rate; + const double end_time = (end_frame + 1) / frame_rate; + + const size_t start_index = time_sampling.getFloorIndex(start_time, num_samples).first; + const size_t end_index = time_sampling.getCeilIndex(end_time, num_samples).first; + + for (size_t i = start_index; i < end_index; ++i) { + result.insert(time_sampling.getSampleTime(i)); + } + + return result; +} + +/* Main function to read data, this will iterate over all the relevant sample times for the + * duration of the requested animation, and call the DataReadingFunc for each of those sample time. + */ +template<typename Params, typename DataReadingFunc> +static void read_data_loop(AlembicProcedural *proc, + CachedData &cached_data, + const Params ¶ms, + DataReadingFunc &&func, + Progress &progress) +{ + const std::set<chrono_t> times = get_relevant_sample_times( + proc, *params.time_sampling, params.num_samples); + + cached_data.set_time_sampling(*params.time_sampling); + + for (chrono_t time : times) { + if (progress.get_cancel()) { + return; + } + + func(cached_data, params, time); + } +} + +/* Polygon Mesh Geometries. */ + +/* Compute the vertex normals in case none are present in the IPolyMeshSchema, this is mostly used + * to avoid computing them in the GeometryManager in order to speed up data updates. */ +static void compute_vertex_normals(CachedData &cache, double current_time) +{ + if (cache.vertices.size() == 0) { + return; + } + + CachedData::CachedAttribute &attr_normal = cache.add_attribute( + ustring("N"), cache.vertices.get_time_sampling()); + attr_normal.std = ATTR_STD_VERTEX_NORMAL; + attr_normal.element = ATTR_ELEMENT_VERTEX; + attr_normal.type_desc = TypeNormal; + + const array<float3> *vertices = + cache.vertices.data_for_time_no_check(current_time).get_data_or_null(); + const array<int3> *triangles = + cache.triangles.data_for_time_no_check(current_time).get_data_or_null(); + + if (!vertices || !triangles) { + attr_normal.data.add_no_data(current_time); + return; + } + + array<char> attr_data(vertices->size() * sizeof(float3)); + float3 *attr_ptr = reinterpret_cast<float3 *>(attr_data.data()); + memset(attr_ptr, 0, vertices->size() * sizeof(float3)); + + for (size_t t = 0; t < triangles->size(); ++t) { + const int3 tri_int3 = triangles->data()[t]; + Mesh::Triangle tri{}; + tri.v[0] = tri_int3[0]; + tri.v[1] = tri_int3[1]; + tri.v[2] = tri_int3[2]; + + const float3 tri_N = tri.compute_normal(vertices->data()); + + for (int v = 0; v < 3; ++v) { + attr_ptr[tri_int3[v]] += tri_N; + } + } + + for (size_t v = 0; v < vertices->size(); ++v) { + attr_ptr[v] = normalize(attr_ptr[v]); + } + + attr_normal.data.add_data(attr_data, current_time); +} + +static void add_normals(const Int32ArraySamplePtr face_indices, + const IN3fGeomParam &normals, + double time, + CachedData &cached_data) +{ + switch (normals.getScope()) { + case kFacevaryingScope: { + const ISampleSelector iss = ISampleSelector(time); + const IN3fGeomParam::Sample sample = normals.getExpandedValue(iss); + + if (!sample.valid()) { + return; + } + + CachedData::CachedAttribute &attr = cached_data.add_attribute(ustring(normals.getName()), + *normals.getTimeSampling()); + attr.std = ATTR_STD_VERTEX_NORMAL; + + const array<float3> *vertices = + cached_data.vertices.data_for_time_no_check(time).get_data_or_null(); + + if (!vertices) { + return; + } + + array<char> data; + data.resize(vertices->size() * sizeof(float3)); + + float3 *data_float3 = reinterpret_cast<float3 *>(data.data()); + + const int *face_indices_array = face_indices->get(); + const N3fArraySamplePtr values = sample.getVals(); + + for (size_t i = 0; i < face_indices->size(); ++i) { + int point_index = face_indices_array[i]; + data_float3[point_index] = make_float3_from_yup(values->get()[i]); + } + + attr.data.add_data(data, time); + break; + } + case kVaryingScope: + case kVertexScope: { + const ISampleSelector iss = ISampleSelector(time); + const IN3fGeomParam::Sample sample = normals.getExpandedValue(iss); + + if (!sample.valid()) { + return; + } + + CachedData::CachedAttribute &attr = cached_data.add_attribute(ustring(normals.getName()), + *normals.getTimeSampling()); + attr.std = ATTR_STD_VERTEX_NORMAL; + + const array<float3> *vertices = + cached_data.vertices.data_for_time_no_check(time).get_data_or_null(); + + if (!vertices) { + return; + } + + array<char> data; + data.resize(vertices->size() * sizeof(float3)); + + float3 *data_float3 = reinterpret_cast<float3 *>(data.data()); + + const Imath::V3f *values = sample.getVals()->get(); + + for (size_t i = 0; i < vertices->size(); ++i) { + data_float3[i] = make_float3_from_yup(values[i]); + } + + attr.data.add_data(data, time); + + break; + } + default: { + break; + } + } +} + +static void add_positions(const P3fArraySamplePtr positions, double time, CachedData &cached_data) +{ + if (!positions) { + return; + } + + array<float3> vertices; + vertices.reserve(positions->size()); + + for (size_t i = 0; i < positions->size(); i++) { + V3f f = positions->get()[i]; + vertices.push_back_reserved(make_float3_from_yup(f)); + } + + cached_data.vertices.add_data(vertices, time); +} + +static void add_triangles(const Int32ArraySamplePtr face_counts, + const Int32ArraySamplePtr face_indices, + double time, + CachedData &cached_data, + const array<int> &polygon_to_shader) +{ + if (!face_counts || !face_indices) { + return; + } + + const size_t num_faces = face_counts->size(); + const int *face_counts_array = face_counts->get(); + const int *face_indices_array = face_indices->get(); + + size_t num_triangles = 0; + for (size_t i = 0; i < face_counts->size(); i++) { + num_triangles += face_counts_array[i] - 2; + } + + array<int> shader; + array<int3> triangles; + array<int> uv_loops; + shader.reserve(num_triangles); + triangles.reserve(num_triangles); + uv_loops.reserve(num_triangles * 3); + int index_offset = 0; + + for (size_t i = 0; i < num_faces; i++) { + int current_shader = 0; + + if (!polygon_to_shader.empty()) { + current_shader = polygon_to_shader[i]; + } + + for (int j = 0; j < face_counts_array[i] - 2; j++) { + int v0 = face_indices_array[index_offset]; + int v1 = face_indices_array[index_offset + j + 1]; + int v2 = face_indices_array[index_offset + j + 2]; + + shader.push_back_reserved(current_shader); + + /* Alembic orders the loops following the RenderMan convention, so need to go in reverse. */ + triangles.push_back_reserved(make_int3(v2, v1, v0)); + uv_loops.push_back_reserved(index_offset + j + 2); + uv_loops.push_back_reserved(index_offset + j + 1); + uv_loops.push_back_reserved(index_offset); + } + + index_offset += face_counts_array[i]; + } + + cached_data.triangles.add_data(triangles, time); + cached_data.uv_loops.add_data(uv_loops, time); + cached_data.shader.add_data(shader, time); +} + +static array<int> compute_polygon_to_shader_map( + const Int32ArraySamplePtr &face_counts, + const vector<FaceSetShaderIndexPair> &face_set_shader_index, + ISampleSelector sample_sel) +{ + if (face_set_shader_index.empty()) { + return {}; + } + + if (!face_counts) { + return {}; + } + + if (face_counts->size() == 0) { + return {}; + } + + array<int> polygon_to_shader(face_counts->size()); + + for (const FaceSetShaderIndexPair &pair : face_set_shader_index) { + const IFaceSet &face_set = pair.face_set; + const IFaceSetSchema face_schem = face_set.getSchema(); + const IFaceSetSchema::Sample face_sample = face_schem.getValue(sample_sel); + const Int32ArraySamplePtr group_faces = face_sample.getFaces(); + const size_t num_group_faces = group_faces->size(); + + for (size_t l = 0; l < num_group_faces; l++) { + size_t pos = (*group_faces)[l]; + + if (pos >= polygon_to_shader.size()) { + continue; + } + + polygon_to_shader[pos] = pair.shader_index; + } + } + + return polygon_to_shader; +} + +static void read_poly_mesh_geometry(CachedData &cached_data, + const PolyMeshSchemaData &data, + chrono_t time) +{ + const ISampleSelector iss = ISampleSelector(time); + + add_positions(data.positions.getValue(iss), time, cached_data); + + const Int32ArraySamplePtr face_counts = data.face_counts.getValue(iss); + const Int32ArraySamplePtr face_indices = data.face_indices.getValue(iss); + + /* Only copy triangles for other frames if the topology is changing over time as well. */ + if (data.topology_variance != kHomogeneousTopology || cached_data.triangles.size() == 0) { + bool do_triangles = true; + + /* Compare key with last one to check whether the topology changed. */ + if (cached_data.triangles.size() > 0) { + const ArraySample::Key key = face_indices->getKey(); + + if (key == cached_data.triangles.key1) { + do_triangles = false; + } + + cached_data.triangles.key1 = key; + } + + if (do_triangles) { + const array<int> polygon_to_shader = compute_polygon_to_shader_map( + face_counts, data.shader_face_sets, iss); + add_triangles(face_counts, face_indices, time, cached_data, polygon_to_shader); + } + else { + cached_data.triangles.reuse_data_for_last_time(time); + cached_data.uv_loops.reuse_data_for_last_time(time); + cached_data.shader.reuse_data_for_last_time(time); + } + + /* Initialize the first key. */ + if (data.topology_variance != kHomogeneousTopology && cached_data.triangles.size() == 1) { + cached_data.triangles.key1 = face_indices->getKey(); + } + } + + if (data.normals.valid()) { + add_normals(face_indices, data.normals, time, cached_data); + } + else { + compute_vertex_normals(cached_data, time); + } +} + +void read_geometry_data(AlembicProcedural *proc, + CachedData &cached_data, + const PolyMeshSchemaData &data, + Progress &progress) +{ + read_data_loop(proc, cached_data, data, read_poly_mesh_geometry, progress); +} + +/* Subdivision Geometries */ + +static void add_subd_polygons(CachedData &cached_data, const SubDSchemaData &data, chrono_t time) +{ + const ISampleSelector iss = ISampleSelector(time); + + const Int32ArraySamplePtr face_counts = data.face_counts.getValue(iss); + const Int32ArraySamplePtr face_indices = data.face_indices.getValue(iss); + + array<int> subd_start_corner; + array<int> shader; + array<int> subd_num_corners; + array<bool> subd_smooth; + array<int> subd_ptex_offset; + array<int> subd_face_corners; + array<int> uv_loops; + + const size_t num_faces = face_counts->size(); + const int *face_counts_array = face_counts->get(); + const int *face_indices_array = face_indices->get(); + + int num_ngons = 0; + int num_corners = 0; + for (size_t i = 0; i < face_counts->size(); i++) { + num_ngons += (face_counts_array[i] == 4 ? 0 : 1); + num_corners += face_counts_array[i]; + } + + subd_start_corner.reserve(num_faces); + subd_num_corners.reserve(num_faces); + subd_smooth.reserve(num_faces); + subd_ptex_offset.reserve(num_faces); + shader.reserve(num_faces); + subd_face_corners.reserve(num_corners); + uv_loops.reserve(num_corners); + + int start_corner = 0; + int current_shader = 0; + int ptex_offset = 0; + + const array<int> polygon_to_shader = compute_polygon_to_shader_map( + face_counts, data.shader_face_sets, iss); + + for (size_t i = 0; i < face_counts->size(); i++) { + num_corners = face_counts_array[i]; + + if (!polygon_to_shader.empty()) { + current_shader = polygon_to_shader[i]; + } + + subd_start_corner.push_back_reserved(start_corner); + subd_num_corners.push_back_reserved(num_corners); + + for (int j = 0; j < num_corners; ++j) { + subd_face_corners.push_back_reserved(face_indices_array[start_corner + j]); + uv_loops.push_back_reserved(start_corner + j); + } + + shader.push_back_reserved(current_shader); + subd_smooth.push_back_reserved(1); + subd_ptex_offset.push_back_reserved(ptex_offset); + + ptex_offset += (num_corners == 4 ? 1 : num_corners); + + start_corner += num_corners; + } + + cached_data.shader.add_data(shader, time); + cached_data.subd_start_corner.add_data(subd_start_corner, time); + cached_data.subd_num_corners.add_data(subd_num_corners, time); + cached_data.subd_smooth.add_data(subd_smooth, time); + cached_data.subd_ptex_offset.add_data(subd_ptex_offset, time); + cached_data.subd_face_corners.add_data(subd_face_corners, time); + cached_data.num_ngons.add_data(num_ngons, time); + cached_data.uv_loops.add_data(uv_loops, time); +} + +static void add_subd_creases(CachedData &cached_data, const SubDSchemaData &data, chrono_t time) +{ + if (!(data.crease_indices.valid() && data.crease_indices.valid() && + data.crease_sharpnesses.valid())) { + return; + } + + const ISampleSelector iss = ISampleSelector(time); + + const Int32ArraySamplePtr creases_length = data.crease_lengths.getValue(iss); + const Int32ArraySamplePtr creases_indices = data.crease_indices.getValue(iss); + const FloatArraySamplePtr creases_sharpnesses = data.crease_sharpnesses.getValue(iss); + + if (creases_length && creases_indices && creases_sharpnesses) { + array<int> creases_edge; + array<float> creases_weight; + + creases_edge.reserve(creases_sharpnesses->size() * 2); + creases_weight.reserve(creases_sharpnesses->size()); + + int length_offset = 0; + int weight_offset = 0; + for (size_t c = 0; c < creases_length->size(); ++c) { + const int crease_length = creases_length->get()[c]; + + for (size_t j = 0; j < crease_length - 1; ++j) { + creases_edge.push_back_reserved(creases_indices->get()[length_offset + j]); + creases_edge.push_back_reserved(creases_indices->get()[length_offset + j + 1]); + creases_weight.push_back_reserved(creases_sharpnesses->get()[weight_offset++]); + } + + length_offset += crease_length; + } + + cached_data.subd_creases_edge.add_data(creases_edge, time); + cached_data.subd_creases_weight.add_data(creases_weight, time); + } +} + +static void read_subd_geometry(CachedData &cached_data, const SubDSchemaData &data, chrono_t time) +{ + const ISampleSelector iss = ISampleSelector(time); + + add_positions(data.positions.getValue(iss), time, cached_data); + + if (data.topology_variance != kHomogenousTopology || cached_data.shader.size() == 0) { + add_subd_polygons(cached_data, data, time); + add_subd_creases(cached_data, data, time); + } +} + +void read_geometry_data(AlembicProcedural *proc, + CachedData &cached_data, + const SubDSchemaData &data, + Progress &progress) +{ + read_data_loop(proc, cached_data, data, read_subd_geometry, progress); +} + +/* Curve Geometries. */ + +static void read_curves_data(CachedData &cached_data, const CurvesSchemaData &data, chrono_t time) +{ + const ISampleSelector iss = ISampleSelector(time); + + const Int32ArraySamplePtr curves_num_vertices = data.num_vertices.getValue(iss); + const P3fArraySamplePtr position = data.positions.getValue(iss); + + FloatArraySamplePtr radiuses; + + if (data.widths.valid()) { + IFloatGeomParam::Sample wsample = data.widths.getExpandedValue(iss); + radiuses = wsample.getVals(); + } + + const bool do_radius = (radiuses != nullptr) && (radiuses->size() > 1); + float radius = (radiuses && radiuses->size() == 1) ? (*radiuses)[0] : data.default_radius; + + array<float3> curve_keys; + array<float> curve_radius; + array<int> curve_first_key; + array<int> curve_shader; + + const bool is_homogenous = data.topology_variance == kHomogenousTopology; + + curve_keys.reserve(position->size()); + curve_radius.reserve(position->size()); + curve_first_key.reserve(curves_num_vertices->size()); + curve_shader.reserve(curves_num_vertices->size()); + + int offset = 0; + for (size_t i = 0; i < curves_num_vertices->size(); i++) { + const int num_vertices = curves_num_vertices->get()[i]; + + for (int j = 0; j < num_vertices; j++) { + const V3f &f = position->get()[offset + j]; + // todo(@kevindietrich): we are reading too much data? + curve_keys.push_back_slow(make_float3_from_yup(f)); + + if (do_radius) { + radius = (*radiuses)[offset + j]; + } + + curve_radius.push_back_slow(radius * data.radius_scale); + } + + if (!is_homogenous || cached_data.curve_first_key.size() == 0) { + curve_first_key.push_back_reserved(offset); + curve_shader.push_back_reserved(0); + } + + offset += num_vertices; + } + + cached_data.curve_keys.add_data(curve_keys, time); + cached_data.curve_radius.add_data(curve_radius, time); + + if (!is_homogenous || cached_data.curve_first_key.size() == 0) { + cached_data.curve_first_key.add_data(curve_first_key, time); + cached_data.curve_shader.add_data(curve_shader, time); + } +} + +void read_geometry_data(AlembicProcedural *proc, + CachedData &cached_data, + const CurvesSchemaData &data, + Progress &progress) +{ + read_data_loop(proc, cached_data, data, read_curves_data, progress); +} + +/* Attributes conversions. */ + +/* Type traits for converting between Alembic and Cycles types. + */ + +template<typename T> struct value_type_converter { + using cycles_type = float; + static constexpr TypeDesc type_desc = TypeFloat; + static constexpr const char *type_name = "float (default)"; + + static cycles_type convert_value(T value) + { + return static_cast<float>(value); + } +}; + +template<> struct value_type_converter<Imath::V2f> { + using cycles_type = float2; + static constexpr TypeDesc type_desc = TypeFloat2; + static constexpr const char *type_name = "float2"; + + static cycles_type convert_value(Imath::V2f value) + { + return make_float2(value.x, value.y); + } +}; + +template<> struct value_type_converter<Imath::V3f> { + using cycles_type = float3; + static constexpr TypeDesc type_desc = TypeVector; + static constexpr const char *type_name = "float3"; + + static cycles_type convert_value(Imath::V3f value) + { + return make_float3_from_yup(value); + } +}; + +template<> struct value_type_converter<Imath::C3f> { + using cycles_type = uchar4; + static constexpr TypeDesc type_desc = TypeRGBA; + static constexpr const char *type_name = "rgb"; + + static cycles_type convert_value(Imath::C3f value) + { + return color_float_to_byte(make_float3(value.x, value.y, value.z)); + } +}; + +template<> struct value_type_converter<Imath::C4f> { + using cycles_type = uchar4; + static constexpr TypeDesc type_desc = TypeRGBA; + static constexpr const char *type_name = "rgba"; + + static cycles_type convert_value(Imath::C4f value) + { + return color_float4_to_uchar4(make_float4(value.r, value.g, value.b, value.a)); + } +}; + +/* Main function used to read attributes of any type. */ +template<typename TRAIT> +static void process_attribute(CachedData &cache, + CachedData::CachedAttribute &attribute, + GeometryScope scope, + const typename ITypedGeomParam<TRAIT>::Sample &sample, + double time) +{ + using abc_type = typename TRAIT::value_type; + using cycles_type = typename value_type_converter<abc_type>::cycles_type; + + const TypedArraySample<TRAIT> &values = *sample.getVals(); + + switch (scope) { + case kConstantScope: + case kVertexScope: { + const array<float3> *vertices = + cache.vertices.data_for_time_no_check(time).get_data_or_null(); + + if (!vertices) { + attribute.data.add_no_data(time); + return; + } + + if (vertices->size() != values.size()) { + attribute.data.add_no_data(time); + return; + } + + array<char> data(vertices->size() * sizeof(cycles_type)); + + cycles_type *pod_typed_data = reinterpret_cast<cycles_type *>(data.data()); + + for (size_t i = 0; i < values.size(); ++i) { + *pod_typed_data++ = value_type_converter<abc_type>::convert_value(values[i]); + } + + attribute.data.add_data(data, time); + break; + } + case kVaryingScope: { + const array<int3> *triangles = + cache.triangles.data_for_time_no_check(time).get_data_or_null(); + + if (!triangles) { + attribute.data.add_no_data(time); + return; + } + + array<char> data(triangles->size() * 3 * sizeof(cycles_type)); + + cycles_type *pod_typed_data = reinterpret_cast<cycles_type *>(data.data()); + + for (const int3 &tri : *triangles) { + *pod_typed_data++ = value_type_converter<abc_type>::convert_value(values[tri.x]); + *pod_typed_data++ = value_type_converter<abc_type>::convert_value(values[tri.y]); + *pod_typed_data++ = value_type_converter<abc_type>::convert_value(values[tri.z]); + } + + attribute.data.add_data(data, time); + break; + } + default: { + break; + } + } +} + +/* UVs are processed separately as their indexing is based on loops, instead of vertices or + * corners. */ +static void process_uvs(CachedData &cache, + CachedData::CachedAttribute &attribute, + GeometryScope scope, + const IV2fGeomParam::Sample &sample, + double time) +{ + if (scope != kFacevaryingScope) { + return; + } + + const array<int> *uv_loops = cache.uv_loops.data_for_time_no_check(time).get_data_or_null(); + + if (!uv_loops) { + return; + } + + const array<int3> *triangles = cache.triangles.data_for_time_no_check(time).get_data_or_null(); + const array<int> *corners = + cache.subd_face_corners.data_for_time_no_check(time).get_data_or_null(); + + array<char> data; + if (triangles) { + data.resize(triangles->size() * 3 * sizeof(float2)); + } + else if (corners) { + data.resize(corners->size() * sizeof(float2)); + } + else { + return; + } + + float2 *data_float2 = reinterpret_cast<float2 *>(data.data()); + + const uint32_t *indices = sample.getIndices()->get(); + const V2f *values = sample.getVals()->get(); + + for (const int uv_loop_index : *uv_loops) { + const uint32_t index = indices[uv_loop_index]; + *data_float2++ = make_float2(values[index][0], values[index][1]); + } + + attribute.data.add_data(data, time); +} + +/* Type of the function used to parse one time worth of data, either process_uvs or + * process_attribute_generic. */ +template<typename TRAIT> +using process_callback_type = void (*)(CachedData &, + CachedData::CachedAttribute &, + GeometryScope, + const typename ITypedGeomParam<TRAIT>::Sample &, + double); + +/* Main loop to process the attributes, this will look at the given param's TimeSampling and + * extract data based on which frame time is requested by the procedural and execute the callback + * for each of those requested time. */ +template<typename TRAIT> +static void read_attribute_loop(AlembicProcedural *proc, + CachedData &cache, + const ITypedGeomParam<TRAIT> ¶m, + process_callback_type<TRAIT> callback, + Progress &progress, + AttributeStandard std = ATTR_STD_NONE) +{ + const std::set<chrono_t> times = get_relevant_sample_times( + proc, *param.getTimeSampling(), param.getNumSamples()); + + if (times.empty()) { + return; + } + + std::string name = param.getName(); + + if (std == ATTR_STD_UV) { + std::string uv_source_name = Alembic::Abc::GetSourceName(param.getMetaData()); + + /* According to the convention, primary UVs should have had their name + * set using Alembic::Abc::SetSourceName, but you can't expect everyone + * to follow it! :) */ + if (!uv_source_name.empty()) { + name = uv_source_name; + } + } + + CachedData::CachedAttribute &attribute = cache.add_attribute(ustring(name), + *param.getTimeSampling()); + + using abc_type = typename TRAIT::value_type; + + attribute.data.set_time_sampling(*param.getTimeSampling()); + attribute.std = std; + attribute.type_desc = value_type_converter<abc_type>::type_desc; + + if (attribute.type_desc == TypeRGBA) { + attribute.element = ATTR_ELEMENT_CORNER_BYTE; + } + else { + if (param.getScope() == kVaryingScope || param.getScope() == kFacevaryingScope) { + attribute.element = ATTR_ELEMENT_CORNER; + } + else { + attribute.element = ATTR_ELEMENT_VERTEX; + } + } + + for (const chrono_t time : times) { + if (progress.get_cancel()) { + return; + } + + ISampleSelector iss = ISampleSelector(time); + typename ITypedGeomParam<TRAIT>::Sample sample; + param.getIndexed(sample, iss); + + if (!sample.valid()) { + continue; + } + + if (!sample.getVals()) { + attribute.data.add_no_data(time); + continue; + } + + /* Check whether we already loaded constant data. */ + if (attribute.data.size() != 0) { + if (param.isConstant()) { + return; + } + + const ArraySample::Key indices_key = sample.getIndices()->getKey(); + const ArraySample::Key values_key = sample.getVals()->getKey(); + + const bool is_same_as_last_time = (indices_key == attribute.data.key1 && + values_key == attribute.data.key2); + + attribute.data.key1 = indices_key; + attribute.data.key2 = values_key; + + if (is_same_as_last_time) { + attribute.data.reuse_data_for_last_time(time); + continue; + } + } + + callback(cache, attribute, param.getScope(), sample, time); + } +} + +/* Attributes requests. */ + +/* This structure is used to tell which ICoumpoundProperty the PropertyHeader comes from, as we + * need the parent when downcasting to the proper type. */ +struct PropHeaderAndParent { + const PropertyHeader *prop; + ICompoundProperty parent; +}; + +/* Parse the ICompoundProperty to look for properties whose names appear in the + * AttributeRequestSet. This also looks into any child ICompoundProperty of the given + * ICompoundProperty. If no property of the given name is found, let it be that way, Cycles will + * use a zero value for the missing attribute. */ +static void parse_requested_attributes_recursive(const AttributeRequestSet &requested_attributes, + const ICompoundProperty &arb_geom_params, + vector<PropHeaderAndParent> &requested_properties) +{ + for (const AttributeRequest &req : requested_attributes.requests) { + const PropertyHeader *property_header = arb_geom_params.getPropertyHeader(req.name.c_str()); + + if (!property_header) { + continue; + } + + requested_properties.push_back({property_header, arb_geom_params}); + } + + /* Look into children compound properties. */ + for (size_t i = 0; i < arb_geom_params.getNumProperties(); ++i) { + const PropertyHeader &property_header = arb_geom_params.getPropertyHeader(i); + + if (property_header.isCompound()) { + ICompoundProperty compound_property = ICompoundProperty(arb_geom_params, + property_header.getName()); + parse_requested_attributes_recursive( + requested_attributes, compound_property, requested_properties); + } + } +} + +/* Main entry point for parsing requested attributes from an ICompoundProperty, this exists so that + * we can simply return the list of properties instead of allocating it on the stack and passing it + * as a parameter. */ +static vector<PropHeaderAndParent> parse_requested_attributes( + const AttributeRequestSet &requested_attributes, const ICompoundProperty &arb_geom_params) +{ + vector<PropHeaderAndParent> requested_properties; + parse_requested_attributes_recursive( + requested_attributes, arb_geom_params, requested_properties); + return requested_properties; +} + +/* Read the attributes requested by the shaders from the archive. This will recursively find named + * attributes from the AttributeRequestSet in the ICompoundProperty and any of its compound child. + * The attributes are added to the CachedData's attribute list. For each attribute we will try to + * deduplicate data across consecutive frames. */ +void read_attributes(AlembicProcedural *proc, + CachedData &cache, + const ICompoundProperty &arb_geom_params, + const IV2fGeomParam &default_uvs_param, + const AttributeRequestSet &requested_attributes, + Progress &progress) +{ + if (default_uvs_param.valid()) { + /* Only the default UVs should be treated as the standard UV attribute. */ + read_attribute_loop(proc, cache, default_uvs_param, process_uvs, progress, ATTR_STD_UV); + } + + vector<PropHeaderAndParent> requested_properties = parse_requested_attributes( + requested_attributes, arb_geom_params); + + for (const PropHeaderAndParent &prop_and_parent : requested_properties) { + if (progress.get_cancel()) { + return; + } + + const PropertyHeader *prop = prop_and_parent.prop; + const ICompoundProperty &parent = prop_and_parent.parent; + + if (IBoolGeomParam::matches(*prop)) { + const IBoolGeomParam ¶m = IBoolGeomParam(parent, prop->getName()); + read_attribute_loop(proc, cache, param, process_attribute<BooleanTPTraits>, progress); + } + else if (IInt32GeomParam::matches(*prop)) { + const IInt32GeomParam ¶m = IInt32GeomParam(parent, prop->getName()); + read_attribute_loop(proc, cache, param, process_attribute<Int32TPTraits>, progress); + } + else if (IFloatGeomParam::matches(*prop)) { + const IFloatGeomParam ¶m = IFloatGeomParam(parent, prop->getName()); + read_attribute_loop(proc, cache, param, process_attribute<Float32TPTraits>, progress); + } + else if (IV2fGeomParam::matches(*prop)) { + const IV2fGeomParam ¶m = IV2fGeomParam(parent, prop->getName()); + if (Alembic::AbcGeom::isUV(*prop)) { + read_attribute_loop(proc, cache, param, process_uvs, progress); + } + else { + read_attribute_loop(proc, cache, param, process_attribute<V2fTPTraits>, progress); + } + } + else if (IV3fGeomParam::matches(*prop)) { + const IV3fGeomParam ¶m = IV3fGeomParam(parent, prop->getName()); + read_attribute_loop(proc, cache, param, process_attribute<V3fTPTraits>, progress); + } + else if (IN3fGeomParam::matches(*prop)) { + const IN3fGeomParam ¶m = IN3fGeomParam(parent, prop->getName()); + read_attribute_loop(proc, cache, param, process_attribute<N3fTPTraits>, progress); + } + else if (IC3fGeomParam::matches(*prop)) { + const IC3fGeomParam ¶m = IC3fGeomParam(parent, prop->getName()); + read_attribute_loop(proc, cache, param, process_attribute<C3fTPTraits>, progress); + } + else if (IC4fGeomParam::matches(*prop)) { + const IC4fGeomParam ¶m = IC4fGeomParam(parent, prop->getName()); + read_attribute_loop(proc, cache, param, process_attribute<C4fTPTraits>, progress); + } + } + + cache.invalidate_last_loaded_time(true); +} + +CCL_NAMESPACE_END + +#endif diff --git a/intern/cycles/render/alembic_read.h b/intern/cycles/render/alembic_read.h new file mode 100644 index 00000000000..9cc8622a1ba --- /dev/null +++ b/intern/cycles/render/alembic_read.h @@ -0,0 +1,134 @@ +/* + * Copyright 2021 Blender Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#ifdef WITH_ALEMBIC + +# include <Alembic/AbcCoreFactory/All.h> +# include <Alembic/AbcGeom/All.h> + +# include "util/util_vector.h" + +CCL_NAMESPACE_BEGIN + +class AlembicProcedural; +class AttributeRequestSet; +class Progress; +struct CachedData; + +/* Maps a FaceSet whose name matches that of a Shader to the index of said shader in the Geometry's + * used_shaders list. */ +struct FaceSetShaderIndexPair { + Alembic::AbcGeom::IFaceSet face_set; + int shader_index; +}; + +/* Data of an IPolyMeshSchema that we need to read. */ +struct PolyMeshSchemaData { + Alembic::AbcGeom::TimeSamplingPtr time_sampling; + size_t num_samples; + Alembic::AbcGeom::MeshTopologyVariance topology_variance; + + Alembic::AbcGeom::IP3fArrayProperty positions; + Alembic::AbcGeom::IInt32ArrayProperty face_indices; + Alembic::AbcGeom::IInt32ArrayProperty face_counts; + + Alembic::AbcGeom::IN3fGeomParam normals; + + vector<FaceSetShaderIndexPair> shader_face_sets; + + // Unsupported for now. + Alembic::AbcGeom::IV3fArrayProperty velocities; +}; + +void read_geometry_data(AlembicProcedural *proc, + CachedData &cached_data, + const PolyMeshSchemaData &data, + Progress &progress); + +/* Data of an ISubDSchema that we need to read. */ +struct SubDSchemaData { + Alembic::AbcGeom::TimeSamplingPtr time_sampling; + size_t num_samples; + Alembic::AbcGeom::MeshTopologyVariance topology_variance; + + Alembic::AbcGeom::IInt32ArrayProperty face_counts; + Alembic::AbcGeom::IInt32ArrayProperty face_indices; + Alembic::AbcGeom::IP3fArrayProperty positions; + + Alembic::AbcGeom::IInt32ArrayProperty crease_indices; + Alembic::AbcGeom::IInt32ArrayProperty crease_lengths; + Alembic::AbcGeom::IFloatArrayProperty crease_sharpnesses; + + vector<FaceSetShaderIndexPair> shader_face_sets; + + // Those are unsupported for now. + Alembic::AbcGeom::IInt32ArrayProperty corner_indices; + Alembic::AbcGeom::IFloatArrayProperty corner_sharpnesses; + Alembic::AbcGeom::IInt32Property face_varying_interpolate_boundary; + Alembic::AbcGeom::IInt32Property face_varying_propagate_corners; + Alembic::AbcGeom::IInt32Property interpolate_boundary; + Alembic::AbcGeom::IInt32ArrayProperty holes; + Alembic::AbcGeom::IStringProperty subdivision_scheme; + Alembic::AbcGeom::IV3fArrayProperty velocities; +}; + +void read_geometry_data(AlembicProcedural *proc, + CachedData &cached_data, + const SubDSchemaData &data, + Progress &progress); + +/* Data of a ICurvesSchema that we need to read. */ +struct CurvesSchemaData { + Alembic::AbcGeom::TimeSamplingPtr time_sampling; + size_t num_samples; + Alembic::AbcGeom::MeshTopologyVariance topology_variance; + + Alembic::AbcGeom::IP3fArrayProperty positions; + + Alembic::AbcGeom::IInt32ArrayProperty num_vertices; + + float default_radius; + float radius_scale; + + // Those are unsupported for now. + Alembic::AbcGeom::IV3fArrayProperty velocities; + // if this property is invalid then the weight for every point is 1 + Alembic::AbcGeom::IFloatArrayProperty position_weights; + Alembic::AbcGeom::IN3fGeomParam normals; + Alembic::AbcGeom::IFloatGeomParam widths; + Alembic::AbcGeom::IUcharArrayProperty orders; + Alembic::AbcGeom::IFloatArrayProperty knots; + + // TODO(@kevindietrich): type, basis, wrap +}; + +void read_geometry_data(AlembicProcedural *proc, + CachedData &cached_data, + const CurvesSchemaData &data, + Progress &progress); + +void read_attributes(AlembicProcedural *proc, + CachedData &cache, + const Alembic::AbcGeom::ICompoundProperty &arb_geom_params, + const Alembic::AbcGeom::IV2fGeomParam &default_uvs_param, + const AttributeRequestSet &requested_attributes, + Progress &progress); + +CCL_NAMESPACE_END + +#endif diff --git a/intern/cycles/render/background.cpp b/intern/cycles/render/background.cpp index f0a779da012..b925e755434 100644 --- a/intern/cycles/render/background.cpp +++ b/intern/cycles/render/background.cpp @@ -59,6 +59,7 @@ Background::Background() : Node(get_node_type()) Background::~Background() { + dereference_all_used_nodes(); } void Background::device_update(Device *device, DeviceScene *dscene, Scene *scene) diff --git a/intern/cycles/render/geometry.cpp b/intern/cycles/render/geometry.cpp index bd2b8164cbb..aac5af06e9f 100644 --- a/intern/cycles/render/geometry.cpp +++ b/intern/cycles/render/geometry.cpp @@ -79,6 +79,7 @@ Geometry::Geometry(const NodeType *node_type, const Type type) Geometry::~Geometry() { + dereference_all_used_nodes(); delete bvh; } diff --git a/intern/cycles/render/light.cpp b/intern/cycles/render/light.cpp index 858b177b69c..5290d68e75a 100644 --- a/intern/cycles/render/light.cpp +++ b/intern/cycles/render/light.cpp @@ -159,6 +159,7 @@ NODE_DEFINE(Light) Light::Light() : Node(get_node_type()) { + dereference_all_used_nodes(); } void Light::tag_update(Scene *scene) @@ -864,7 +865,7 @@ void LightManager::device_update_points(Device *, DeviceScene *dscene, Scene *sc const float min_spread_angle = 1.0f * M_PI_F / 180.0f; const float spread_angle = 0.5f * (M_PI_F - max(light->spread, min_spread_angle)); /* Normalization computed using: - * integrate cos(x) (1 - tan(x) * tan(a)) * sin(x) from x = a to pi/2. */ + * integrate cos(x) * (1 - tan(x) * tan(a)) * sin(x) from x = 0 to pi/2 - a. */ const float tan_spread = tanf(spread_angle); const float normalize_spread = 2.0f / (2.0f + (2.0f * spread_angle - M_PI_F) * tan_spread); diff --git a/intern/cycles/render/osl.cpp b/intern/cycles/render/osl.cpp index 53c67049571..9bd6e3a5e2d 100644 --- a/intern/cycles/render/osl.cpp +++ b/intern/cycles/render/osl.cpp @@ -91,10 +91,10 @@ void OSLShaderManager::reset(Scene * /*scene*/) shading_system_init(); } -void OSLShaderManager::device_update(Device *device, - DeviceScene *dscene, - Scene *scene, - Progress &progress) +void OSLShaderManager::device_update_specific(Device *device, + DeviceScene *dscene, + Scene *scene, + Progress &progress) { if (!need_update()) return; @@ -1149,7 +1149,7 @@ void OSLCompiler::compile(OSLGlobals *og, Shader *shader) shader->has_integrator_dependency = false; /* generate surface shader */ - if (shader->used && graph && output->input("Surface")->link) { + if (shader->reference_count() && graph && output->input("Surface")->link) { shader->osl_surface_ref = compile_type(shader, shader->graph, SHADER_TYPE_SURFACE); if (has_bump) @@ -1165,7 +1165,7 @@ void OSLCompiler::compile(OSLGlobals *og, Shader *shader) } /* generate volume shader */ - if (shader->used && graph && output->input("Volume")->link) { + if (shader->reference_count() && graph && output->input("Volume")->link) { shader->osl_volume_ref = compile_type(shader, shader->graph, SHADER_TYPE_VOLUME); shader->has_volume = true; } @@ -1173,7 +1173,7 @@ void OSLCompiler::compile(OSLGlobals *og, Shader *shader) shader->osl_volume_ref = OSL::ShaderGroupRef(); /* generate displacement shader */ - if (shader->used && graph && output->input("Displacement")->link) { + if (shader->reference_count() && graph && output->input("Displacement")->link) { shader->osl_displacement_ref = compile_type(shader, shader->graph, SHADER_TYPE_DISPLACEMENT); shader->has_displacement = true; } diff --git a/intern/cycles/render/osl.h b/intern/cycles/render/osl.h index ea2d0ca492c..c9eaf26e70a 100644 --- a/intern/cycles/render/osl.h +++ b/intern/cycles/render/osl.h @@ -79,7 +79,10 @@ class OSLShaderManager : public ShaderManager { return true; } - void device_update(Device *device, DeviceScene *dscene, Scene *scene, Progress &progress); + void device_update_specific(Device *device, + DeviceScene *dscene, + Scene *scene, + Progress &progress) override; void device_free(Device *device, DeviceScene *dscene, Scene *scene); /* osl compile and query */ diff --git a/intern/cycles/render/scene.cpp b/intern/cycles/render/scene.cpp index 38e8d9145dc..f753bb43c42 100644 --- a/intern/cycles/render/scene.cpp +++ b/intern/cycles/render/scene.cpp @@ -143,21 +143,27 @@ void Scene::free_memory(bool final) delete bvh; bvh = NULL; - foreach (Shader *s, shaders) - delete s; - /* delete procedurals before other types as they may hold pointers to those types */ + /* The order of deletion is important to make sure data is freed based on possible dependencies + * as the Nodes' reference counts are decremented in the destructors: + * + * - Procedurals can create and hold pointers to any other types. + * - Objects can hold pointers to Geometries and ParticleSystems + * - Lights and Geometries can hold pointers to Shaders. + * + * Similarly, we first delete all nodes and their associated device data, and then the managers + * and their associated device data. + */ foreach (Procedural *p, procedurals) delete p; - foreach (Geometry *g, geometry) - delete g; foreach (Object *o, objects) delete o; - foreach (Light *l, lights) - delete l; + foreach (Geometry *g, geometry) + delete g; foreach (ParticleSystem *p, particle_systems) delete p; + foreach (Light *l, lights) + delete l; - shaders.clear(); geometry.clear(); objects.clear(); lights.clear(); @@ -169,7 +175,25 @@ void Scene::free_memory(bool final) film->device_free(device, &dscene, this); background->device_free(device, &dscene); integrator->device_free(device, &dscene, true); + } + if (final) { + delete camera; + delete dicing_camera; + delete film; + delete background; + delete integrator; + } + + /* Delete Shaders after every other nodes to ensure that we do not try to decrement the reference + * count on some dangling pointer. */ + foreach (Shader *s, shaders) + delete s; + + shaders.clear(); + + /* Now that all nodes have been deleted, we can safely delete managers and device data. */ + if (device) { object_manager->device_free(device, &dscene, true); geometry_manager->device_free(device, &dscene, true); shader_manager->device_free(device, &dscene, this); @@ -189,11 +213,6 @@ void Scene::free_memory(bool final) if (final) { delete lookup_tables; - delete camera; - delete dicing_camera; - delete film; - delete background; - delete integrator; delete object_manager; delete geometry_manager; delete shader_manager; @@ -504,9 +523,6 @@ bool Scene::update(Progress &progress, bool &kernel_switch_needed) { /* update scene */ if (need_update()) { - /* Updated used shader tag so we know which features are need for the kernel. */ - shader_manager->update_shaders_used(this); - /* Update max_closures. */ KernelIntegrator *kintegrator = &dscene.data.integrator; if (params.background) { @@ -566,9 +582,6 @@ bool Scene::load_kernels(Progress &progress, bool lock_scene) return false; } - progress.add_skip_time(timer, false); - VLOG(1) << "Total time spent loading kernels: " << time_dt() - timer.get_start(); - kernels_loaded = true; loaded_kernel_features = requested_features; return true; @@ -587,7 +600,7 @@ int Scene::get_max_closure_count() int max_closures = 0; for (int i = 0; i < shaders.size(); i++) { Shader *shader = shaders[i]; - if (shader->used) { + if (shader->reference_count()) { int num_closures = shader->graph->get_num_closures(); max_closures = max(max_closures, num_closures); } @@ -748,9 +761,10 @@ template<> void Scene::delete_node_impl(ParticleSystem *node) particle_system_manager->tag_update(this); } -template<> void Scene::delete_node_impl(Shader * /*node*/) +template<> void Scene::delete_node_impl(Shader *shader) { /* don't delete unused shaders, not supported */ + shader->clear_reference_count(); } template<> void Scene::delete_node_impl(Procedural *node) @@ -817,9 +831,12 @@ template<> void Scene::delete_nodes(const set<ParticleSystem *> &nodes, const No particle_system_manager->tag_update(this); } -template<> void Scene::delete_nodes(const set<Shader *> & /*nodes*/, const NodeOwner * /*owner*/) +template<> void Scene::delete_nodes(const set<Shader *> &nodes, const NodeOwner * /*owner*/) { /* don't delete unused shaders, not supported */ + for (Shader *shader : nodes) { + shader->clear_reference_count(); + } } template<> void Scene::delete_nodes(const set<Procedural *> &nodes, const NodeOwner *owner) diff --git a/intern/cycles/render/shader.cpp b/intern/cycles/render/shader.cpp index 44a48cd2839..59b60904746 100644 --- a/intern/cycles/render/shader.cpp +++ b/intern/cycles/render/shader.cpp @@ -16,7 +16,6 @@ #include "device/device.h" -#include "render/alembic.h" #include "render/background.h" #include "render/camera.h" #include "render/colorspace.h" @@ -27,6 +26,7 @@ #include "render/nodes.h" #include "render/object.h" #include "render/osl.h" +#include "render/procedural.h" #include "render/scene.h" #include "render/shader.h" #include "render/svm.h" @@ -218,7 +218,6 @@ Shader::Shader() : Node(get_node_type()) displacement_method = DISPLACE_BUMP; id = -1; - used = false; need_update_uvs = true; need_update_attribute = true; @@ -382,8 +381,9 @@ void Shader::tag_used(Scene *scene) { /* if an unused shader suddenly gets used somewhere, it needs to be * recompiled because it was skipped for compilation before */ - if (!used) { + if (!reference_count()) { tag_modified(); + /* We do not reference here as the shader will be referenced when added to a socket. */ scene->shader_manager->tag_update(scene, ShaderManager::SHADER_MODIFIED); } } @@ -461,52 +461,28 @@ int ShaderManager::get_shader_id(Shader *shader, bool smooth) return id; } -void ShaderManager::update_shaders_used(Scene *scene) +void ShaderManager::device_update(Device *device, + DeviceScene *dscene, + Scene *scene, + Progress &progress) { if (!need_update()) { return; } - /* figure out which shaders are in use, so SVM/OSL can skip compiling them - * for speed and avoid loading image textures into memory */ uint id = 0; foreach (Shader *shader, scene->shaders) { - shader->used = false; shader->id = id++; } - scene->default_surface->used = true; - scene->default_light->used = true; - scene->default_background->used = true; - scene->default_empty->used = true; + /* Those shaders should always be compiled as they are used as fallback if a shader cannot be + * found, e.g. bad shader index for the triangle shaders on a Mesh. */ + assert(scene->default_surface->reference_count() != 0); + assert(scene->default_light->reference_count() != 0); + assert(scene->default_background->reference_count() != 0); + assert(scene->default_empty->reference_count() != 0); - if (scene->background->get_shader()) - scene->background->get_shader()->used = true; - -#ifdef WITH_ALEMBIC - foreach (Procedural *procedural, scene->procedurals) { - AlembicProcedural *abc_proc = static_cast<AlembicProcedural *>(procedural); - - foreach (Node *abc_node, abc_proc->get_objects()) { - AlembicObject *abc_object = static_cast<AlembicObject *>(abc_node); - - foreach (Node *node, abc_object->get_used_shaders()) { - Shader *shader = static_cast<Shader *>(node); - shader->used = true; - } - } - } -#endif - - foreach (Geometry *geom, scene->geometry) - foreach (Node *node, geom->get_used_shaders()) { - Shader *shader = static_cast<Shader *>(node); - shader->used = true; - } - - foreach (Light *light, scene->lights) - if (light->get_shader()) - const_cast<Shader *>(light->get_shader())->used = true; + device_update_specific(device, dscene, scene, progress); } void ShaderManager::device_update_common(Device *device, @@ -639,6 +615,7 @@ void ShaderManager::add_default(Scene *scene) Shader *shader = scene->create_node<Shader>(); shader->name = "default_surface"; shader->set_graph(graph); + shader->reference(); scene->default_surface = shader; shader->tag_update(scene); } @@ -657,6 +634,8 @@ void ShaderManager::add_default(Scene *scene) shader->set_graph(graph); scene->default_volume = shader; shader->tag_update(scene); + /* No default reference for the volume to avoid compiling volume kernels if there are no actual + * volumes in the scene */ } /* default light */ @@ -673,6 +652,7 @@ void ShaderManager::add_default(Scene *scene) Shader *shader = scene->create_node<Shader>(); shader->name = "default_light"; shader->set_graph(graph); + shader->reference(); scene->default_light = shader; shader->tag_update(scene); } @@ -684,6 +664,7 @@ void ShaderManager::add_default(Scene *scene) Shader *shader = scene->create_node<Shader>(); shader->name = "default_background"; shader->set_graph(graph); + shader->reference(); scene->default_background = shader; shader->tag_update(scene); } @@ -695,6 +676,7 @@ void ShaderManager::add_default(Scene *scene) Shader *shader = scene->create_node<Shader>(); shader->name = "default_empty"; shader->set_graph(graph); + shader->reference(); scene->default_empty = shader; shader->tag_update(scene); } @@ -735,7 +717,7 @@ void ShaderManager::get_requested_features(Scene *scene, requested_features->nodes_features = 0; for (int i = 0; i < scene->shaders.size(); i++) { Shader *shader = scene->shaders[i]; - if (!shader->used) { + if (!shader->reference_count()) { continue; } diff --git a/intern/cycles/render/shader.h b/intern/cycles/render/shader.h index f47d64f346c..50c8bed4669 100644 --- a/intern/cycles/render/shader.h +++ b/intern/cycles/render/shader.h @@ -132,7 +132,6 @@ class Shader : public Node { /* determined before compiling */ uint id; - bool used; #ifdef WITH_OSL /* osl shading state references */ @@ -187,10 +186,11 @@ class ShaderManager { } /* device update */ - virtual void device_update(Device *device, - DeviceScene *dscene, - Scene *scene, - Progress &progress) = 0; + void device_update(Device *device, DeviceScene *dscene, Scene *scene, Progress &progress); + virtual void device_update_specific(Device *device, + DeviceScene *dscene, + Scene *scene, + Progress &progress) = 0; virtual void device_free(Device *device, DeviceScene *dscene, Scene *scene) = 0; void device_update_common(Device *device, DeviceScene *dscene, Scene *scene, Progress &progress); @@ -208,7 +208,6 @@ class ShaderManager { static void add_default(Scene *scene); /* Selective nodes compilation. */ - void update_shaders_used(Scene *scene); void get_requested_features(Scene *scene, DeviceRequestedFeatures *requested_features); static void free_memory(); diff --git a/intern/cycles/render/svm.cpp b/intern/cycles/render/svm.cpp index fce604234f1..5c793c5c016 100644 --- a/intern/cycles/render/svm.cpp +++ b/intern/cycles/render/svm.cpp @@ -69,10 +69,10 @@ void SVMShaderManager::device_update_shader(Scene *scene, << summary.full_report(); } -void SVMShaderManager::device_update(Device *device, - DeviceScene *dscene, - Scene *scene, - Progress &progress) +void SVMShaderManager::device_update_specific(Device *device, + DeviceScene *dscene, + Scene *scene, + Progress &progress) { if (!need_update()) return; @@ -776,7 +776,7 @@ void SVMCompiler::compile_type(Shader *shader, ShaderGraph *graph, ShaderType ty add_node(NODE_ENTER_BUMP_EVAL, bump_state_offset); } - if (shader->used) { + if (shader->reference_count()) { CompilerState state(graph); if (clin->link) { bool generate = false; diff --git a/intern/cycles/render/svm.h b/intern/cycles/render/svm.h index a4ca68e1d8d..85b4b9c419f 100644 --- a/intern/cycles/render/svm.h +++ b/intern/cycles/render/svm.h @@ -46,7 +46,10 @@ class SVMShaderManager : public ShaderManager { void reset(Scene *scene); - void device_update(Device *device, DeviceScene *dscene, Scene *scene, Progress &progress); + void device_update_specific(Device *device, + DeviceScene *dscene, + Scene *scene, + Progress &progress) override; void device_free(Device *device, DeviceScene *dscene, Scene *scene); protected: diff --git a/intern/cycles/util/util_math_fast.h b/intern/cycles/util/util_math_fast.h index 107b36ce6cd..5ae56290f05 100644 --- a/intern/cycles/util/util_math_fast.h +++ b/intern/cycles/util/util_math_fast.h @@ -362,7 +362,7 @@ ccl_device float fast_atan2f(float y, float x) ccl_device float fast_log2f(float x) { /* NOTE: clamp to avoid special cases and make result "safe" from large - * negative values/nans. */ + * negative values/NAN's. */ x = clamp(x, FLT_MIN, FLT_MAX); unsigned bits = __float_as_uint(x); int exponent = (int)(bits >> 23) - 127; diff --git a/intern/ghost/GHOST_C-api.h b/intern/ghost/GHOST_C-api.h index d79111b742f..c776eb6b44c 100644 --- a/intern/ghost/GHOST_C-api.h +++ b/intern/ghost/GHOST_C-api.h @@ -276,7 +276,7 @@ extern int GHOST_GetFullScreen(GHOST_SystemHandle systemhandle); * wait (block) until the next event before returning. * \return Indication of the presence of events. */ -extern int GHOST_ProcessEvents(GHOST_SystemHandle systemhandle, int waitForEvent); +extern bool GHOST_ProcessEvents(GHOST_SystemHandle systemhandle, bool waitForEvent); /** * Retrieves events from the queue and send them to the event consumers. diff --git a/intern/ghost/GHOST_Types.h b/intern/ghost/GHOST_Types.h index bd7ba6657f0..7d819913efc 100644 --- a/intern/ghost/GHOST_Types.h +++ b/intern/ghost/GHOST_Types.h @@ -416,7 +416,10 @@ typedef enum { GHOST_kGrabNormal, /** Wrap the mouse location to prevent limiting screen bounds. */ GHOST_kGrabWrap, - /** Hide the mouse while grabbing and restore the original location on release (numbuts). */ + /** + * Hide the mouse while grabbing and restore the original location on release + * (used for number buttons and some other draggable UI elements). + */ GHOST_kGrabHide, } GHOST_TGrabCursorMode; diff --git a/intern/ghost/intern/GHOST_C-api.cpp b/intern/ghost/intern/GHOST_C-api.cpp index af65e1cd4d2..1d96354c504 100644 --- a/intern/ghost/intern/GHOST_C-api.cpp +++ b/intern/ghost/intern/GHOST_C-api.cpp @@ -248,11 +248,11 @@ int GHOST_GetFullScreen(GHOST_SystemHandle systemhandle) return (int)system->getFullScreen(); } -int GHOST_ProcessEvents(GHOST_SystemHandle systemhandle, int waitForEvent) +bool GHOST_ProcessEvents(GHOST_SystemHandle systemhandle, bool waitForEvent) { GHOST_ISystem *system = (GHOST_ISystem *)systemhandle; - return (int)system->processEvents(waitForEvent ? true : false); + return system->processEvents(waitForEvent); } void GHOST_DispatchEvents(GHOST_SystemHandle systemhandle) diff --git a/intern/ghost/intern/GHOST_DropTargetX11.cpp b/intern/ghost/intern/GHOST_DropTargetX11.cpp index 82ed9de230d..a62db31c952 100644 --- a/intern/ghost/intern/GHOST_DropTargetX11.cpp +++ b/intern/ghost/intern/GHOST_DropTargetX11.cpp @@ -112,8 +112,7 @@ GHOST_DropTargetX11::~GHOST_DropTargetX11() } } -/* based on a code from Saul Rennison - * http://stackoverflow.com/questions/2673207/c-c-url-decode-library */ +/* Based on: https://stackoverflow.com/a/2766963/432509 */ typedef enum DecodeState_e { STATE_SEARCH = 0, ///< searching for an ampersand to convert diff --git a/intern/ghost/intern/GHOST_SystemCocoa.mm b/intern/ghost/intern/GHOST_SystemCocoa.mm index 050c90097c5..6ddb7f031f5 100644 --- a/intern/ghost/intern/GHOST_SystemCocoa.mm +++ b/intern/ghost/intern/GHOST_SystemCocoa.mm @@ -1711,7 +1711,7 @@ GHOST_TSuccess GHOST_SystemCocoa::handleMouseEvent(void *eventPtr) dx = [event scrollingDeltaX]; dy = [event scrollingDeltaY]; - /* However, wacom tablet (intuos5) needs old deltas, + /* However, Wacom tablet (intuos5) needs old deltas, * it then has momentum and phase at zero. */ if (phase == NSEventPhaseNone && momentumPhase == NSEventPhaseNone) { dx = [event deltaX]; diff --git a/intern/ghost/test/gears/GHOST_C-Test.c b/intern/ghost/test/gears/GHOST_C-Test.c index 4283f990cfb..8cd1b5acb89 100644 --- a/intern/ghost/test/gears/GHOST_C-Test.c +++ b/intern/ghost/test/gears/GHOST_C-Test.c @@ -477,7 +477,7 @@ int main(int argc, char **argv) /* Enter main loop */ while (!sExitRequested) { - if (!GHOST_ProcessEvents(shSystem, 0)) { + if (!GHOST_ProcessEvents(shSystem, false)) { #ifdef WIN32 /* If there were no events, be nice to other applications */ Sleep(10); diff --git a/intern/ghost/test/multitest/MultiTest.c b/intern/ghost/test/multitest/MultiTest.c index 8c8858fc6d8..3b424f1ca89 100644 --- a/intern/ghost/test/multitest/MultiTest.c +++ b/intern/ghost/test/multitest/MultiTest.c @@ -926,7 +926,7 @@ void multitestapp_exit(MultiTestApp *app) void multitestapp_run(MultiTestApp *app) { while (!app->exit) { - GHOST_ProcessEvents(app->sys, 1); + GHOST_ProcessEvents(app->sys, true); GHOST_DispatchEvents(app->sys); } } diff --git a/release/scripts/modules/bl_i18n_utils/utils_spell_check.py b/release/scripts/modules/bl_i18n_utils/utils_spell_check.py index 6046e8e74d8..c310eee0b14 100644 --- a/release/scripts/modules/bl_i18n_utils/utils_spell_check.py +++ b/release/scripts/modules/bl_i18n_utils/utils_spell_check.py @@ -119,6 +119,7 @@ class SpellChecker: "dirtree", "editcurve", "editmesh", + "faceforward", "filebrowser", "filelist", "filename", "filenames", @@ -200,6 +201,7 @@ class SpellChecker: "selfcollision", "shadowbuffer", "shadowbuffers", "singletexture", + "softbox", "spellcheck", "spellchecking", "startup", "stateful", diff --git a/release/scripts/modules/rna_manual_reference.py b/release/scripts/modules/rna_manual_reference.py index b6fda8911cc..4478b0dc367 100644 --- a/release/scripts/modules/rna_manual_reference.py +++ b/release/scripts/modules/rna_manual_reference.py @@ -2115,4 +2115,9 @@ url_manual_mapping = ( ("bpy.ops.ed*", "interface/undo_redo.html#bpy-ops-ed"), ("bpy.ops.ui*", "interface/index.html#bpy-ops-ui"), ("bpy.ops.wm*", "interface/index.html#bpy-ops-wm"), + ("bpy.ops.file.pack_all", "blend/packed_data#pack-resources"), + ("bpy.ops.file.unpack_all", "blend/packed_data#unpack-resources"), + ("bpy.ops.file.autopack_toggle", "blend/packed_data#automatically-pack-resources"), + ("bpy.ops.file.pack_libraries", "blend/packed_data#pack-linked-libraries"), + ("bpy.ops.file.unpack_libraries", "blend/packed_data#unpack-linked-libraries"), ) diff --git a/release/scripts/modules/sys_info.py b/release/scripts/modules/sys_info.py index cb80529f0f3..5116e0f0088 100644 --- a/release/scripts/modules/sys_info.py +++ b/release/scripts/modules/sys_info.py @@ -23,6 +23,7 @@ def write_sysinfo(filepath): import sys + import platform import subprocess @@ -63,7 +64,7 @@ def write_sysinfo(filepath): )) output.write("build date: %s, %s\n" % (prepr(bpy.app.build_date), prepr(bpy.app.build_time))) - output.write("platform: %s\n" % prepr(bpy.app.build_platform)) + output.write("platform: %s\n" % prepr(platform.platform())) output.write("binary path: %s\n" % prepr(bpy.app.binary_path)) output.write("build cflags: %s\n" % prepr(bpy.app.build_cflags)) output.write("build cxxflags: %s\n" % prepr(bpy.app.build_cxxflags)) diff --git a/release/scripts/presets/keyconfig/keymap_data/blender_default.py b/release/scripts/presets/keyconfig/keymap_data/blender_default.py index 9404bfe327a..8d0a5806adc 100644 --- a/release/scripts/presets/keyconfig/keymap_data/blender_default.py +++ b/release/scripts/presets/keyconfig/keymap_data/blender_default.py @@ -2009,8 +2009,7 @@ def km_file_browser_main(params): ) items.extend([ - ("file.execute", {"type": 'LEFTMOUSE', "value": 'DOUBLE_CLICK'}, - {"properties": [("need_active", True)]}), + ("file.mouse_execute", {"type": 'LEFTMOUSE', "value": 'DOUBLE_CLICK'}, None), # Both .execute and .select are needed here. The former only works if # there's a file operator (i.e. not in regular editor mode) but is # needed to load files. The latter makes selection work if there's no diff --git a/release/scripts/presets/keyconfig/keymap_data/industry_compatible_data.py b/release/scripts/presets/keyconfig/keymap_data/industry_compatible_data.py index 91f153a0f42..31f2be57e38 100644 --- a/release/scripts/presets/keyconfig/keymap_data/industry_compatible_data.py +++ b/release/scripts/presets/keyconfig/keymap_data/industry_compatible_data.py @@ -1263,8 +1263,7 @@ def km_file_browser_main(params): ) items.extend([ - ("file.execute", {"type": 'LEFTMOUSE', "value": 'DOUBLE_CLICK'}, - {"properties": [("need_active", True)]}), + ("file.mouse_execute", {"type": 'LEFTMOUSE', "value": 'DOUBLE_CLICK'}, None), ("file.refresh", {"type": 'R', "value": 'PRESS', "ctrl": True}, None), ("file.select", {"type": 'LEFTMOUSE', "value": 'DOUBLE_CLICK'}, None), ("file.select", {"type": 'LEFTMOUSE', "value": 'CLICK'}, diff --git a/release/scripts/startup/bl_operators/screen_play_rendered_anim.py b/release/scripts/startup/bl_operators/screen_play_rendered_anim.py index 0946e89a647..6d60c58cc3a 100644 --- a/release/scripts/startup/bl_operators/screen_play_rendered_anim.py +++ b/release/scripts/startup/bl_operators/screen_play_rendered_anim.py @@ -130,6 +130,7 @@ class PlayRenderedAnim(Operator): "-s", str(frame_start), "-e", str(frame_end), "-j", str(scene.frame_step), + "-c", str(prefs.system.memory_cache_limit), file, ] cmd.extend(opts) diff --git a/release/scripts/startup/bl_ui/properties_paint_common.py b/release/scripts/startup/bl_ui/properties_paint_common.py index 22ef0fe77dd..4e3e4c2d58d 100644 --- a/release/scripts/startup/bl_ui/properties_paint_common.py +++ b/release/scripts/startup/bl_ui/properties_paint_common.py @@ -1225,7 +1225,7 @@ def brush_basic_gpencil_paint_settings(layout, context, brush, *, compact=False) row = layout.row(align=True) row.prop(gp_settings, "fill_factor") row = layout.row(align=True) - row.prop(gp_settings, "fill_leak", text="Leak Size") + row.prop(gp_settings, "dilate_pixels") row = layout.row(align=True) row.prop(brush, "size", text="Thickness") layout.use_property_split = use_property_split_prev diff --git a/release/scripts/startup/bl_ui/space_topbar.py b/release/scripts/startup/bl_ui/space_topbar.py index adab0b0c88a..5e68896a2a7 100644 --- a/release/scripts/startup/bl_ui/space_topbar.py +++ b/release/scripts/startup/bl_ui/space_topbar.py @@ -504,8 +504,6 @@ class TOPBAR_MT_file_external_data(Menu): icon = 'CHECKBOX_HLT' if bpy.data.use_autopack else 'CHECKBOX_DEHLT' layout.operator("file.autopack_toggle", icon=icon) - layout.separator() - pack_all = layout.row() pack_all.operator("file.pack_all") pack_all.active = not bpy.data.use_autopack @@ -516,8 +514,16 @@ class TOPBAR_MT_file_external_data(Menu): layout.separator() + layout.operator("file.pack_libraries") + layout.operator("file.unpack_libraries") + + layout.separator() + layout.operator("file.make_paths_relative") layout.operator("file.make_paths_absolute") + + layout.separator() + layout.operator("file.report_missing_files") layout.operator("file.find_missing_files") diff --git a/release/scripts/startup/bl_ui/space_view3d.py b/release/scripts/startup/bl_ui/space_view3d.py index 08f4ed9dd6c..d86e65d9f0d 100644 --- a/release/scripts/startup/bl_ui/space_view3d.py +++ b/release/scripts/startup/bl_ui/space_view3d.py @@ -938,7 +938,8 @@ class VIEW3D_MT_transform_base: layout.operator("transform.bend", text="Bend") layout.operator("transform.push_pull", text="Push/Pull") - if context.mode != 'OBJECT': + if context.mode in {'EDIT_MESH', 'EDIT_ARMATURE', 'EDIT_SURFACE', 'EDIT_CURVE', + 'EDIT_LATTICE', 'EDIT_METABALL'}: layout.operator("transform.vertex_warp", text="Warp") layout.operator_context = 'EXEC_REGION_WIN' layout.operator("transform.vertex_random", text="Randomize").offset = 0.1 diff --git a/release/scripts/startup/bl_ui/space_view3d_toolbar.py b/release/scripts/startup/bl_ui/space_view3d_toolbar.py index ab012a6f2ef..08d581bfa24 100644 --- a/release/scripts/startup/bl_ui/space_view3d_toolbar.py +++ b/release/scripts/startup/bl_ui/space_view3d_toolbar.py @@ -1468,6 +1468,9 @@ class VIEW3D_PT_tools_grease_pencil_brush_advanced(View3DPanel, Panel): row.prop(gp_settings, "show_fill_extend", text="", icon='GRID') col.separator() + col.prop(gp_settings, "fill_leak", text="Leak Size") + + col.separator() col.prop(gp_settings, "fill_simplify_level", text="Simplify") if gp_settings.fill_draw_mode != 'STROKE': col = layout.column(align=False, heading="Ignore Transparent") diff --git a/release/scripts/startup/nodeitems_builtins.py b/release/scripts/startup/nodeitems_builtins.py index 4584f799b95..379fa145c92 100644 --- a/release/scripts/startup/nodeitems_builtins.py +++ b/release/scripts/startup/nodeitems_builtins.py @@ -505,6 +505,9 @@ geometry_node_categories = [ NodeItem("ShaderNodeSeparateRGB"), NodeItem("ShaderNodeCombineRGB"), ]), + GeometryNodeCategory("GEO_CURVE", "Curve", items=[ + NodeItem("GeometryNodeCurveToMesh"), + ]), GeometryNodeCategory("GEO_GEOMETRY", "Geometry", items=[ NodeItem("GeometryNodeBoundBox"), NodeItem("GeometryNodeTransform"), diff --git a/source/blender/blenkernel/BKE_attribute_math.hh b/source/blender/blenkernel/BKE_attribute_math.hh index fc7498951f9..b0d32b20d44 100644 --- a/source/blender/blenkernel/BKE_attribute_math.hh +++ b/source/blender/blenkernel/BKE_attribute_math.hh @@ -14,6 +14,8 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +#pragma once + #include "BLI_array.hh" #include "BLI_color.hh" #include "BLI_float2.hh" diff --git a/source/blender/blenkernel/BKE_blender_version.h b/source/blender/blenkernel/BKE_blender_version.h index 61489aa7494..0bab980cfcd 100644 --- a/source/blender/blenkernel/BKE_blender_version.h +++ b/source/blender/blenkernel/BKE_blender_version.h @@ -39,7 +39,7 @@ extern "C" { /* Blender file format version. */ #define BLENDER_FILE_VERSION BLENDER_VERSION -#define BLENDER_FILE_SUBVERSION 0 +#define BLENDER_FILE_SUBVERSION 1 /* Minimum Blender version that supports reading file written with the current * version. Older Blender versions will test this and show a warning if the file diff --git a/source/blender/blenkernel/BKE_geometry_set.h b/source/blender/blenkernel/BKE_geometry_set.h index c00310408af..5f6a9ec7b91 100644 --- a/source/blender/blenkernel/BKE_geometry_set.h +++ b/source/blender/blenkernel/BKE_geometry_set.h @@ -36,25 +36,13 @@ typedef enum GeometryComponentType { GEO_COMPONENT_TYPE_POINT_CLOUD = 1, GEO_COMPONENT_TYPE_INSTANCES = 2, GEO_COMPONENT_TYPE_VOLUME = 3, + GEO_COMPONENT_TYPE_CURVE = 4, } GeometryComponentType; void BKE_geometry_set_free(struct GeometrySet *geometry_set); bool BKE_geometry_set_has_instances(const struct GeometrySet *geometry_set); -typedef enum InstancedDataType { - INSTANCE_DATA_TYPE_OBJECT = 0, - INSTANCE_DATA_TYPE_COLLECTION = 1, -} InstancedDataType; - -typedef struct InstancedData { - InstancedDataType type; - union { - struct Object *object; - struct Collection *collection; - } data; -} InstancedData; - #ifdef __cplusplus } #endif diff --git a/source/blender/blenkernel/BKE_geometry_set.hh b/source/blender/blenkernel/BKE_geometry_set.hh index 89cdef34297..106af8172d1 100644 --- a/source/blender/blenkernel/BKE_geometry_set.hh +++ b/source/blender/blenkernel/BKE_geometry_set.hh @@ -30,6 +30,7 @@ #include "BLI_map.hh" #include "BLI_set.hh" #include "BLI_user_counter.hh" +#include "BLI_vector_set.hh" #include "BKE_attribute_access.hh" #include "BKE_geometry_set.h" @@ -39,6 +40,7 @@ struct Mesh; struct Object; struct PointCloud; struct Volume; +class CurveEval; enum class GeometryOwnershipType { /* The geometry is owned. This implies that it can be changed. */ @@ -363,18 +365,25 @@ struct GeometrySet { Mesh *mesh, GeometryOwnershipType ownership = GeometryOwnershipType::Owned); static GeometrySet create_with_pointcloud( PointCloud *pointcloud, GeometryOwnershipType ownership = GeometryOwnershipType::Owned); + static GeometrySet create_with_curve( + CurveEval *curve, GeometryOwnershipType ownership = GeometryOwnershipType::Owned); /* Utility methods for access. */ bool has_mesh() const; bool has_pointcloud() const; bool has_instances() const; bool has_volume() const; + bool has_curve() const; + const Mesh *get_mesh_for_read() const; const PointCloud *get_pointcloud_for_read() const; const Volume *get_volume_for_read() const; + const CurveEval *get_curve_for_read() const; + Mesh *get_mesh_for_write(); PointCloud *get_pointcloud_for_write(); Volume *get_volume_for_write(); + CurveEval *get_curve_for_write(); /* Utility methods for replacement. */ void replace_mesh(Mesh *mesh, GeometryOwnershipType ownership = GeometryOwnershipType::Owned); @@ -382,6 +391,8 @@ struct GeometrySet { GeometryOwnershipType ownership = GeometryOwnershipType::Owned); void replace_volume(Volume *volume, GeometryOwnershipType ownership = GeometryOwnershipType::Owned); + void replace_curve(CurveEval *curve, + GeometryOwnershipType ownership = GeometryOwnershipType::Owned); }; /** A geometry component that can store a mesh. */ @@ -463,12 +474,113 @@ class PointCloudComponent : public GeometryComponent { const blender::bke::ComponentAttributeProviders *get_attribute_providers() const final; }; +/** A geometry component that stores curve data, in other words, a group of splines. */ +class CurveComponent : public GeometryComponent { + private: + CurveEval *curve_ = nullptr; + GeometryOwnershipType ownership_ = GeometryOwnershipType::Owned; + + public: + CurveComponent(); + ~CurveComponent(); + GeometryComponent *copy() const override; + + void clear(); + bool has_curve() const; + void replace(CurveEval *curve, GeometryOwnershipType ownership = GeometryOwnershipType::Owned); + CurveEval *release(); + + const CurveEval *get_for_read() const; + CurveEval *get_for_write(); + + int attribute_domain_size(const AttributeDomain domain) const final; + + bool is_empty() const final; + + bool owns_direct_data() const override; + void ensure_owns_direct_data() override; + + static constexpr inline GeometryComponentType static_type = GEO_COMPONENT_TYPE_CURVE; + + private: + const blender::bke::ComponentAttributeProviders *get_attribute_providers() const final; +}; + +class InstanceReference { + public: + enum class Type { + /** + * An empty instance. This allows an `InstanceReference` to be default constructed without + * being in an invalid state. There might also be other use cases that we haven't explored much + * yet (such as changing the instance later on, and "disabling" some instances). + */ + None, + Object, + Collection, + }; + + private: + Type type_ = Type::None; + /** Depending on the type this is either null, an Object or Collection pointer. */ + void *data_ = nullptr; + + public: + InstanceReference() = default; + + InstanceReference(Object &object) : type_(Type::Object), data_(&object) + { + } + + InstanceReference(Collection &collection) : type_(Type::Collection), data_(&collection) + { + } + + Type type() const + { + return type_; + } + + Object &object() const + { + BLI_assert(type_ == Type::Object); + return *(Object *)data_; + } + + Collection &collection() const + { + BLI_assert(type_ == Type::Collection); + return *(Collection *)data_; + } + + uint64_t hash() const + { + return blender::get_default_hash(data_); + } + + friend bool operator==(const InstanceReference &a, const InstanceReference &b) + { + return a.data_ == b.data_; + } +}; + /** A geometry component that stores instances. */ class InstancesComponent : public GeometryComponent { private: - blender::Vector<blender::float4x4> transforms_; - blender::Vector<int> ids_; - blender::Vector<InstancedData> instanced_data_; + /** + * Indexed set containing information about the data that is instanced. + * Actual instances store an index ("handle") into this set. + */ + blender::VectorSet<InstanceReference> references_; + + /** Index into `references_`. Determines what data is instanced. */ + blender::Vector<int> instance_reference_handles_; + /** Transformation of the instances. */ + blender::Vector<blender::float4x4> instance_transforms_; + /** + * IDs of the instances. They are used for consistency over multiple frames for things like + * motion blur. + */ + blender::Vector<int> instance_ids_; /* These almost unique ids are generated based on `ids_`, which might not contain unique ids at * all. They are *almost* unique, because under certain very unlikely circumstances, they are not @@ -483,14 +595,20 @@ class InstancesComponent : public GeometryComponent { GeometryComponent *copy() const override; void clear(); - void add_instance(Object *object, blender::float4x4 transform, const int id = -1); - void add_instance(Collection *collection, blender::float4x4 transform, const int id = -1); - void add_instance(InstancedData data, blender::float4x4 transform, const int id = -1); - - blender::Span<InstancedData> instanced_data() const; - blender::Span<blender::float4x4> transforms() const; - blender::Span<int> ids() const; - blender::MutableSpan<blender::float4x4> transforms(); + + void reserve(int min_capacity); + + int add_reference(InstanceReference reference); + void add_instance(int instance_handle, const blender::float4x4 &transform, const int id = -1); + + blender::Span<InstanceReference> references() const; + + blender::Span<int> instance_reference_handles() const; + blender::MutableSpan<blender::float4x4> instance_transforms(); + blender::Span<blender::float4x4> instance_transforms() const; + blender::MutableSpan<int> instance_ids(); + blender::Span<int> instance_ids() const; + int instances_amount() const; blender::Span<int> almost_unique_ids() const; diff --git a/source/blender/blenkernel/BKE_lib_query.h b/source/blender/blenkernel/BKE_lib_query.h index 4e781aea9d3..9c49514e7b8 100644 --- a/source/blender/blenkernel/BKE_lib_query.h +++ b/source/blender/blenkernel/BKE_lib_query.h @@ -70,12 +70,15 @@ enum { /** That ID is used as library override's reference by its owner. */ IDWALK_CB_OVERRIDE_LIBRARY_REFERENCE = (1 << 5), + /** That ID pointer is not overridable. */ + IDWALK_CB_OVERRIDE_LIBRARY_NOT_OVERRIDABLE = (1 << 6), + /** * Indicates that this is an internal runtime ID pointer, like e.g. `ID.newid` or `ID.original`. * \note Those should be ignored in most cases, and won't be processed/generated anyway unless * `IDWALK_DO_INTERNAL_RUNTIME_POINTERS` option is enabled. */ - IDWALK_CB_INTERNAL = (1 << 6), + IDWALK_CB_INTERNAL = (1 << 7), /** * This ID usage is fully refcounted. diff --git a/source/blender/blenkernel/BKE_nla.h b/source/blender/blenkernel/BKE_nla.h index 16d48024d07..af238fda659 100644 --- a/source/blender/blenkernel/BKE_nla.h +++ b/source/blender/blenkernel/BKE_nla.h @@ -58,7 +58,14 @@ struct NlaTrack *BKE_nlatrack_copy(struct Main *bmain, struct NlaTrack *nlt, const bool use_same_actions, const int flag); -void BKE_nla_tracks_copy(struct Main *bmain, ListBase *dst, ListBase *src, const int flag); +void BKE_nla_tracks_copy(struct Main *bmain, ListBase *dst, const ListBase *src, const int flag); + +/* Copy NLA tracks from #adt_source to #adt_dest, and update the active track/strip pointers to + * point at those copies. */ +void BKE_nla_tracks_copy_from_adt(struct Main *bmain, + struct AnimData *adt_dest, + const struct AnimData *adt_source, + int flag); struct NlaTrack *BKE_nlatrack_add(struct AnimData *adt, struct NlaTrack *prev, diff --git a/source/blender/blenkernel/BKE_node.h b/source/blender/blenkernel/BKE_node.h index 819c148f0ad..bcab456b36e 100644 --- a/source/blender/blenkernel/BKE_node.h +++ b/source/blender/blenkernel/BKE_node.h @@ -1418,6 +1418,7 @@ int ntreeTexExecTree(struct bNodeTree *ntree, #define GEO_NODE_BOUNDING_BOX 1042 #define GEO_NODE_SWITCH 1043 #define GEO_NODE_ATTRIBUTE_TRANSFER 1044 +#define GEO_NODE_CURVE_TO_MESH 1045 /** \} */ diff --git a/source/blender/blenkernel/BKE_spline.hh b/source/blender/blenkernel/BKE_spline.hh new file mode 100644 index 00000000000..098abf6de65 --- /dev/null +++ b/source/blender/blenkernel/BKE_spline.hh @@ -0,0 +1,478 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#pragma once + +/** \file + * \ingroup bke + */ + +#include <mutex> + +#include "FN_generic_virtual_array.hh" + +#include "BLI_float3.hh" +#include "BLI_float4x4.hh" +#include "BLI_vector.hh" + +#include "BKE_attribute_math.hh" + +struct Curve; + +class Spline; +using SplinePtr = std::unique_ptr<Spline>; + +/** + * A spline is an abstraction of a single branch-less curve section, its evaluation methods, + * and data. The spline data itself is just control points and a set of attributes by the set + * of "evaluated" data is often used instead. + * + * Any derived class of Spline has to manage two things: + * 1. Interpolating arbitrary attribute data from the control points to evaluated points. + * 2. Evaluating the positions based on the stored control point data. + * + * Beyond that, everything is the base class's responsibility, with minor exceptions. Further + * evaluation happens in a layer on top of the evaluated points generated by the derived types. + * + * There are a few methods to evaluate a spline: + * 1. #evaluated_positions and #interpolate_to_evaluated_points give data at the initial + * evaluated points, depending on the resolution. + * 2. #lookup_evaluated_factor and #lookup_evaluated_factor are meant for one-off lookups + * along the length of a curve. + * + * Commonly used evaluated data is stored in caches on the spline itself so that operations on + * splines don't need to worry about taking ownership of evaluated data when they don't need to. + */ +class Spline { + public: + enum class Type { + Bezier, + NURBS, + Poly, + }; + + protected: + Type type_; + bool is_cyclic_ = false; + + public: + enum NormalCalculationMode { + ZUp, + Minimum, + Tangent, + }; + /* Only #Zup is supported at the moment. */ + NormalCalculationMode normal_mode; + + protected: + /** Direction of the spline at each evaluated point. */ + mutable blender::Vector<blender::float3> evaluated_tangents_cache_; + mutable std::mutex tangent_cache_mutex_; + mutable bool tangent_cache_dirty_ = true; + + /** Normal direction vectors for each evaluated point. */ + mutable blender::Vector<blender::float3> evaluated_normals_cache_; + mutable std::mutex normal_cache_mutex_; + mutable bool normal_cache_dirty_ = true; + + /** Accumulated lengths along the evaluated points. */ + mutable blender::Vector<float> evaluated_lengths_cache_; + mutable std::mutex length_cache_mutex_; + mutable bool length_cache_dirty_ = true; + + public: + virtual ~Spline() = default; + Spline(const Type type) : type_(type) + { + } + Spline(Spline &other) + : type_(other.type_), is_cyclic_(other.is_cyclic_), normal_mode(other.normal_mode) + { + } + + virtual SplinePtr copy() const = 0; + + Spline::Type type() const; + + /** Return the number of control points. */ + virtual int size() const = 0; + int segments_size() const; + bool is_cyclic() const; + void set_cyclic(const bool value); + + virtual blender::MutableSpan<blender::float3> positions() = 0; + virtual blender::Span<blender::float3> positions() const = 0; + virtual blender::MutableSpan<float> radii() = 0; + virtual blender::Span<float> radii() const = 0; + virtual blender::MutableSpan<float> tilts() = 0; + virtual blender::Span<float> tilts() const = 0; + + virtual void translate(const blender::float3 &translation); + virtual void transform(const blender::float4x4 &matrix); + + /** + * Mark all caches for re-computation. This must be called after any operation that would + * change the generated positions, tangents, normals, mapping, etc. of the evaluated points. + */ + virtual void mark_cache_invalid() = 0; + virtual int evaluated_points_size() const = 0; + int evaluated_edges_size() const; + + float length() const; + + virtual blender::Span<blender::float3> evaluated_positions() const = 0; + + blender::Span<float> evaluated_lengths() const; + blender::Span<blender::float3> evaluated_tangents() const; + blender::Span<blender::float3> evaluated_normals() const; + + void bounds_min_max(blender::float3 &min, blender::float3 &max, const bool use_evaluated) const; + + struct LookupResult { + /** + * The index of the evaluated point before the result location. In other words, the index of + * the edge that the result lies on. If the sampled factor/length is the very end of the + * spline, this will be the second to last index, if it's the very beginning, this will be 0. + */ + int evaluated_index; + /** + * The index of the evaluated point after the result location, accounting for wrapping when + * the spline is cyclic. If the sampled factor/length is the very end of the spline, this will + * be the last index (#evaluated_points_size - 1). + */ + int next_evaluated_index; + /** + * The portion of the way from the evaluated point at #evaluated_index to the next point. + * If the sampled factor/length is the very end of the spline, this will be the 1.0f + */ + float factor; + }; + LookupResult lookup_evaluated_factor(const float factor) const; + LookupResult lookup_evaluated_length(const float length) const; + + /** + * Interpolate a virtual array of data with the size of the number of control points to the + * evaluated points. For poly splines, the lifetime of the returned virtual array must not + * exceed the lifetime of the input data. + */ + virtual blender::fn::GVArrayPtr interpolate_to_evaluated_points( + const blender::fn::GVArray &source_data) const = 0; + + protected: + virtual void correct_end_tangents() const = 0; +}; + +/** + * A Bézier spline is made up of a many curve segments, possibly achieving continuity of curvature + * by constraining the alignment of curve handles. Evaluation stores the positions and a map of + * factors and indices in a list of floats, which is then used to interpolate any other data. + */ +class BezierSpline final : public Spline { + public: + enum class HandleType { + /** The handle can be moved anywhere, and doesn't influence the point's other handle. */ + Free, + /** The location is automatically calculated to be smooth. */ + Auto, + /** The location is calculated to point to the next/previous control point. */ + Vector, + /** The location is constrained to point in the opposite direction as the other handle. */ + Align, + }; + + private: + blender::Vector<HandleType> handle_types_left_; + blender::Vector<blender::float3> handle_positions_left_; + blender::Vector<blender::float3> positions_; + blender::Vector<HandleType> handle_types_right_; + blender::Vector<blender::float3> handle_positions_right_; + blender::Vector<float> radii_; + blender::Vector<float> tilts_; + int resolution_; + + /** Start index in evaluated points array for every control point. */ + mutable blender::Vector<int> offset_cache_; + mutable std::mutex offset_cache_mutex_; + mutable bool offset_cache_dirty_ = true; + + /** Cache of evaluated positions. */ + mutable blender::Vector<blender::float3> evaluated_position_cache_; + mutable std::mutex position_cache_mutex_; + mutable bool position_cache_dirty_ = true; + + /** Cache of "index factors" based calculated from the evaluated positions. */ + mutable blender::Vector<float> evaluated_mapping_cache_; + mutable std::mutex mapping_cache_mutex_; + mutable bool mapping_cache_dirty_ = true; + + public: + virtual SplinePtr copy() const final; + BezierSpline() : Spline(Type::Bezier) + { + } + BezierSpline(const BezierSpline &other) + : Spline((Spline &)other), + handle_types_left_(other.handle_types_left_), + handle_positions_left_(other.handle_positions_left_), + positions_(other.positions_), + handle_types_right_(other.handle_types_right_), + handle_positions_right_(other.handle_positions_right_), + radii_(other.radii_), + tilts_(other.tilts_), + resolution_(other.resolution_) + { + } + + int size() const final; + int resolution() const; + void set_resolution(const int value); + + void add_point(const blender::float3 position, + const HandleType handle_type_start, + const blender::float3 handle_position_start, + const HandleType handle_type_end, + const blender::float3 handle_position_end, + const float radius, + const float tilt); + + blender::MutableSpan<blender::float3> positions() final; + blender::Span<blender::float3> positions() const final; + blender::MutableSpan<float> radii() final; + blender::Span<float> radii() const final; + blender::MutableSpan<float> tilts() final; + blender::Span<float> tilts() const final; + blender::Span<HandleType> handle_types_left() const; + blender::MutableSpan<HandleType> handle_types_left(); + blender::Span<blender::float3> handle_positions_left() const; + blender::MutableSpan<blender::float3> handle_positions_left(); + blender::Span<HandleType> handle_types_right() const; + blender::MutableSpan<HandleType> handle_types_right(); + blender::Span<blender::float3> handle_positions_right() const; + blender::MutableSpan<blender::float3> handle_positions_right(); + + void translate(const blender::float3 &translation) override; + void transform(const blender::float4x4 &matrix) override; + + bool point_is_sharp(const int index) const; + + void mark_cache_invalid() final; + int evaluated_points_size() const final; + + blender::Span<int> control_point_offsets() const; + blender::Span<float> evaluated_mappings() const; + blender::Span<blender::float3> evaluated_positions() const final; + struct InterpolationData { + int control_point_index; + int next_control_point_index; + /** + * Linear interpolation weight between the two indices, from 0 to 1. + * Higher means next control point. + */ + float factor; + }; + InterpolationData interpolation_data_from_index_factor(const float index_factor) const; + + virtual blender::fn::GVArrayPtr interpolate_to_evaluated_points( + const blender::fn::GVArray &source_data) const; + + private: + void correct_end_tangents() const final; + bool segment_is_vector(const int start_index) const; + void evaluate_bezier_segment(const int index, + const int next_index, + blender::MutableSpan<blender::float3> positions) const; + blender::Array<int> evaluated_point_offsets() const; +}; + +/** + * Data for Non-Uniform Rational B-Splines. The mapping from control points to evaluated points is + * influenced by a vector of knots, weights for each point, and the order of the spline. Every + * mapping of data to evaluated points is handled the same way, but the positions are cached in + * the spline. + */ +class NURBSpline final : public Spline { + public: + enum class KnotsMode { + Normal, + EndPoint, + Bezier, + }; + KnotsMode knots_mode; + + struct BasisCache { + /** The influence at each control point `i + #start_index`. */ + blender::Vector<float> weights; + /** + * An offset for the start of #weights: the first control point index with a non-zero weight. + */ + int start_index; + }; + + private: + blender::Vector<blender::float3> positions_; + blender::Vector<float> radii_; + blender::Vector<float> tilts_; + blender::Vector<float> weights_; + int resolution_; + /** + * Defines the number of nearby control points that influence a given evaluated point. Higher + * orders give smoother results. The number of control points must be greater than or equal to + * this value. + */ + uint8_t order_; + + /** + * Determines where and how the control points affect the evaluated points. The length should + * always be the value returned by #knots_size(), and each value should be greater than or equal + * to the previous. Only invalidated when a point is added or removed. + */ + mutable blender::Vector<float> knots_; + mutable std::mutex knots_mutex_; + mutable bool knots_dirty_ = true; + + /** Cache of control point influences on each evaluated point. */ + mutable blender::Vector<BasisCache> basis_cache_; + mutable std::mutex basis_cache_mutex_; + mutable bool basis_cache_dirty_ = true; + + /** + * Cache of position data calculated from the basis cache. Though it is interpolated + * in the same way as any other attribute, it is stored to save unnecessary recalculation. + */ + mutable blender::Vector<blender::float3> evaluated_position_cache_; + mutable std::mutex position_cache_mutex_; + mutable bool position_cache_dirty_ = true; + + public: + SplinePtr copy() const final; + NURBSpline() : Spline(Type::NURBS) + { + } + NURBSpline(const NURBSpline &other) + : Spline((Spline &)other), + knots_mode(other.knots_mode), + positions_(other.positions_), + radii_(other.radii_), + tilts_(other.tilts_), + weights_(other.weights_), + resolution_(other.resolution_), + order_(other.order_) + { + } + + int size() const final; + int resolution() const; + void set_resolution(const int value); + uint8_t order() const; + void set_order(const uint8_t value); + + void add_point(const blender::float3 position, + const float radius, + const float tilt, + const float weight); + + bool check_valid_size_and_order() const; + int knots_size() const; + + blender::MutableSpan<blender::float3> positions() final; + blender::Span<blender::float3> positions() const final; + blender::MutableSpan<float> radii() final; + blender::Span<float> radii() const final; + blender::MutableSpan<float> tilts() final; + blender::Span<float> tilts() const final; + blender::Span<float> knots() const; + + blender::MutableSpan<float> weights(); + blender::Span<float> weights() const; + + void mark_cache_invalid() final; + int evaluated_points_size() const final; + + blender::Span<blender::float3> evaluated_positions() const final; + + blender::fn::GVArrayPtr interpolate_to_evaluated_points( + const blender::fn::GVArray &source_data) const final; + + protected: + void correct_end_tangents() const final; + void calculate_knots() const; + void calculate_basis_cache() const; +}; + +/** + * A Poly spline is like a bezier spline with a resolution of one. The main reason to distinguish + * the two is for reduced complexity and increased performance, since interpolating data to control + * points does not change it. + */ +class PolySpline final : public Spline { + public: + blender::Vector<blender::float3> positions_; + blender::Vector<float> radii_; + blender::Vector<float> tilts_; + + private: + public: + SplinePtr copy() const final; + PolySpline() : Spline(Type::Poly) + { + } + PolySpline(const PolySpline &other) + : Spline((Spline &)other), + positions_(other.positions_), + radii_(other.radii_), + tilts_(other.tilts_) + { + } + + int size() const final; + + void add_point(const blender::float3 position, const float radius, const float tilt); + + blender::MutableSpan<blender::float3> positions() final; + blender::Span<blender::float3> positions() const final; + blender::MutableSpan<float> radii() final; + blender::Span<float> radii() const final; + blender::MutableSpan<float> tilts() final; + blender::Span<float> tilts() const final; + + void mark_cache_invalid() final; + int evaluated_points_size() const final; + + blender::Span<blender::float3> evaluated_positions() const final; + + blender::fn::GVArrayPtr interpolate_to_evaluated_points( + const blender::fn::GVArray &source_data) const final; + + protected: + void correct_end_tangents() const final; +}; + +/** + * A #CurveEval corresponds to the #Curve object data. The name is different for clarity, since + * more of the data is stored in the splines, but also just to be different than the name in DNA. + */ +class CurveEval { + public: + blender::Vector<SplinePtr> splines; + + CurveEval *copy(); + + void translate(const blender::float3 &translation); + void transform(const blender::float4x4 &matrix); + void bounds_min_max(blender::float3 &min, blender::float3 &max, const bool use_evaluated) const; +}; + +std::unique_ptr<CurveEval> curve_eval_from_dna_curve(const Curve &curve); diff --git a/source/blender/blenkernel/CMakeLists.txt b/source/blender/blenkernel/CMakeLists.txt index 69df29527dd..0944bbed2f3 100644 --- a/source/blender/blenkernel/CMakeLists.txt +++ b/source/blender/blenkernel/CMakeLists.txt @@ -112,6 +112,7 @@ set(SRC intern/curve_convert.c intern/curve_decimate.c intern/curve_deform.c + intern/curve_eval.cc intern/curveprofile.c intern/customdata.c intern/customdata_file.c @@ -133,6 +134,7 @@ set(SRC intern/fmodifier.c intern/font.c intern/freestyle.c + intern/geometry_component_curve.cc intern/geometry_component_instances.cc intern/geometry_component_mesh.cc intern/geometry_component_pointcloud.cc @@ -241,6 +243,10 @@ set(SRC intern/softbody.c intern/sound.c intern/speaker.c + intern/spline_base.cc + intern/spline_bezier.cc + intern/spline_nurbs.cc + intern/spline_poly.cc intern/studiolight.c intern/subdiv.c intern/subdiv_ccg.c @@ -322,6 +328,7 @@ set(SRC BKE_customdata_file.h BKE_data_transfer.h BKE_deform.h + BKE_spline.hh BKE_displist.h BKE_displist_tangent.h BKE_duplilist.h diff --git a/source/blender/blenkernel/intern/DerivedMesh.cc b/source/blender/blenkernel/intern/DerivedMesh.cc index 473bd9d9d7e..c63447c6cdd 100644 --- a/source/blender/blenkernel/intern/DerivedMesh.cc +++ b/source/blender/blenkernel/intern/DerivedMesh.cc @@ -1862,9 +1862,11 @@ static void editbmesh_calc_modifiers(struct Depsgraph *depsgraph, BKE_id_free(nullptr, mesh_orco); } - /* Ensure normals calculation below is correct. */ - BLI_assert((mesh_input->flag & ME_AUTOSMOOTH) == (mesh_final->flag & ME_AUTOSMOOTH)); - BLI_assert(mesh_input->smoothresh == mesh_final->smoothresh); + /* Ensure normals calculation below is correct (normal settings have transferred properly). + * However, nodes modifiers might create meshes from scratch or transfer meshes from other + * objects with different settings, and in general it doesn't make sense to guarantee that + * the settings are the same as the original mesh. If necessary, this could become a modifier + * type flag. */ BLI_assert(mesh_input->smoothresh == mesh_cage->smoothresh); /* Compute normals. */ diff --git a/source/blender/blenkernel/intern/anim_data.c b/source/blender/blenkernel/intern/anim_data.c index 447ed8fbe14..68de3e93a8e 100644 --- a/source/blender/blenkernel/intern/anim_data.c +++ b/source/blender/blenkernel/intern/anim_data.c @@ -354,7 +354,7 @@ AnimData *BKE_animdata_copy(Main *bmain, AnimData *adt, const int flag) } /* duplicate NLA data */ - BKE_nla_tracks_copy(bmain, &dadt->nla_tracks, &adt->nla_tracks, flag); + BKE_nla_tracks_copy_from_adt(bmain, dadt, adt, flag); /* duplicate drivers (F-Curves) */ BKE_fcurves_copy(&dadt->drivers, &adt->drivers); diff --git a/source/blender/blenkernel/intern/attribute_access.cc b/source/blender/blenkernel/intern/attribute_access.cc index 5b2f588959f..c24630c5666 100644 --- a/source/blender/blenkernel/intern/attribute_access.cc +++ b/source/blender/blenkernel/intern/attribute_access.cc @@ -143,10 +143,8 @@ CustomDataType attribute_data_type_highest_complexity(Span<CustomDataType> data_ static int attribute_domain_priority(const AttributeDomain domain) { switch (domain) { -#if 0 case ATTR_DOMAIN_CURVE: return 0; -#endif case ATTR_DOMAIN_FACE: return 1; case ATTR_DOMAIN_EDGE: diff --git a/source/blender/blenkernel/intern/brush.c b/source/blender/blenkernel/intern/brush.c index ef567044282..20c5af0efb6 100644 --- a/source/blender/blenkernel/intern/brush.c +++ b/source/blender/blenkernel/intern/brush.c @@ -989,6 +989,7 @@ void BKE_gpencil_brush_preset_set(Main *bmain, Brush *brush, const short type) brush->gpencil_settings->draw_smoothfac = 0.1f; brush->gpencil_settings->draw_smoothlvl = 1; brush->gpencil_settings->draw_subdivide = 1; + brush->gpencil_settings->dilate_pixels = 1; brush->gpencil_settings->flag |= GP_BRUSH_FILL_SHOW_EXTENDLINES; diff --git a/source/blender/blenkernel/intern/curve_eval.cc b/source/blender/blenkernel/intern/curve_eval.cc new file mode 100644 index 00000000000..4c63c7f05ee --- /dev/null +++ b/source/blender/blenkernel/intern/curve_eval.cc @@ -0,0 +1,184 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "BLI_array.hh" +#include "BLI_listbase.h" +#include "BLI_span.hh" + +#include "DNA_curve_types.h" + +#include "BKE_curve.h" +#include "BKE_spline.hh" + +using blender::float3; +using blender::float4x4; +using blender::Span; + +CurveEval *CurveEval::copy() +{ + CurveEval *new_curve = new CurveEval(); + + for (SplinePtr &spline : this->splines) { + new_curve->splines.append(spline->copy()); + } + + return new_curve; +} + +void CurveEval::translate(const float3 &translation) +{ + for (SplinePtr &spline : this->splines) { + spline->translate(translation); + spline->mark_cache_invalid(); + } +} + +void CurveEval::transform(const float4x4 &matrix) +{ + for (SplinePtr &spline : this->splines) { + spline->transform(matrix); + } +} + +void CurveEval::bounds_min_max(float3 &min, float3 &max, const bool use_evaluated) const +{ + for (const SplinePtr &spline : this->splines) { + spline->bounds_min_max(min, max, use_evaluated); + } +} + +static BezierSpline::HandleType handle_type_from_dna_bezt(const eBezTriple_Handle dna_handle_type) +{ + switch (dna_handle_type) { + case HD_FREE: + return BezierSpline::HandleType::Free; + case HD_AUTO: + return BezierSpline::HandleType::Auto; + case HD_VECT: + return BezierSpline::HandleType::Vector; + case HD_ALIGN: + return BezierSpline::HandleType::Align; + case HD_AUTO_ANIM: + return BezierSpline::HandleType::Auto; + case HD_ALIGN_DOUBLESIDE: + return BezierSpline::HandleType::Align; + } + BLI_assert_unreachable(); + return BezierSpline::HandleType::Auto; +} + +static Spline::NormalCalculationMode normal_mode_from_dna_curve(const int twist_mode) +{ + switch (twist_mode) { + case CU_TWIST_Z_UP: + return Spline::NormalCalculationMode::ZUp; + case CU_TWIST_MINIMUM: + return Spline::NormalCalculationMode::Minimum; + case CU_TWIST_TANGENT: + return Spline::NormalCalculationMode::Tangent; + } + BLI_assert_unreachable(); + return Spline::NormalCalculationMode::Minimum; +} + +static NURBSpline::KnotsMode knots_mode_from_dna_nurb(const short flag) +{ + switch (flag & (CU_NURB_ENDPOINT | CU_NURB_BEZIER)) { + case CU_NURB_ENDPOINT: + return NURBSpline::KnotsMode::EndPoint; + case CU_NURB_BEZIER: + return NURBSpline::KnotsMode::Bezier; + default: + return NURBSpline::KnotsMode::Normal; + } + + BLI_assert_unreachable(); + return NURBSpline::KnotsMode::Normal; +} + +std::unique_ptr<CurveEval> curve_eval_from_dna_curve(const Curve &dna_curve) +{ + std::unique_ptr<CurveEval> curve = std::make_unique<CurveEval>(); + + const ListBase *nurbs = BKE_curve_nurbs_get(&const_cast<Curve &>(dna_curve)); + + curve->splines.reserve(BLI_listbase_count(nurbs)); + + /* TODO: Optimize by reserving the correct points size. */ + LISTBASE_FOREACH (const Nurb *, nurb, nurbs) { + switch (nurb->type) { + case CU_BEZIER: { + std::unique_ptr<BezierSpline> spline = std::make_unique<BezierSpline>(); + spline->set_resolution(nurb->resolu); + spline->set_cyclic(nurb->flagu & CU_NURB_CYCLIC); + + for (const BezTriple &bezt : Span(nurb->bezt, nurb->pntsu)) { + spline->add_point(bezt.vec[1], + handle_type_from_dna_bezt((eBezTriple_Handle)bezt.h1), + bezt.vec[0], + handle_type_from_dna_bezt((eBezTriple_Handle)bezt.h2), + bezt.vec[2], + bezt.radius, + bezt.tilt); + } + + curve->splines.append(std::move(spline)); + break; + } + case CU_NURBS: { + std::unique_ptr<NURBSpline> spline = std::make_unique<NURBSpline>(); + spline->set_resolution(nurb->resolu); + spline->set_cyclic(nurb->flagu & CU_NURB_CYCLIC); + spline->set_order(nurb->orderu); + spline->knots_mode = knots_mode_from_dna_nurb(nurb->flagu); + + for (const BPoint &bp : Span(nurb->bp, nurb->pntsu)) { + spline->add_point(bp.vec, bp.radius, bp.tilt, bp.vec[3]); + } + + curve->splines.append(std::move(spline)); + break; + } + case CU_POLY: { + std::unique_ptr<PolySpline> spline = std::make_unique<PolySpline>(); + spline->set_cyclic(nurb->flagu & CU_NURB_CYCLIC); + + for (const BPoint &bp : Span(nurb->bp, nurb->pntsu)) { + spline->add_point(bp.vec, bp.radius, bp.tilt); + } + + curve->splines.append(std::move(spline)); + break; + } + default: { + BLI_assert_unreachable(); + break; + } + } + } + + /* Note: Normal mode is stored separately in each spline to facilitate combining splines + * from multiple curve objects, where the value may be different. */ + const Spline::NormalCalculationMode normal_mode = normal_mode_from_dna_curve( + dna_curve.twist_mode); + for (SplinePtr &spline : curve->splines) { + spline->normal_mode = normal_mode; + } + + return curve; +} + +/** \} */ diff --git a/source/blender/blenkernel/intern/geometry_component_curve.cc b/source/blender/blenkernel/intern/geometry_component_curve.cc new file mode 100644 index 00000000000..0490d577b88 --- /dev/null +++ b/source/blender/blenkernel/intern/geometry_component_curve.cc @@ -0,0 +1,299 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "BKE_spline.hh" + +#include "BKE_attribute_access.hh" +#include "BKE_attribute_math.hh" +#include "BKE_geometry_set.hh" + +#include "attribute_access_intern.hh" + +/* -------------------------------------------------------------------- */ +/** \name Geometry Component Implementation + * \{ */ + +CurveComponent::CurveComponent() : GeometryComponent(GEO_COMPONENT_TYPE_CURVE) +{ +} + +CurveComponent::~CurveComponent() +{ + this->clear(); +} + +GeometryComponent *CurveComponent::copy() const +{ + CurveComponent *new_component = new CurveComponent(); + if (curve_ != nullptr) { + new_component->curve_ = curve_->copy(); + new_component->ownership_ = GeometryOwnershipType::Owned; + } + return new_component; +} + +void CurveComponent::clear() +{ + BLI_assert(this->is_mutable()); + if (curve_ != nullptr) { + if (ownership_ == GeometryOwnershipType::Owned) { + delete curve_; + } + curve_ = nullptr; + } +} + +bool CurveComponent::has_curve() const +{ + return curve_ != nullptr; +} + +/* Clear the component and replace it with the new curve. */ +void CurveComponent::replace(CurveEval *curve, GeometryOwnershipType ownership) +{ + BLI_assert(this->is_mutable()); + this->clear(); + curve_ = curve; + ownership_ = ownership; +} + +CurveEval *CurveComponent::release() +{ + BLI_assert(this->is_mutable()); + CurveEval *curve = curve_; + curve_ = nullptr; + return curve; +} + +const CurveEval *CurveComponent::get_for_read() const +{ + return curve_; +} + +CurveEval *CurveComponent::get_for_write() +{ + BLI_assert(this->is_mutable()); + if (ownership_ == GeometryOwnershipType::ReadOnly) { + curve_ = curve_->copy(); + ownership_ = GeometryOwnershipType::Owned; + } + return curve_; +} + +bool CurveComponent::is_empty() const +{ + return curve_ == nullptr; +} + +bool CurveComponent::owns_direct_data() const +{ + return ownership_ == GeometryOwnershipType::Owned; +} + +void CurveComponent::ensure_owns_direct_data() +{ + BLI_assert(this->is_mutable()); + if (ownership_ != GeometryOwnershipType::Owned) { + curve_ = curve_->copy(); + ownership_ = GeometryOwnershipType::Owned; + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Attribute Access + * \{ */ + +int CurveComponent::attribute_domain_size(const AttributeDomain domain) const +{ + if (curve_ == nullptr) { + return 0; + } + if (domain == ATTR_DOMAIN_POINT) { + int total = 0; + for (const SplinePtr &spline : curve_->splines) { + total += spline->size(); + } + return total; + } + if (domain == ATTR_DOMAIN_CURVE) { + return curve_->splines.size(); + } + return 0; +} + +namespace blender::bke { + +class BuiltinSplineAttributeProvider final : public BuiltinAttributeProvider { + using AsReadAttribute = GVArrayPtr (*)(const CurveEval &data); + using AsWriteAttribute = GVMutableArrayPtr (*)(CurveEval &data); + using UpdateOnWrite = void (*)(Spline &spline); + const AsReadAttribute as_read_attribute_; + const AsWriteAttribute as_write_attribute_; + + public: + BuiltinSplineAttributeProvider(std::string attribute_name, + const CustomDataType attribute_type, + const WritableEnum writable, + const AsReadAttribute as_read_attribute, + const AsWriteAttribute as_write_attribute) + : BuiltinAttributeProvider(std::move(attribute_name), + ATTR_DOMAIN_CURVE, + attribute_type, + BuiltinAttributeProvider::NonCreatable, + writable, + BuiltinAttributeProvider::NonDeletable), + as_read_attribute_(as_read_attribute), + as_write_attribute_(as_write_attribute) + { + } + + GVArrayPtr try_get_for_read(const GeometryComponent &component) const final + { + const CurveComponent &curve_component = static_cast<const CurveComponent &>(component); + const CurveEval *curve = curve_component.get_for_read(); + if (curve == nullptr) { + return {}; + } + + return as_read_attribute_(*curve); + } + + GVMutableArrayPtr try_get_for_write(GeometryComponent &component) const final + { + if (writable_ != Writable) { + return {}; + } + CurveComponent &curve_component = static_cast<CurveComponent &>(component); + CurveEval *curve = curve_component.get_for_write(); + if (curve == nullptr) { + return {}; + } + + return as_write_attribute_(*curve); + } + + bool try_delete(GeometryComponent &UNUSED(component)) const final + { + return false; + } + + bool try_create(GeometryComponent &UNUSED(component), + const AttributeInit &UNUSED(initializer)) const final + { + return false; + } + + bool exists(const GeometryComponent &component) const final + { + return component.attribute_domain_size(ATTR_DOMAIN_CURVE) != 0; + } +}; + +static int get_spline_resolution(const SplinePtr &spline) +{ + if (const BezierSpline *bezier_spline = dynamic_cast<const BezierSpline *>(spline.get())) { + return bezier_spline->resolution(); + } + if (const NURBSpline *nurb_spline = dynamic_cast<const NURBSpline *>(spline.get())) { + return nurb_spline->resolution(); + } + return 1; +} + +static void set_spline_resolution(SplinePtr &spline, const int resolution) +{ + if (BezierSpline *bezier_spline = dynamic_cast<BezierSpline *>(spline.get())) { + bezier_spline->set_resolution(std::max(resolution, 1)); + } + if (NURBSpline *nurb_spline = dynamic_cast<NURBSpline *>(spline.get())) { + nurb_spline->set_resolution(std::max(resolution, 1)); + } +} + +static GVArrayPtr make_resolution_read_attribute(const CurveEval &curve) +{ + return std::make_unique<fn::GVArray_For_DerivedSpan<SplinePtr, int, get_spline_resolution>>( + curve.splines.as_span()); +} + +static GVMutableArrayPtr make_resolution_write_attribute(CurveEval &curve) +{ + return std::make_unique<fn::GVMutableArray_For_DerivedSpan<SplinePtr, + int, + get_spline_resolution, + set_spline_resolution>>( + curve.splines.as_mutable_span()); +} + +static bool get_cyclic_value(const SplinePtr &spline) +{ + return spline->is_cyclic(); +} + +static void set_cyclic_value(SplinePtr &spline, const bool value) +{ + if (spline->is_cyclic() != value) { + spline->set_cyclic(value); + spline->mark_cache_invalid(); + } +} + +static GVArrayPtr make_cyclic_read_attribute(const CurveEval &curve) +{ + return std::make_unique<fn::GVArray_For_DerivedSpan<SplinePtr, bool, get_cyclic_value>>( + curve.splines.as_span()); +} + +static GVMutableArrayPtr make_cyclic_write_attribute(CurveEval &curve) +{ + return std::make_unique< + fn::GVMutableArray_For_DerivedSpan<SplinePtr, bool, get_cyclic_value, set_cyclic_value>>( + curve.splines.as_mutable_span()); +} + +/** + * In this function all the attribute providers for a curve component are created. Most data + * in this function is statically allocated, because it does not change over time. + */ +static ComponentAttributeProviders create_attribute_providers_for_curve() +{ + static BuiltinSplineAttributeProvider resolution("resolution", + CD_PROP_INT32, + BuiltinAttributeProvider::Writable, + make_resolution_read_attribute, + make_resolution_write_attribute); + + static BuiltinSplineAttributeProvider cyclic("cyclic", + CD_PROP_BOOL, + BuiltinAttributeProvider::Writable, + make_cyclic_read_attribute, + make_cyclic_write_attribute); + + return ComponentAttributeProviders({&resolution, &cyclic}, {}); +} + +} // namespace blender::bke + +const blender::bke::ComponentAttributeProviders *CurveComponent::get_attribute_providers() const +{ + static blender::bke::ComponentAttributeProviders providers = + blender::bke::create_attribute_providers_for_curve(); + return &providers; +} + +/** \} */ diff --git a/source/blender/blenkernel/intern/geometry_component_instances.cc b/source/blender/blenkernel/intern/geometry_component_instances.cc index feb30e8575a..55025ed5ac9 100644 --- a/source/blender/blenkernel/intern/geometry_component_instances.cc +++ b/source/blender/blenkernel/intern/geometry_component_instances.cc @@ -42,72 +42,86 @@ InstancesComponent::InstancesComponent() : GeometryComponent(GEO_COMPONENT_TYPE_ GeometryComponent *InstancesComponent::copy() const { InstancesComponent *new_component = new InstancesComponent(); - new_component->transforms_ = transforms_; - new_component->instanced_data_ = instanced_data_; - new_component->ids_ = ids_; + new_component->instance_reference_handles_ = instance_reference_handles_; + new_component->instance_transforms_ = instance_transforms_; + new_component->instance_ids_ = instance_ids_; + new_component->references_ = references_; return new_component; } -void InstancesComponent::clear() +void InstancesComponent::reserve(int min_capacity) { - instanced_data_.clear(); - transforms_.clear(); - ids_.clear(); + instance_reference_handles_.reserve(min_capacity); + instance_transforms_.reserve(min_capacity); + instance_ids_.reserve(min_capacity); } -void InstancesComponent::add_instance(Object *object, float4x4 transform, const int id) +void InstancesComponent::clear() { - InstancedData data; - data.type = INSTANCE_DATA_TYPE_OBJECT; - data.data.object = object; - this->add_instance(data, transform, id); + instance_reference_handles_.clear(); + instance_transforms_.clear(); + instance_ids_.clear(); + + references_.clear(); } -void InstancesComponent::add_instance(Collection *collection, float4x4 transform, const int id) +void InstancesComponent::add_instance(const int instance_handle, + const float4x4 &transform, + const int id) { - InstancedData data; - data.type = INSTANCE_DATA_TYPE_COLLECTION; - data.data.collection = collection; - this->add_instance(data, transform, id); + BLI_assert(instance_handle >= 0); + BLI_assert(instance_handle < references_.size()); + instance_reference_handles_.append(instance_handle); + instance_transforms_.append(transform); + instance_ids_.append(id); } -void InstancesComponent::add_instance(InstancedData data, float4x4 transform, const int id) +blender::Span<int> InstancesComponent::instance_reference_handles() const { - instanced_data_.append(data); - transforms_.append(transform); - ids_.append(id); + return instance_reference_handles_; } -Span<InstancedData> InstancesComponent::instanced_data() const +blender::MutableSpan<blender::float4x4> InstancesComponent::instance_transforms() +{ + return instance_transforms_; +} +blender::Span<blender::float4x4> InstancesComponent::instance_transforms() const { - return instanced_data_; + return instance_transforms_; } -Span<float4x4> InstancesComponent::transforms() const +blender::MutableSpan<int> InstancesComponent::instance_ids() +{ + return instance_ids_; +} +blender::Span<int> InstancesComponent::instance_ids() const { - return transforms_; + return instance_ids_; } -Span<int> InstancesComponent::ids() const +/** + * Returns a handle for the given reference. + * If the reference exists already, the handle of the existing reference is returned. + * Otherwise a new handle is added. + */ +int InstancesComponent::add_reference(InstanceReference reference) { - return ids_; + return references_.index_of_or_add_as(reference); } -MutableSpan<float4x4> InstancesComponent::transforms() +blender::Span<InstanceReference> InstancesComponent::references() const { - return transforms_; + return references_; } int InstancesComponent::instances_amount() const { - const int size = instanced_data_.size(); - BLI_assert(transforms_.size() == size); - return size; + return instance_transforms_.size(); } bool InstancesComponent::is_empty() const { - return transforms_.size() == 0; + return this->instance_reference_handles_.size() == 0; } bool InstancesComponent::owns_direct_data() const @@ -178,8 +192,8 @@ static blender::Array<int> generate_unique_instance_ids(Span<int> original_ids) blender::Span<int> InstancesComponent::almost_unique_ids() const { std::lock_guard lock(almost_unique_ids_mutex_); - if (almost_unique_ids_.size() != ids_.size()) { - almost_unique_ids_ = generate_unique_instance_ids(ids_); + if (almost_unique_ids_.size() != instance_ids_.size()) { + almost_unique_ids_ = generate_unique_instance_ids(instance_ids_); } return almost_unique_ids_; } diff --git a/source/blender/blenkernel/intern/geometry_set.cc b/source/blender/blenkernel/intern/geometry_set.cc index 6ae78343fea..3d85118deee 100644 --- a/source/blender/blenkernel/intern/geometry_set.cc +++ b/source/blender/blenkernel/intern/geometry_set.cc @@ -24,6 +24,7 @@ #include "BKE_mesh_wrapper.h" #include "BKE_modifier.h" #include "BKE_pointcloud.h" +#include "BKE_spline.hh" #include "BKE_volume.h" #include "DNA_collection_types.h" @@ -60,6 +61,8 @@ GeometryComponent *GeometryComponent::create(GeometryComponentType component_typ return new InstancesComponent(); case GEO_COMPONENT_TYPE_VOLUME: return new VolumeComponent(); + case GEO_COMPONENT_TYPE_CURVE: + return new CurveComponent(); } BLI_assert_unreachable(); return nullptr; @@ -182,6 +185,11 @@ void GeometrySet::compute_boundbox_without_instances(float3 *r_min, float3 *r_ma if (volume != nullptr) { BKE_volume_min_max(volume, *r_min, *r_max); } + const CurveEval *curve = this->get_curve_for_read(); + if (curve != nullptr) { + /* Using the evaluated positions is somewhat arbitrary, but it is probably expected. */ + curve->bounds_min_max(*r_min, *r_max, true); + } } std::ostream &operator<<(std::ostream &stream, const GeometrySet &geometry_set) @@ -252,6 +260,13 @@ const Volume *GeometrySet::get_volume_for_read() const return (component == nullptr) ? nullptr : component->get_for_read(); } +/* Returns a read-only curve or null. */ +const CurveEval *GeometrySet::get_curve_for_read() const +{ + const CurveComponent *component = this->get_component_for_read<CurveComponent>(); + return (component == nullptr) ? nullptr : component->get_for_read(); +} + /* Returns true when the geometry set has a point cloud component that has a point cloud. */ bool GeometrySet::has_pointcloud() const { @@ -273,6 +288,13 @@ bool GeometrySet::has_volume() const return component != nullptr && component->has_volume(); } +/* Returns true when the geometry set has a curve component that has a curve. */ +bool GeometrySet::has_curve() const +{ + const CurveComponent *component = this->get_component_for_read<CurveComponent>(); + return component != nullptr && component->has_curve(); +} + /* Create a new geometry set that only contains the given mesh. */ GeometrySet GeometrySet::create_with_mesh(Mesh *mesh, GeometryOwnershipType ownership) { @@ -292,6 +314,15 @@ GeometrySet GeometrySet::create_with_pointcloud(PointCloud *pointcloud, return geometry_set; } +/* Create a new geometry set that only contains the given curve. */ +GeometrySet GeometrySet::create_with_curve(CurveEval *curve, GeometryOwnershipType ownership) +{ + GeometrySet geometry_set; + CurveComponent &component = geometry_set.get_component_for_write<CurveComponent>(); + component.replace(curve, ownership); + return geometry_set; +} + /* Clear the existing mesh and replace it with the given one. */ void GeometrySet::replace_mesh(Mesh *mesh, GeometryOwnershipType ownership) { @@ -299,6 +330,13 @@ void GeometrySet::replace_mesh(Mesh *mesh, GeometryOwnershipType ownership) component.replace(mesh, ownership); } +/* Clear the existing curve and replace it with the given one. */ +void GeometrySet::replace_curve(CurveEval *curve, GeometryOwnershipType ownership) +{ + CurveComponent &component = this->get_component_for_write<CurveComponent>(); + component.replace(curve, ownership); +} + /* Clear the existing point cloud and replace with the given one. */ void GeometrySet::replace_pointcloud(PointCloud *pointcloud, GeometryOwnershipType ownership) { @@ -334,6 +372,13 @@ Volume *GeometrySet::get_volume_for_write() return component.get_for_write(); } +/* Returns a mutable curve or null. No ownership is transferred. */ +CurveEval *GeometrySet::get_curve_for_write() +{ + CurveComponent &component = this->get_component_for_write<CurveComponent>(); + return component.get_for_write(); +} + /** \} */ /* -------------------------------------------------------------------- */ diff --git a/source/blender/blenkernel/intern/geometry_set_instances.cc b/source/blender/blenkernel/intern/geometry_set_instances.cc index 47db5d1f901..a792e268d5c 100644 --- a/source/blender/blenkernel/intern/geometry_set_instances.cc +++ b/source/blender/blenkernel/intern/geometry_set_instances.cc @@ -19,6 +19,7 @@ #include "BKE_mesh_wrapper.h" #include "BKE_modifier.h" #include "BKE_pointcloud.h" +#include "BKE_spline.hh" #include "DNA_collection_types.h" #include "DNA_mesh_types.h" @@ -50,6 +51,16 @@ static void add_final_mesh_as_geometry_component(const Object &object, GeometryS } } +static void add_curve_data_as_geometry_component(const Object &object, GeometrySet &geometry_set) +{ + BLI_assert(object.type == OB_CURVE); + if (object.data != nullptr) { + std::unique_ptr<CurveEval> curve = curve_eval_from_dna_curve(*(Curve *)object.data); + CurveComponent &curve_component = geometry_set.get_component_for_write<CurveComponent>(); + curve_component.replace(curve.release(), GeometryOwnershipType::Owned); + } +} + /** * \note This doesn't extract instances from the "dupli" system for non-geometry-nodes instances. */ @@ -73,6 +84,9 @@ static GeometrySet object_get_geometry_set_for_read(const Object &object) if (object.type == OB_MESH) { add_final_mesh_as_geometry_component(object, geometry_set); } + else if (object.type == OB_CURVE) { + add_curve_data_as_geometry_component(object, geometry_set); + } /* TODO: Cover the case of point-clouds without modifiers-- they may not be covered by the * #geometry_set_eval case above. */ @@ -135,21 +149,28 @@ static void geometry_set_collect_recursive(const GeometrySet &geometry_set, const InstancesComponent &instances_component = *geometry_set.get_component_for_read<InstancesComponent>(); - Span<float4x4> transforms = instances_component.transforms(); - Span<InstancedData> instances = instances_component.instanced_data(); - for (const int i : instances.index_range()) { - const InstancedData &data = instances[i]; + Span<float4x4> transforms = instances_component.instance_transforms(); + Span<int> handles = instances_component.instance_reference_handles(); + Span<InstanceReference> references = instances_component.references(); + for (const int i : transforms.index_range()) { + const InstanceReference &reference = references[handles[i]]; const float4x4 instance_transform = transform * transforms[i]; - if (data.type == INSTANCE_DATA_TYPE_OBJECT) { - BLI_assert(data.data.object != nullptr); - const Object &object = *data.data.object; - geometry_set_collect_recursive_object(object, instance_transform, r_sets); - } - else if (data.type == INSTANCE_DATA_TYPE_COLLECTION) { - BLI_assert(data.data.collection != nullptr); - const Collection &collection = *data.data.collection; - geometry_set_collect_recursive_collection_instance(collection, instance_transform, r_sets); + switch (reference.type()) { + case InstanceReference::Type::Object: { + Object &object = reference.object(); + geometry_set_collect_recursive_object(object, instance_transform, r_sets); + break; + } + case InstanceReference::Type::Collection: { + Collection &collection = reference.collection(); + geometry_set_collect_recursive_collection_instance( + collection, instance_transform, r_sets); + break; + } + case InstanceReference::Type::None: { + break; + } } } } @@ -253,19 +274,24 @@ static bool instances_attribute_foreach_recursive(const GeometrySet &geometry_se return true; } - for (const InstancedData &data : instances_component->instanced_data()) { - if (data.type == INSTANCE_DATA_TYPE_OBJECT) { - BLI_assert(data.data.object != nullptr); - const Object &object = *data.data.object; - if (!object_instance_attribute_foreach(object, callback, limit, count)) { - return false; + for (const InstanceReference &reference : instances_component->references()) { + switch (reference.type()) { + case InstanceReference::Type::Object: { + const Object &object = reference.object(); + if (!object_instance_attribute_foreach(object, callback, limit, count)) { + return false; + } + break; } - } - else if (data.type == INSTANCE_DATA_TYPE_COLLECTION) { - BLI_assert(data.data.collection != nullptr); - const Collection &collection = *data.data.collection; - if (!collection_instance_attribute_foreach(collection, callback, limit, count)) { - return false; + case InstanceReference::Type::Collection: { + const Collection &collection = reference.collection(); + if (!collection_instance_attribute_foreach(collection, callback, limit, count)) { + return false; + } + break; + } + case InstanceReference::Type::None: { + break; } } } @@ -492,6 +518,28 @@ static void join_attributes(Span<GeometryInstanceGroup> set_groups, } } +static void join_curve_splines(Span<GeometryInstanceGroup> set_groups, CurveComponent &result) +{ + CurveEval *new_curve = new CurveEval(); + for (const GeometryInstanceGroup &set_group : set_groups) { + const GeometrySet &set = set_group.geometry_set; + if (!set.has_curve()) { + continue; + } + + const CurveEval &source_curve = *set.get_curve_for_read(); + for (const SplinePtr &source_spline : source_curve.splines) { + for (const float4x4 &transform : set_group.transforms) { + SplinePtr new_spline = source_spline->copy(); + new_spline->transform(transform); + new_curve->splines.append(std::move(new_spline)); + } + } + } + + result.replace(new_curve); +} + static void join_instance_groups_mesh(Span<GeometryInstanceGroup> set_groups, bool convert_points_to_vertices, GeometrySet &result) @@ -558,6 +606,12 @@ static void join_instance_groups_volume(Span<GeometryInstanceGroup> set_groups, UNUSED_VARS(set_groups, dst_component); } +static void join_instance_groups_curve(Span<GeometryInstanceGroup> set_groups, GeometrySet &result) +{ + CurveComponent &dst_component = result.get_component_for_write<CurveComponent>(); + join_curve_splines(set_groups, dst_component); +} + GeometrySet geometry_set_realize_mesh_for_modifier(const GeometrySet &geometry_set) { if (!geometry_set.has_instances() && !geometry_set.has_pointcloud()) { @@ -589,6 +643,7 @@ GeometrySet geometry_set_realize_instances(const GeometrySet &geometry_set) join_instance_groups_mesh(set_groups, false, new_geometry_set); join_instance_groups_pointcloud(set_groups, new_geometry_set); join_instance_groups_volume(set_groups, new_geometry_set); + join_instance_groups_curve(set_groups, new_geometry_set); return new_geometry_set; } diff --git a/source/blender/blenkernel/intern/gpencil_geom.c b/source/blender/blenkernel/intern/gpencil_geom.c index 612bfe65f34..04403e264a4 100644 --- a/source/blender/blenkernel/intern/gpencil_geom.c +++ b/source/blender/blenkernel/intern/gpencil_geom.c @@ -1688,7 +1688,6 @@ void BKE_gpencil_stroke_normal(const bGPDstroke *gps, float r_normal[3]) float vec1[3]; float vec2[3]; - float vec3[3]; /* initial vector (p0 -> p1) */ sub_v3_v3v3(vec1, &pt1->x, &pt0->x); @@ -1697,8 +1696,7 @@ void BKE_gpencil_stroke_normal(const bGPDstroke *gps, float r_normal[3]) sub_v3_v3v3(vec2, &pt3->x, &pt0->x); /* vector orthogonal to polygon plane */ - cross_v3_v3v3(vec3, vec1, vec2); - cross_v3_v3v3(r_normal, vec1, vec3); + cross_v3_v3v3(r_normal, vec1, vec2); /* Normalize vector */ normalize_v3(r_normal); diff --git a/source/blender/blenkernel/intern/gpencil_modifier.c b/source/blender/blenkernel/intern/gpencil_modifier.c index d6c252b39ed..16386cac029 100644 --- a/source/blender/blenkernel/intern/gpencil_modifier.c +++ b/source/blender/blenkernel/intern/gpencil_modifier.c @@ -757,8 +757,8 @@ void BKE_gpencil_modifiers_calc(Depsgraph *depsgraph, Scene *scene, Object *ob) { bGPdata *gpd = (bGPdata *)ob->data; const bool is_edit = GPENCIL_ANY_EDIT_MODE(gpd); - const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd); const bool is_render = (bool)(DEG_get_mode(depsgraph) == DAG_EVAL_RENDER); + const bool is_curve_edit = (bool)(GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd) && !is_render); const bool is_multiedit = (bool)(GPENCIL_MULTIEDIT_SESSIONS_ON(gpd) && !is_render); const bool do_modifiers = (bool)((!is_multiedit) && (!is_curve_edit) && (ob->greasepencil_modifiers.first != NULL) && diff --git a/source/blender/blenkernel/intern/lib_override.c b/source/blender/blenkernel/intern/lib_override.c index 45ba2526da2..84091abd410 100644 --- a/source/blender/blenkernel/intern/lib_override.c +++ b/source/blender/blenkernel/intern/lib_override.c @@ -435,8 +435,8 @@ static bool lib_override_hierarchy_dependencies_recursive_tag(LibOverrideGroupTa for (MainIDRelationsEntryItem *to_id_entry = entry->to_ids; to_id_entry != NULL; to_id_entry = to_id_entry->next) { - if ((to_id_entry->usage_flag & IDWALK_CB_LOOPBACK) != 0) { - /* Never consider 'loop back' relationships ('from', 'parents', 'owner' etc. pointers) as + if ((to_id_entry->usage_flag & IDWALK_CB_OVERRIDE_LIBRARY_NOT_OVERRIDABLE) != 0) { + /* Never consider non-overridable relationships ('from', 'parents', 'owner' etc. pointers) as * actual dependencies. */ continue; } @@ -480,10 +480,8 @@ static void lib_override_linked_group_tag_recursive(LibOverrideGroupTagData *dat for (MainIDRelationsEntryItem *to_id_entry = entry->to_ids; to_id_entry != NULL; to_id_entry = to_id_entry->next) { - if ((to_id_entry->usage_flag & - (IDWALK_CB_EMBEDDED | IDWALK_CB_LOOPBACK | IDWALK_CB_OVERRIDE_LIBRARY_REFERENCE)) != 0) { - /* Never consider 'loop back' relationships ('from', 'parents', 'owner' etc. pointers), nor - * override references or embedded ID pointers, as actual dependencies. */ + if ((to_id_entry->usage_flag & IDWALK_CB_OVERRIDE_LIBRARY_NOT_OVERRIDABLE) != 0) { + /* Never consider non-overridable relationships as actual dependencies. */ continue; } @@ -578,10 +576,8 @@ static void lib_override_local_group_tag_recursive(LibOverrideGroupTagData *data for (MainIDRelationsEntryItem *to_id_entry = entry->to_ids; to_id_entry != NULL; to_id_entry = to_id_entry->next) { - if ((to_id_entry->usage_flag & - (IDWALK_CB_EMBEDDED | IDWALK_CB_LOOPBACK | IDWALK_CB_OVERRIDE_LIBRARY_REFERENCE)) != 0) { - /* Never consider 'loop back' relationships ('from', 'parents', 'owner' etc. pointers), nor - * override references or embedded ID pointers, as actual dependencies. */ + if ((to_id_entry->usage_flag & IDWALK_CB_OVERRIDE_LIBRARY_NOT_OVERRIDABLE) != 0) { + /* Never consider non-overridable relationships as actual dependencies. */ continue; } @@ -1182,8 +1178,7 @@ void BKE_lib_override_library_main_resync(Main *bmain, Scene *scene, ViewLayer * for (MainIDRelationsEntryItem *entry_item = entry->to_ids; entry_item != NULL; entry_item = entry_item->next) { - if (entry_item->usage_flag & - (IDWALK_CB_EMBEDDED | IDWALK_CB_LOOPBACK | IDWALK_CB_OVERRIDE_LIBRARY_REFERENCE)) { + if (entry_item->usage_flag & IDWALK_CB_OVERRIDE_LIBRARY_NOT_OVERRIDABLE) { continue; } ID *id_to = *entry_item->id_pointer.to; @@ -2059,8 +2054,8 @@ static void lib_override_library_id_hierarchy_recursive_reset(Main *bmain, ID *i for (MainIDRelationsEntryItem *to_id_entry = entry->to_ids; to_id_entry != NULL; to_id_entry = to_id_entry->next) { - if ((to_id_entry->usage_flag & IDWALK_CB_LOOPBACK) != 0) { - /* Never consider 'loop back' relationships ('from', 'parents', 'owner' etc. pointers) as + if ((to_id_entry->usage_flag & IDWALK_CB_OVERRIDE_LIBRARY_NOT_OVERRIDABLE) != 0) { + /* Never consider non-overridable relationships ('from', 'parents', 'owner' etc. pointers) as * actual dependencies. */ continue; } diff --git a/source/blender/blenkernel/intern/lib_query.c b/source/blender/blenkernel/intern/lib_query.c index e33743eb36b..3281783d81a 100644 --- a/source/blender/blenkernel/intern/lib_query.c +++ b/source/blender/blenkernel/intern/lib_query.c @@ -56,11 +56,19 @@ typedef struct LibraryForeachIDData { */ ID *self_id; + /** Flags controlling the behavior of the 'foreach id' looping code. */ int flag; + /** Generic flags to be passed to all callback calls for current processed data. */ int cb_flag; + /** Callback flags that are forbidden for all callback calls for current processed data. */ int cb_flag_clear; + + /* Function to call for every ID pointers of current processed data, and its opaque user data + * pointer. */ LibraryIDLinkCallback callback; void *user_data; + /** Store the returned value from the callback, to decide how to continue the processing of ID + * pointers for current data. */ int status; /* To handle recursion. */ @@ -73,13 +81,25 @@ bool BKE_lib_query_foreachid_process(LibraryForeachIDData *data, ID **id_pp, int if (!(data->status & IDWALK_STOP)) { const int flag = data->flag; ID *old_id = *id_pp; - const int callback_return = data->callback(&(struct LibraryIDLinkCallbackData){ - .user_data = data->user_data, - .bmain = data->bmain, - .id_owner = data->owner_id, - .id_self = data->self_id, - .id_pointer = id_pp, - .cb_flag = ((cb_flag | data->cb_flag) & ~data->cb_flag_clear)}); + + /* Update the callback flags with the ones defined (or forbidden) in `data` by the generic + * caller code. */ + cb_flag = ((cb_flag | data->cb_flag) & ~data->cb_flag_clear); + + /* Update the callback flags with some extra information regarding overrides: all 'loopback', + * 'internal', 'embedded' etc. ID pointers are never overridable. */ + if (cb_flag & (IDWALK_CB_INTERNAL | IDWALK_CB_EMBEDDED | IDWALK_CB_LOOPBACK | + IDWALK_CB_OVERRIDE_LIBRARY_REFERENCE)) { + cb_flag |= IDWALK_CB_OVERRIDE_LIBRARY_NOT_OVERRIDABLE; + } + + const int callback_return = data->callback( + &(struct LibraryIDLinkCallbackData){.user_data = data->user_data, + .bmain = data->bmain, + .id_owner = data->owner_id, + .id_self = data->self_id, + .id_pointer = id_pp, + .cb_flag = cb_flag}); if (flag & IDWALK_READONLY) { BLI_assert(*(id_pp) == old_id); } @@ -132,7 +152,10 @@ void BKE_lib_query_idpropertiesForeachIDLink_callback(IDProperty *id_prop, void BLI_assert(id_prop->type == IDP_ID); LibraryForeachIDData *data = (LibraryForeachIDData *)user_data; - BKE_LIB_FOREACHID_PROCESS_ID(data, id_prop->data.pointer, IDWALK_CB_USER); + const int cb_flag = IDWALK_CB_USER | ((id_prop->flag & IDP_FLAG_OVERRIDABLE_LIBRARY) ? + 0 : + IDWALK_CB_OVERRIDE_LIBRARY_NOT_OVERRIDABLE); + BKE_LIB_FOREACHID_PROCESS_ID(data, id_prop->data.pointer, cb_flag); } bool BKE_library_foreach_ID_embedded(LibraryForeachIDData *data, ID **id_pp) diff --git a/source/blender/blenkernel/intern/mesh_convert.c b/source/blender/blenkernel/intern/mesh_convert.c index 5892dc49fd6..098d9c420aa 100644 --- a/source/blender/blenkernel/intern/mesh_convert.c +++ b/source/blender/blenkernel/intern/mesh_convert.c @@ -1061,6 +1061,9 @@ static Object *object_for_curve_to_mesh_create(Object *object) return temp_object; } +/** + * Populate `object->runtime.curve_cache` which is then used to create the mesh. + */ static void curve_to_mesh_eval_ensure(Object *object) { Curve *curve = (Curve *)object->data; @@ -1070,10 +1073,13 @@ static void curve_to_mesh_eval_ensure(Object *object) remapped_object.data = &remapped_curve; - if (remapped_object.runtime.curve_cache == NULL) { - remapped_object.runtime.curve_cache = MEM_callocN(sizeof(CurveCache), "CurveCache for Curve"); + if (object->runtime.curve_cache == NULL) { + object->runtime.curve_cache = MEM_callocN(sizeof(CurveCache), "CurveCache for Curve"); } + /* Temporarily share the curve-cache with the temporary object, owned by `object`. */ + remapped_object.runtime.curve_cache = object->runtime.curve_cache; + /* Clear all modifiers for the bevel object. * * This is because they can not be reliably evaluated for an original object (at least because @@ -1116,6 +1122,9 @@ static void curve_to_mesh_eval_ensure(Object *object) BKE_object_eval_assign_data(&remapped_object, &mesh_eval->id, true); } + /* Owned by `object` & needed by the caller to create the mesh. */ + remapped_object.runtime.curve_cache = NULL; + BKE_object_runtime_free_data(&remapped_object); BKE_object_runtime_free_data(&taper_object); BKE_object_runtime_free_data(&taper_object); diff --git a/source/blender/blenkernel/intern/movieclip.c b/source/blender/blenkernel/intern/movieclip.c index 9c2cd03dbc2..0f2f56f4f2b 100644 --- a/source/blender/blenkernel/intern/movieclip.c +++ b/source/blender/blenkernel/intern/movieclip.c @@ -950,7 +950,7 @@ static void movieclip_load_get_size(MovieClip *clip) int width, height; MovieClipUser user = {0}; - user.framenr = 1; + user.framenr = BKE_movieclip_remap_clip_to_scene_frame(clip, 1); BKE_movieclip_get_size(clip, &user, &width, &height); if (width && height) { diff --git a/source/blender/blenkernel/intern/nla.c b/source/blender/blenkernel/intern/nla.c index 97aa6b07ab0..92e21183acb 100644 --- a/source/blender/blenkernel/intern/nla.c +++ b/source/blender/blenkernel/intern/nla.c @@ -254,7 +254,7 @@ NlaTrack *BKE_nlatrack_copy(Main *bmain, * \param flag: Control ID pointers management, see LIB_ID_CREATE_.../LIB_ID_COPY_... * flags in BKE_lib_id.h */ -void BKE_nla_tracks_copy(Main *bmain, ListBase *dst, ListBase *src, const int flag) +void BKE_nla_tracks_copy(Main *bmain, ListBase *dst, const ListBase *src, const int flag) { NlaTrack *nlt, *nlt_d; @@ -275,6 +275,54 @@ void BKE_nla_tracks_copy(Main *bmain, ListBase *dst, ListBase *src, const int fl } } +/* Set adt_dest->actstrip to the strip with the same index as adt_source->actstrip. */ +static void update_active_strip(AnimData *adt_dest, + NlaTrack *track_dest, + const AnimData *adt_source, + NlaTrack *track_source) +{ + BLI_assert(BLI_listbase_count(&track_source->strips) == BLI_listbase_count(&track_dest->strips)); + + NlaStrip *strip_dest = track_dest->strips.first; + LISTBASE_FOREACH (NlaStrip *, strip_source, &track_source->strips) { + if (strip_source == adt_source->actstrip) { + adt_dest->actstrip = strip_dest; + } + + strip_dest = strip_dest->next; + } +} + +/* Set adt_dest->act_track to the track with the same index as adt_source->act_track. */ +static void update_active_track(AnimData *adt_dest, const AnimData *adt_source) +{ + BLI_assert(BLI_listbase_count(&adt_source->nla_tracks) == + BLI_listbase_count(&adt_dest->nla_tracks)); + + NlaTrack *track_dest = adt_dest->nla_tracks.first; + LISTBASE_FOREACH (NlaTrack *, track_source, &adt_source->nla_tracks) { + if (track_source == adt_source->act_track) { + adt_dest->act_track = track_dest; + /* Assumption: the active strip is on the active track. */ + update_active_strip(adt_dest, track_dest, adt_source, track_source); + } + + track_dest = track_dest->next; + } +} + +void BKE_nla_tracks_copy_from_adt(Main *bmain, + AnimData *adt_dest, + const AnimData *adt_source, + const int flag) +{ + adt_dest->act_track = NULL; + adt_dest->actstrip = NULL; + + BKE_nla_tracks_copy(bmain, &adt_dest->nla_tracks, &adt_source->nla_tracks, flag); + update_active_track(adt_dest, adt_source); +} + /* Adding ------------------------------------------- */ /* Add a NLA Track to the given AnimData diff --git a/source/blender/blenkernel/intern/node.cc b/source/blender/blenkernel/intern/node.cc index d940326cc2c..6634c42a274 100644 --- a/source/blender/blenkernel/intern/node.cc +++ b/source/blender/blenkernel/intern/node.cc @@ -4947,6 +4947,7 @@ static void registerGeometryNodes() register_node_type_geo_boolean(); register_node_type_geo_bounding_box(); register_node_type_geo_collection_info(); + register_node_type_geo_curve_to_mesh(); register_node_type_geo_edge_split(); register_node_type_geo_is_viewport(); register_node_type_geo_join_geometry(); diff --git a/source/blender/blenkernel/intern/object_dupli.cc b/source/blender/blenkernel/intern/object_dupli.cc index 835b62c06bf..768fa9373c1 100644 --- a/source/blender/blenkernel/intern/object_dupli.cc +++ b/source/blender/blenkernel/intern/object_dupli.cc @@ -842,38 +842,38 @@ static void make_duplis_instances_component(const DupliContext *ctx) return; } - Span<float4x4> instance_offset_matrices = component->transforms(); + Span<float4x4> instance_offset_matrices = component->instance_transforms(); + Span<int> instance_reference_handles = component->instance_reference_handles(); Span<int> almost_unique_ids = component->almost_unique_ids(); - Span<InstancedData> instanced_data = component->instanced_data(); + Span<InstanceReference> references = component->references(); - for (int i = 0; i < component->instances_amount(); i++) { - const InstancedData &data = instanced_data[i]; + for (int64_t i : instance_offset_matrices.index_range()) { + const InstanceReference &reference = references[instance_reference_handles[i]]; const int id = almost_unique_ids[i]; - if (data.type == INSTANCE_DATA_TYPE_OBJECT) { - Object *object = data.data.object; - if (object != nullptr) { + switch (reference.type()) { + case InstanceReference::Type::Object: { + Object &object = reference.object(); float matrix[4][4]; mul_m4_m4m4(matrix, ctx->object->obmat, instance_offset_matrices[i].values); - make_dupli(ctx, object, matrix, id); + make_dupli(ctx, &object, matrix, id); float space_matrix[4][4]; - mul_m4_m4m4(space_matrix, instance_offset_matrices[i].values, object->imat); + mul_m4_m4m4(space_matrix, instance_offset_matrices[i].values, object.imat); mul_m4_m4_pre(space_matrix, ctx->object->obmat); - make_recursive_duplis(ctx, object, space_matrix, id); + make_recursive_duplis(ctx, &object, space_matrix, id); + break; } - } - else if (data.type == INSTANCE_DATA_TYPE_COLLECTION) { - Collection *collection = data.data.collection; - if (collection != nullptr) { + case InstanceReference::Type::Collection: { + Collection &collection = reference.collection(); float collection_matrix[4][4]; unit_m4(collection_matrix); - sub_v3_v3(collection_matrix[3], collection->instance_offset); + sub_v3_v3(collection_matrix[3], collection.instance_offset); mul_m4_m4_pre(collection_matrix, instance_offset_matrices[i].values); mul_m4_m4_pre(collection_matrix, ctx->object->obmat); eEvaluationMode mode = DEG_get_mode(ctx->depsgraph); - FOREACH_COLLECTION_VISIBLE_OBJECT_RECURSIVE_BEGIN (collection, object, mode) { + FOREACH_COLLECTION_VISIBLE_OBJECT_RECURSIVE_BEGIN (&collection, object, mode) { if (object == ctx->object) { continue; } @@ -885,6 +885,10 @@ static void make_duplis_instances_component(const DupliContext *ctx) make_recursive_duplis(ctx, object, collection_matrix, id); } FOREACH_COLLECTION_VISIBLE_OBJECT_RECURSIVE_END; + break; + } + case InstanceReference::Type::None: { + break; } } } diff --git a/source/blender/blenkernel/intern/scene.c b/source/blender/blenkernel/intern/scene.c index e1f013eb589..a4ab64a8a02 100644 --- a/source/blender/blenkernel/intern/scene.c +++ b/source/blender/blenkernel/intern/scene.c @@ -742,7 +742,8 @@ static void scene_foreach_id(ID *id, LibraryForeachIDData *data) BKE_LIB_FOREACHID_PROCESS(data, view_layer->mat_override, IDWALK_CB_USER); LISTBASE_FOREACH (Base *, base, &view_layer->object_bases) { - BKE_LIB_FOREACHID_PROCESS(data, base->object, IDWALK_CB_NOP); + BKE_LIB_FOREACHID_PROCESS( + data, base->object, IDWALK_CB_NOP | IDWALK_CB_OVERRIDE_LIBRARY_NOT_OVERRIDABLE); } scene_foreach_layer_collection(data, &view_layer->layer_collections); diff --git a/source/blender/blenkernel/intern/spline_base.cc b/source/blender/blenkernel/intern/spline_base.cc new file mode 100644 index 00000000000..31c2178fa3f --- /dev/null +++ b/source/blender/blenkernel/intern/spline_base.cc @@ -0,0 +1,274 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "BLI_array.hh" +#include "BLI_span.hh" + +#include "FN_generic_virtual_array.hh" + +#include "BKE_spline.hh" + +using blender::float3; +using blender::IndexRange; +using blender::MutableSpan; +using blender::Span; + +Spline::Type Spline::type() const +{ + return type_; +} + +void Spline::translate(const blender::float3 &translation) +{ + for (float3 &position : this->positions()) { + position += translation; + } + this->mark_cache_invalid(); +} + +void Spline::transform(const blender::float4x4 &matrix) +{ + for (float3 &position : this->positions()) { + position = matrix * position; + } + this->mark_cache_invalid(); +} + +int Spline::evaluated_edges_size() const +{ + const int eval_size = this->evaluated_points_size(); + if (eval_size == 1) { + return 0; + } + + return this->is_cyclic_ ? eval_size : eval_size - 1; +} + +float Spline::length() const +{ + return this->evaluated_lengths().last(); +} + +int Spline::segments_size() const +{ + const int points_len = this->size(); + + return is_cyclic_ ? points_len : points_len - 1; +} + +bool Spline::is_cyclic() const +{ + return is_cyclic_; +} + +void Spline::set_cyclic(const bool value) +{ + is_cyclic_ = value; +} + +static void accumulate_lengths(Span<float3> positions, + const bool is_cyclic, + MutableSpan<float> lengths) +{ + float length = 0.0f; + for (const int i : IndexRange(positions.size() - 1)) { + length += float3::distance(positions[i], positions[i + 1]); + lengths[i] = length; + } + if (is_cyclic) { + lengths.last() = length + float3::distance(positions.last(), positions.first()); + } +} + +/** + * Return non-owning access to the cache of accumulated lengths along the spline. Each item is the + * length of the subsequent segment, i.e. the first value is the length of the first segment rather + * than 0. This calculation is rather trivial, and only depends on the evaluated positions. + * However, the results are used often, so it makes sense to cache it. + */ +Span<float> Spline::evaluated_lengths() const +{ + if (!length_cache_dirty_) { + return evaluated_lengths_cache_; + } + + std::lock_guard lock{length_cache_mutex_}; + if (!length_cache_dirty_) { + return evaluated_lengths_cache_; + } + + const int total = evaluated_edges_size(); + evaluated_lengths_cache_.resize(total); + + Span<float3> positions = this->evaluated_positions(); + accumulate_lengths(positions, is_cyclic_, evaluated_lengths_cache_); + + length_cache_dirty_ = false; + return evaluated_lengths_cache_; +} + +static float3 direction_bisect(const float3 &prev, const float3 &middle, const float3 &next) +{ + const float3 dir_prev = (middle - prev).normalized(); + const float3 dir_next = (next - middle).normalized(); + + return (dir_prev + dir_next).normalized(); +} + +static void calculate_tangents(Span<float3> positions, + const bool is_cyclic, + MutableSpan<float3> tangents) +{ + if (positions.size() == 1) { + return; + } + + for (const int i : IndexRange(1, positions.size() - 2)) { + tangents[i] = direction_bisect(positions[i - 1], positions[i], positions[i + 1]); + } + + if (is_cyclic) { + const float3 &second_to_last = positions[positions.size() - 2]; + const float3 &last = positions.last(); + const float3 &first = positions.first(); + const float3 &second = positions[1]; + tangents.first() = direction_bisect(last, first, second); + tangents.last() = direction_bisect(second_to_last, last, first); + } + else { + tangents.first() = (positions[1] - positions[0]).normalized(); + tangents.last() = (positions.last() - positions[positions.size() - 2]).normalized(); + } +} + +/** + * Return non-owning access to the direction of the curve at each evaluated point. + */ +Span<float3> Spline::evaluated_tangents() const +{ + if (!tangent_cache_dirty_) { + return evaluated_tangents_cache_; + } + + std::lock_guard lock{tangent_cache_mutex_}; + if (!tangent_cache_dirty_) { + return evaluated_tangents_cache_; + } + + const int eval_size = this->evaluated_points_size(); + evaluated_tangents_cache_.resize(eval_size); + + Span<float3> positions = this->evaluated_positions(); + + if (eval_size == 1) { + evaluated_tangents_cache_.first() = float3(1.0f, 0.0f, 0.0f); + } + else { + calculate_tangents(positions, is_cyclic_, evaluated_tangents_cache_); + this->correct_end_tangents(); + } + + tangent_cache_dirty_ = false; + return evaluated_tangents_cache_; +} + +static float3 rotate_direction_around_axis(const float3 &direction, + const float3 &axis, + const float angle) +{ + BLI_ASSERT_UNIT_V3(direction); + BLI_ASSERT_UNIT_V3(axis); + + const float3 axis_scaled = axis * float3::dot(direction, axis); + const float3 diff = direction - axis_scaled; + const float3 cross = float3::cross(axis, diff); + + return axis_scaled + diff * std::cos(angle) + cross * std::sin(angle); +} + +static void calculate_normals_z_up(Span<float3> tangents, MutableSpan<float3> normals) +{ + for (const int i : normals.index_range()) { + normals[i] = float3::cross(tangents[i], float3(0.0f, 0.0f, 1.0f)).normalized(); + } +} + +/** + * Return non-owning access to the direction vectors perpendicular to the tangents at every + * evaluated point. The method used to generate the normal vectors depends on Spline.normal_mode. + */ +Span<float3> Spline::evaluated_normals() const +{ + if (!normal_cache_dirty_) { + return evaluated_normals_cache_; + } + + std::lock_guard lock{normal_cache_mutex_}; + if (!normal_cache_dirty_) { + return evaluated_normals_cache_; + } + + const int eval_size = this->evaluated_points_size(); + evaluated_normals_cache_.resize(eval_size); + + Span<float3> tangents = evaluated_tangents(); + MutableSpan<float3> normals = evaluated_normals_cache_; + + /* Only Z up normals are supported at the moment. */ + calculate_normals_z_up(tangents, normals); + + /* Rotate the generated normals with the interpolated tilt data. */ + blender::fn::GVArray_Typed<float> tilts{ + this->interpolate_to_evaluated_points(blender::fn::GVArray_For_Span(this->tilts()))}; + for (const int i : normals.index_range()) { + normals[i] = rotate_direction_around_axis(normals[i], tangents[i], tilts[i]); + } + + normal_cache_dirty_ = false; + return evaluated_normals_cache_; +} + +Spline::LookupResult Spline::lookup_evaluated_factor(const float factor) const +{ + return this->lookup_evaluated_length(this->length() * factor); +} + +/** + * \note This does not support extrapolation currently. + */ +Spline::LookupResult Spline::lookup_evaluated_length(const float length) const +{ + BLI_assert(length >= 0.0f && length <= this->length()); + + Span<float> lengths = this->evaluated_lengths(); + + const float *offset = std::lower_bound(lengths.begin(), lengths.end(), length); + const int index = offset - lengths.begin(); + const int next_index = (index == this->size() - 1) ? 0 : index + 1; + + const float previous_length = (index == 0) ? 0.0f : lengths[index - 1]; + const float factor = (length - previous_length) / (lengths[index] - previous_length); + + return LookupResult{index, next_index, factor}; +} + +void Spline::bounds_min_max(float3 &min, float3 &max, const bool use_evaluated) const +{ + Span<float3> positions = use_evaluated ? this->evaluated_positions() : this->positions(); + for (const float3 &position : positions) { + minmax_v3v3_v3(min, max, position); + } +} diff --git a/source/blender/blenkernel/intern/spline_bezier.cc b/source/blender/blenkernel/intern/spline_bezier.cc new file mode 100644 index 00000000000..f62718011da --- /dev/null +++ b/source/blender/blenkernel/intern/spline_bezier.cc @@ -0,0 +1,478 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "BLI_array.hh" +#include "BLI_span.hh" +#include "BLI_task.hh" + +#include "BKE_spline.hh" + +using blender::Array; +using blender::float3; +using blender::IndexRange; +using blender::MutableSpan; +using blender::Span; + +SplinePtr BezierSpline::copy() const +{ + return std::make_unique<BezierSpline>(*this); +} + +int BezierSpline::size() const +{ + const int size = positions_.size(); + BLI_assert(size == handle_types_left_.size()); + BLI_assert(size == handle_positions_left_.size()); + BLI_assert(size == handle_types_right_.size()); + BLI_assert(size == handle_positions_right_.size()); + BLI_assert(size == radii_.size()); + BLI_assert(size == tilts_.size()); + return size; +} + +int BezierSpline::resolution() const +{ + return resolution_; +} + +void BezierSpline::set_resolution(const int value) +{ + BLI_assert(value > 0); + resolution_ = value; + this->mark_cache_invalid(); +} + +void BezierSpline::add_point(const float3 position, + const HandleType handle_type_start, + const float3 handle_position_start, + const HandleType handle_type_end, + const float3 handle_position_end, + const float radius, + const float tilt) +{ + handle_types_left_.append(handle_type_start); + handle_positions_left_.append(handle_position_start); + positions_.append(position); + handle_types_right_.append(handle_type_end); + handle_positions_right_.append(handle_position_end); + radii_.append(radius); + tilts_.append(tilt); + this->mark_cache_invalid(); +} + +MutableSpan<float3> BezierSpline::positions() +{ + return positions_; +} +Span<float3> BezierSpline::positions() const +{ + return positions_; +} +MutableSpan<float> BezierSpline::radii() +{ + return radii_; +} +Span<float> BezierSpline::radii() const +{ + return radii_; +} +MutableSpan<float> BezierSpline::tilts() +{ + return tilts_; +} +Span<float> BezierSpline::tilts() const +{ + return tilts_; +} +Span<BezierSpline::HandleType> BezierSpline::handle_types_left() const +{ + return handle_types_left_; +} +MutableSpan<BezierSpline::HandleType> BezierSpline::handle_types_left() +{ + return handle_types_left_; +} +Span<float3> BezierSpline::handle_positions_left() const +{ + return handle_positions_left_; +} +MutableSpan<float3> BezierSpline::handle_positions_left() +{ + return handle_positions_left_; +} +Span<BezierSpline::HandleType> BezierSpline::handle_types_right() const +{ + return handle_types_right_; +} +MutableSpan<BezierSpline::HandleType> BezierSpline::handle_types_right() +{ + return handle_types_right_; +} +Span<float3> BezierSpline::handle_positions_right() const +{ + return handle_positions_right_; +} +MutableSpan<float3> BezierSpline::handle_positions_right() +{ + return handle_positions_right_; +} + +void BezierSpline::translate(const blender::float3 &translation) +{ + for (float3 &position : this->positions()) { + position += translation; + } + for (float3 &handle_position : this->handle_positions_left()) { + handle_position += translation; + } + for (float3 &handle_position : this->handle_positions_right()) { + handle_position += translation; + } + this->mark_cache_invalid(); +} + +void BezierSpline::transform(const blender::float4x4 &matrix) +{ + for (float3 &position : this->positions()) { + position = matrix * position; + } + for (float3 &handle_position : this->handle_positions_left()) { + handle_position = matrix * handle_position; + } + for (float3 &handle_position : this->handle_positions_right()) { + handle_position = matrix * handle_position; + } + this->mark_cache_invalid(); +} + +bool BezierSpline::point_is_sharp(const int index) const +{ + return ELEM(handle_types_left_[index], HandleType::Vector, HandleType::Free) || + ELEM(handle_types_right_[index], HandleType::Vector, HandleType::Free); +} + +bool BezierSpline::segment_is_vector(const int index) const +{ + if (index == this->size() - 1) { + BLI_assert(is_cyclic_); + return handle_types_right_.last() == HandleType::Vector && + handle_types_left_.first() == HandleType::Vector; + } + return handle_types_right_[index] == HandleType::Vector && + handle_types_left_[index + 1] == HandleType::Vector; +} + +void BezierSpline::mark_cache_invalid() +{ + offset_cache_dirty_ = true; + position_cache_dirty_ = true; + mapping_cache_dirty_ = true; + tangent_cache_dirty_ = true; + normal_cache_dirty_ = true; + length_cache_dirty_ = true; +} + +int BezierSpline::evaluated_points_size() const +{ + const int points_len = this->size(); + BLI_assert(points_len > 0); + + const int last_offset = this->control_point_offsets().last(); + if (is_cyclic_ && points_len > 1) { + return last_offset + (this->segment_is_vector(points_len - 1) ? 0 : resolution_); + } + + return last_offset + 1; +} + +/** + * If the spline is not cyclic, the direction for the first and last points is just the + * direction formed by the corresponding handles and control points. In the unlikely situation + * that the handles define a zero direction, fallback to using the direction defined by the + * first and last evaluated segments already calculated in #Spline::evaluated_tangents(). + */ +void BezierSpline::correct_end_tangents() const +{ + if (is_cyclic_) { + return; + } + + MutableSpan<float3> tangents(evaluated_tangents_cache_); + + if (handle_positions_left_.first() != positions_.first()) { + tangents.first() = (positions_.first() - handle_positions_left_.first()).normalized(); + } + if (handle_positions_right_.last() != positions_.last()) { + tangents.last() = (handle_positions_right_.last() - positions_.last()).normalized(); + } +} + +static void bezier_forward_difference_3d(const float3 &point_0, + const float3 &point_1, + const float3 &point_2, + const float3 &point_3, + MutableSpan<float3> result) +{ + BLI_assert(result.size() > 0); + const float inv_len = 1.0f / static_cast<float>(result.size()); + const float inv_len_squared = inv_len * inv_len; + const float inv_len_cubed = inv_len_squared * inv_len; + + const float3 rt1 = 3.0f * (point_1 - point_0) * inv_len; + const float3 rt2 = 3.0f * (point_0 - 2.0f * point_1 + point_2) * inv_len_squared; + const float3 rt3 = (point_3 - point_0 + 3.0f * (point_1 - point_2)) * inv_len_cubed; + + float3 q0 = point_0; + float3 q1 = rt1 + rt2 + rt3; + float3 q2 = 2.0f * rt2 + 6.0f * rt3; + float3 q3 = 6.0f * rt3; + for (const int i : result.index_range()) { + result[i] = q0; + q0 += q1; + q1 += q2; + q2 += q3; + } +} + +void BezierSpline::evaluate_bezier_segment(const int index, + const int next_index, + MutableSpan<float3> positions) const +{ + if (this->segment_is_vector(index)) { + BLI_assert(positions.size() == 1); + positions.first() = positions_[index]; + } + else { + bezier_forward_difference_3d(positions_[index], + handle_positions_right_[index], + handle_positions_left_[next_index], + positions_[next_index], + positions); + } +} + +/** + * Returns access to a cache of offsets into the evaluated point array for each control point. + * This is important because while most control point edges generate the number of edges specified + * by the resolution, vector segments only generate one edge. + */ +Span<int> BezierSpline::control_point_offsets() const +{ + if (!offset_cache_dirty_) { + return offset_cache_; + } + + std::lock_guard lock{offset_cache_mutex_}; + if (!offset_cache_dirty_) { + return offset_cache_; + } + + const int points_len = this->size(); + offset_cache_.resize(points_len); + + MutableSpan<int> offsets = offset_cache_; + + int offset = 0; + for (const int i : IndexRange(points_len - 1)) { + offsets[i] = offset; + offset += this->segment_is_vector(i) ? 1 : resolution_; + } + offsets.last() = offset; + + offset_cache_dirty_ = false; + return offsets; +} + +static void calculate_mappings_linear_resolution(Span<int> offsets, + const int size, + const int resolution, + const bool is_cyclic, + MutableSpan<float> r_mappings) +{ + const float first_segment_len_inv = 1.0f / offsets[1]; + for (const int i : IndexRange(0, offsets[1])) { + r_mappings[i] = i * first_segment_len_inv; + } + + const int grain_size = std::max(2048 / resolution, 1); + blender::parallel_for(IndexRange(1, size - 2), grain_size, [&](IndexRange range) { + for (const int i_control_point : range) { + const int segment_len = offsets[i_control_point + 1] - offsets[i_control_point]; + const float segment_len_inv = 1.0f / segment_len; + for (const int i : IndexRange(segment_len)) { + r_mappings[offsets[i_control_point] + i] = i_control_point + i * segment_len_inv; + } + } + }); + + if (is_cyclic) { + const int last_segment_len = offsets[size - 1] - offsets[size - 2]; + const float last_segment_len_inv = 1.0f / last_segment_len; + for (const int i : IndexRange(last_segment_len)) { + r_mappings[offsets[size - 1] + i] = size - 1 + i * last_segment_len_inv; + } + } + else { + r_mappings.last() = size - 1; + } +} + +/** + * Returns non-owning access to an array of values containing the information necessary to + * interpolate values from the original control points to evaluated points. The control point + * index is the integer part of each value, and the factor used for interpolating to the next + * control point is the remaining factional part. + */ +Span<float> BezierSpline::evaluated_mappings() const +{ + if (!mapping_cache_dirty_) { + return evaluated_mapping_cache_; + } + + std::lock_guard lock{mapping_cache_mutex_}; + if (!mapping_cache_dirty_) { + return evaluated_mapping_cache_; + } + + const int size = this->size(); + const int eval_size = this->evaluated_points_size(); + evaluated_mapping_cache_.resize(eval_size); + MutableSpan<float> mappings = evaluated_mapping_cache_; + + if (eval_size == 1) { + mappings.first() = 0.0f; + mapping_cache_dirty_ = false; + return mappings; + } + + Span<int> offsets = this->control_point_offsets(); + + calculate_mappings_linear_resolution(offsets, size, resolution_, is_cyclic_, mappings); + + mapping_cache_dirty_ = false; + return mappings; +} + +Span<float3> BezierSpline::evaluated_positions() const +{ + if (!position_cache_dirty_) { + return evaluated_position_cache_; + } + + std::lock_guard lock{position_cache_mutex_}; + if (!position_cache_dirty_) { + return evaluated_position_cache_; + } + + const int eval_size = this->evaluated_points_size(); + evaluated_position_cache_.resize(eval_size); + + MutableSpan<float3> positions = evaluated_position_cache_; + + Span<int> offsets = this->control_point_offsets(); + BLI_assert(offsets.last() <= eval_size); + + const int grain_size = std::max(512 / resolution_, 1); + blender::parallel_for(IndexRange(this->size() - 1), grain_size, [&](IndexRange range) { + for (const int i : range) { + this->evaluate_bezier_segment( + i, i + 1, positions.slice(offsets[i], offsets[i + 1] - offsets[i])); + } + }); + + const int i_last = this->size() - 1; + if (is_cyclic_) { + this->evaluate_bezier_segment(i_last, 0, positions.slice(offsets.last(), resolution_)); + } + else { + /* Since evaluating the bezier segment doesn't add the final point, + * it must be added manually in the non-cyclic case. */ + positions.last() = positions_.last(); + } + + position_cache_dirty_ = false; + return positions; +} + +/** + * Convert the data encoded in #evaulated_mappings into its parts-- the information necessary + * to interpolate data from control points to evaluated points between them. The next control + * point index result will not overflow the size of the vector. + */ +BezierSpline::InterpolationData BezierSpline::interpolation_data_from_index_factor( + const float index_factor) const +{ + const int points_len = this->size(); + + if (is_cyclic_) { + if (index_factor < points_len) { + const int index = std::floor(index_factor); + const int next_index = (index < points_len - 1) ? index + 1 : 0; + return InterpolationData{index, next_index, index_factor - index}; + } + return InterpolationData{points_len - 1, 0, 1.0f}; + } + + if (index_factor < points_len - 1) { + const int index = std::floor(index_factor); + const int next_index = index + 1; + return InterpolationData{index, next_index, index_factor - index}; + } + return InterpolationData{points_len - 2, points_len - 1, 1.0f}; +} + +/* Use a spline argument to avoid adding this to the header. */ +template<typename T> +static void interpolate_to_evaluated_points_impl(const BezierSpline &spline, + const blender::VArray<T> &source_data, + MutableSpan<T> result_data) +{ + Span<float> mappings = spline.evaluated_mappings(); + + for (const int i : result_data.index_range()) { + BezierSpline::InterpolationData interp = spline.interpolation_data_from_index_factor( + mappings[i]); + + const T &value = source_data[interp.control_point_index]; + const T &next_value = source_data[interp.next_control_point_index]; + + result_data[i] = blender::attribute_math::mix2(interp.factor, value, next_value); + } +} + +blender::fn::GVArrayPtr BezierSpline::interpolate_to_evaluated_points( + const blender::fn::GVArray &source_data) const +{ + BLI_assert(source_data.size() == this->size()); + + const int eval_size = this->evaluated_points_size(); + if (eval_size == 1) { + return source_data.shallow_copy(); + } + + blender::fn::GVArrayPtr new_varray; + blender::attribute_math::convert_to_static_type(source_data.type(), [&](auto dummy) { + using T = decltype(dummy); + if constexpr (!std::is_void_v<blender::attribute_math::DefaultMixer<T>>) { + Array<T> values(eval_size); + interpolate_to_evaluated_points_impl<T>(*this, source_data.typed<T>(), values); + new_varray = std::make_unique<blender::fn::GVArray_For_ArrayContainer<Array<T>>>( + std::move(values)); + } + }); + + return new_varray; +} diff --git a/source/blender/blenkernel/intern/spline_nurbs.cc b/source/blender/blenkernel/intern/spline_nurbs.cc new file mode 100644 index 00000000000..37d1232cfeb --- /dev/null +++ b/source/blender/blenkernel/intern/spline_nurbs.cc @@ -0,0 +1,417 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "BLI_array.hh" +#include "BLI_span.hh" +#include "BLI_virtual_array.hh" + +#include "BKE_attribute_math.hh" +#include "BKE_spline.hh" + +using blender::Array; +using blender::float3; +using blender::IndexRange; +using blender::MutableSpan; +using blender::Span; + +SplinePtr NURBSpline::copy() const +{ + return std::make_unique<NURBSpline>(*this); +} + +int NURBSpline::size() const +{ + const int size = positions_.size(); + BLI_assert(size == radii_.size()); + BLI_assert(size == tilts_.size()); + BLI_assert(size == weights_.size()); + return size; +} + +int NURBSpline::resolution() const +{ + return resolution_; +} + +void NURBSpline::set_resolution(const int value) +{ + BLI_assert(value > 0); + resolution_ = value; + this->mark_cache_invalid(); +} + +uint8_t NURBSpline::order() const +{ + return order_; +} + +void NURBSpline::set_order(const uint8_t value) +{ + BLI_assert(value >= 2 && value <= 6); + order_ = value; + this->mark_cache_invalid(); +} + +void NURBSpline::add_point(const float3 position, + const float radius, + const float tilt, + const float weight) +{ + positions_.append(position); + radii_.append(radius); + tilts_.append(tilt); + weights_.append(weight); + knots_dirty_ = true; + this->mark_cache_invalid(); +} + +MutableSpan<float3> NURBSpline::positions() +{ + return positions_; +} +Span<float3> NURBSpline::positions() const +{ + return positions_; +} +MutableSpan<float> NURBSpline::radii() +{ + return radii_; +} +Span<float> NURBSpline::radii() const +{ + return radii_; +} +MutableSpan<float> NURBSpline::tilts() +{ + return tilts_; +} +Span<float> NURBSpline::tilts() const +{ + return tilts_; +} +MutableSpan<float> NURBSpline::weights() +{ + return weights_; +} +Span<float> NURBSpline::weights() const +{ + return weights_; +} + +void NURBSpline::mark_cache_invalid() +{ + basis_cache_dirty_ = true; + position_cache_dirty_ = true; + tangent_cache_dirty_ = true; + normal_cache_dirty_ = true; + length_cache_dirty_ = true; +} + +int NURBSpline::evaluated_points_size() const +{ + if (!this->check_valid_size_and_order()) { + return 0; + } + return resolution_ * this->segments_size(); +} + +void NURBSpline::correct_end_tangents() const +{ +} + +bool NURBSpline::check_valid_size_and_order() const +{ + if (this->size() < order_) { + return false; + } + + if (!is_cyclic_ && this->knots_mode == KnotsMode::Bezier) { + if (order_ == 4) { + if (this->size() < 5) { + return false; + } + } + else if (order_ != 3) { + return false; + } + } + + return true; +} + +int NURBSpline::knots_size() const +{ + const int size = this->size() + order_; + return is_cyclic_ ? size + order_ - 1 : size; +} + +void NURBSpline::calculate_knots() const +{ + const KnotsMode mode = this->knots_mode; + const int length = this->size(); + const int order = order_; + + knots_.resize(this->knots_size()); + + MutableSpan<float> knots = knots_; + + if (mode == NURBSpline::KnotsMode::Normal || is_cyclic_) { + for (const int i : knots.index_range()) { + knots[i] = static_cast<float>(i); + } + } + else if (mode == NURBSpline::KnotsMode::EndPoint) { + float k = 0.0f; + for (const int i : IndexRange(1, knots.size())) { + knots[i - 1] = k; + if (i >= order && i <= length) { + k += 1.0f; + } + } + } + else if (mode == NURBSpline::KnotsMode::Bezier) { + BLI_assert(ELEM(order, 3, 4)); + if (order == 3) { + float k = 0.6f; + for (const int i : knots.index_range()) { + if (i >= order && i <= length) { + k += 0.5f; + } + knots[i] = std::floor(k); + } + } + else { + float k = 0.34f; + for (const int i : knots.index_range()) { + knots[i] = std::floor(k); + k += 1.0f / 3.0f; + } + } + } + + if (is_cyclic_) { + const int b = length + order - 1; + if (order > 2) { + for (const int i : IndexRange(1, order - 2)) { + if (knots[b] != knots[b - i]) { + if (i == order - 1) { + knots[length + order - 2] += 1.0f; + break; + } + } + } + } + + int c = order; + for (int i = b; i < this->knots_size(); i++) { + knots[i] = knots[i - 1] + (knots[c] - knots[c - 1]); + c--; + } + } +} + +Span<float> NURBSpline::knots() const +{ + if (!knots_dirty_) { + BLI_assert(knots_.size() == this->size() + order_); + return knots_; + } + + std::lock_guard lock{knots_mutex_}; + if (!knots_dirty_) { + BLI_assert(knots_.size() == this->size() + order_); + return knots_; + } + + this->calculate_knots(); + + knots_dirty_ = false; + + return knots_; +} + +static void calculate_basis_for_point(const float parameter, + const int points_len, + const int order, + Span<float> knots, + MutableSpan<float> basis_buffer, + NURBSpline::BasisCache &basis_cache) +{ + /* Clamp parameter due to floating point inaccuracy. TODO: Look into using doubles. */ + const float t = std::clamp(parameter, knots[0], knots[points_len + order - 1]); + + int start = 0; + int end = 0; + for (const int i : IndexRange(points_len + order - 1)) { + const bool knots_equal = knots[i] == knots[i + 1]; + if (knots_equal || t < knots[i] || t > knots[i + 1]) { + basis_buffer[i] = 0.0f; + continue; + } + + basis_buffer[i] = 1.0f; + start = std::max(i - order - 1, 0); + end = i; + basis_buffer.slice(i + 1, points_len + order - 1 - i).fill(0.0f); + break; + } + basis_buffer[points_len + order - 1] = 0.0f; + + for (const int i_order : IndexRange(2, order - 1)) { + if (end + i_order >= points_len + order) { + end = points_len + order - 1 - i_order; + } + for (const int i : IndexRange(start, end - start + 1)) { + float new_basis = 0.0f; + if (basis_buffer[i] != 0.0f) { + new_basis += ((t - knots[i]) * basis_buffer[i]) / (knots[i + i_order - 1] - knots[i]); + } + + if (basis_buffer[i + 1] != 0.0f) { + new_basis += ((knots[i + i_order] - t) * basis_buffer[i + 1]) / + (knots[i + i_order] - knots[i + 1]); + } + + basis_buffer[i] = new_basis; + } + } + + /* Shrink the range of calculated values to avoid storing unnecessary zeros. */ + while (basis_buffer[start] == 0.0f && start < end) { + start++; + } + while (basis_buffer[end] == 0.0f && end > start) { + end--; + } + + basis_cache.weights.clear(); + basis_cache.weights.extend(basis_buffer.slice(start, end - start + 1)); + basis_cache.start_index = start; +} + +void NURBSpline::calculate_basis_cache() const +{ + if (!basis_cache_dirty_) { + return; + } + + std::lock_guard lock{basis_cache_mutex_}; + if (!basis_cache_dirty_) { + return; + } + + const int points_len = this->size(); + const int eval_size = this->evaluated_points_size(); + BLI_assert(this->evaluated_edges_size() > 0); + basis_cache_.resize(eval_size); + + const int order = this->order(); + Span<float> control_weights = this->weights(); + Span<float> knots = this->knots(); + + MutableSpan<BasisCache> basis_cache(basis_cache_); + + /* This buffer is reused by each basis calculation to store temporary values. + * Theoretically it could be optimized away in the future. */ + Array<float> basis_buffer(this->knots_size()); + + const float start = knots[order - 1]; + const float end = is_cyclic_ ? knots[points_len + order - 1] : knots[points_len]; + const float step = (end - start) / this->evaluated_edges_size(); + float parameter = start; + for (const int i : IndexRange(eval_size)) { + BasisCache &basis = basis_cache[i]; + calculate_basis_for_point( + parameter, points_len + (is_cyclic_ ? order - 1 : 0), order, knots, basis_buffer, basis); + BLI_assert(basis.weights.size() <= order); + + for (const int j : basis.weights.index_range()) { + const int point_index = (basis.start_index + j) % points_len; + basis.weights[j] *= control_weights[point_index]; + } + + parameter += step; + } + + basis_cache_dirty_ = false; +} + +template<typename T> +void interpolate_to_evaluated_points_impl(Span<NURBSpline::BasisCache> weights, + const blender::VArray<T> &source_data, + MutableSpan<T> result_data) +{ + const int points_len = source_data.size(); + BLI_assert(result_data.size() == weights.size()); + blender::attribute_math::DefaultMixer<T> mixer(result_data); + + for (const int i : result_data.index_range()) { + Span<float> point_weights = weights[i].weights; + const int start_index = weights[i].start_index; + + for (const int j : point_weights.index_range()) { + const int point_index = (start_index + j) % points_len; + mixer.mix_in(i, source_data[point_index], point_weights[j]); + } + } + + mixer.finalize(); +} + +blender::fn::GVArrayPtr NURBSpline::interpolate_to_evaluated_points( + const blender::fn::GVArray &source_data) const +{ + BLI_assert(source_data.size() == this->size()); + + this->calculate_basis_cache(); + Span<BasisCache> weights(basis_cache_); + + blender::fn::GVArrayPtr new_varray; + blender::attribute_math::convert_to_static_type(source_data.type(), [&](auto dummy) { + using T = decltype(dummy); + if constexpr (!std::is_void_v<blender::attribute_math::DefaultMixer<T>>) { + Array<T> values(this->evaluated_points_size()); + interpolate_to_evaluated_points_impl<T>(weights, source_data.typed<T>(), values); + new_varray = std::make_unique<blender::fn::GVArray_For_ArrayContainer<Array<T>>>( + std::move(values)); + } + }); + + return new_varray; +} + +Span<float3> NURBSpline::evaluated_positions() const +{ + if (!position_cache_dirty_) { + return evaluated_position_cache_; + } + + std::lock_guard lock{position_cache_mutex_}; + if (!position_cache_dirty_) { + return evaluated_position_cache_; + } + + const int eval_size = this->evaluated_points_size(); + evaluated_position_cache_.resize(eval_size); + + blender::fn::GVArray_Typed<float3> evaluated_positions{ + this->interpolate_to_evaluated_points(blender::fn::GVArray_For_Span<float3>(positions_))}; + + evaluated_positions->materialize(evaluated_position_cache_); + + position_cache_dirty_ = false; + return evaluated_position_cache_; +} diff --git a/source/blender/blenkernel/intern/spline_poly.cc b/source/blender/blenkernel/intern/spline_poly.cc new file mode 100644 index 00000000000..09c578c0503 --- /dev/null +++ b/source/blender/blenkernel/intern/spline_poly.cc @@ -0,0 +1,105 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "BLI_span.hh" +#include "BLI_virtual_array.hh" + +#include "BKE_spline.hh" + +using blender::float3; +using blender::MutableSpan; +using blender::Span; + +SplinePtr PolySpline::copy() const +{ + return std::make_unique<PolySpline>(*this); +} + +int PolySpline::size() const +{ + const int size = positions_.size(); + BLI_assert(size == radii_.size()); + BLI_assert(size == tilts_.size()); + return size; +} + +void PolySpline::add_point(const float3 position, const float radius, const float tilt) +{ + positions_.append(position); + radii_.append(radius); + tilts_.append(tilt); + this->mark_cache_invalid(); +} + +MutableSpan<float3> PolySpline::positions() +{ + return positions_; +} +Span<float3> PolySpline::positions() const +{ + return positions_; +} +MutableSpan<float> PolySpline::radii() +{ + return radii_; +} +Span<float> PolySpline::radii() const +{ + return radii_; +} +MutableSpan<float> PolySpline::tilts() +{ + return tilts_; +} +Span<float> PolySpline::tilts() const +{ + return tilts_; +} + +void PolySpline::mark_cache_invalid() +{ + tangent_cache_dirty_ = true; + normal_cache_dirty_ = true; + length_cache_dirty_ = true; +} + +int PolySpline::evaluated_points_size() const +{ + return this->size(); +} + +void PolySpline::correct_end_tangents() const +{ +} + +Span<float3> PolySpline::evaluated_positions() const +{ + return this->positions(); +} + +/** + * Poly spline interpolation from control points to evaluated points is a special case, since + * the result data is the same as the input data. This function returns a GVArray that points to + * the original data. Therefore the lifetime of the returned virtual array must not be longer than + * the source data. + */ +blender::fn::GVArrayPtr PolySpline::interpolate_to_evaluated_points( + const blender::fn::GVArray &source_data) const +{ + BLI_assert(source_data.size() == this->size()); + + return source_data.shallow_copy(); +} diff --git a/source/blender/blenkernel/intern/text.c b/source/blender/blenkernel/intern/text.c index 944e01321ce..27f5593c2ca 100644 --- a/source/blender/blenkernel/intern/text.c +++ b/source/blender/blenkernel/intern/text.c @@ -171,6 +171,9 @@ static void text_free_data(ID *id) static void text_blend_write(BlendWriter *writer, ID *id, const void *id_address) { + if (id->us < 1 && !BLO_write_is_undo(writer)) { + return; + } Text *text = (Text *)id; /* Note: we are clearing local temp data here, *not* the flag in the actual 'real' ID. */ @@ -231,8 +234,6 @@ static void text_blend_read_data(BlendDataReader *reader, ID *id) } text->flags = (text->flags) & ~TXT_ISEXT; - - id_us_ensure_real(&text->id); } IDTypeInfo IDType_ID_TXT = { @@ -293,8 +294,10 @@ Text *BKE_text_add(Main *bmain, const char *name) Text *ta; ta = BKE_id_new(bmain, ID_TXT, name); - /* Texts always have 'real' user (see also read code). */ - id_us_ensure_real(&ta->id); + /* Texts have no users by default... Set the fake user flag to ensure that this text block + * doesn't get deleted by default when cleaning up data blocks. */ + id_us_min(&ta->id); + id_fake_user_set(&ta->id); return ta; } @@ -468,7 +471,7 @@ bool BKE_text_reload(Text *text) * \param is_internal: If \a true, this text data-block only exists in memory, * not as a file on disk. * - * \note text data-blocks have no user by default, only the 'real user' flag. + * \note text data-blocks have no real user but have 'fake user' enabled by default */ Text *BKE_text_load_ex(Main *bmain, const char *file, const char *relpath, const bool is_internal) { @@ -489,9 +492,8 @@ Text *BKE_text_load_ex(Main *bmain, const char *file, const char *relpath, const } ta = BKE_libblock_alloc(bmain, ID_TXT, BLI_path_basename(filepath_abs), 0); - /* Texts have no user by default... Only the 'real' user flag. */ - id_us_ensure_real(&ta->id); id_us_min(&ta->id); + id_fake_user_set(&ta->id); BLI_listbase_clear(&ta->lines); ta->curl = ta->sell = NULL; diff --git a/source/blender/blenlib/BLI_float3.hh b/source/blender/blenlib/BLI_float3.hh index cbc4d4ed366..04aae375889 100644 --- a/source/blender/blenlib/BLI_float3.hh +++ b/source/blender/blenlib/BLI_float3.hh @@ -245,6 +245,13 @@ struct float3 { return result; } + static float3 cross(const float3 &a, const float3 &b) + { + float3 result; + cross_v3_v3v3(result, a, b); + return result; + } + static float3 project(const float3 &a, const float3 &b) { float3 result; diff --git a/source/blender/blenlib/BLI_float4x4.hh b/source/blender/blenlib/BLI_float4x4.hh index f5ba91bc12c..396b0b1bd21 100644 --- a/source/blender/blenlib/BLI_float4x4.hh +++ b/source/blender/blenlib/BLI_float4x4.hh @@ -45,6 +45,37 @@ struct float4x4 { return mat; } + static float4x4 from_normalized_axis_data(const float3 location, + const float3 forward, + const float3 up) + { + BLI_ASSERT_UNIT_V3(forward); + BLI_ASSERT_UNIT_V3(up); + float4x4 matrix; + const float3 cross = float3::cross(forward, up); + matrix.values[0][0] = forward.x; + matrix.values[1][0] = cross.x; + matrix.values[2][0] = up.x; + matrix.values[3][0] = location.x; + + matrix.values[0][1] = forward.y; + matrix.values[1][1] = cross.y; + matrix.values[2][1] = up.y; + matrix.values[3][1] = location.y; + + matrix.values[0][2] = forward.z; + matrix.values[1][2] = cross.z; + matrix.values[2][2] = up.z; + matrix.values[3][2] = location.z; + + matrix.values[0][3] = 0.0f; + matrix.values[1][3] = 0.0f; + matrix.values[2][3] = 0.0f; + matrix.values[3][3] = 1.0f; + + return matrix; + } + static float4x4 identity() { float4x4 mat; @@ -116,6 +147,19 @@ struct float4x4 { return scale; } + void apply_scale(const float scale) + { + values[0][0] *= scale; + values[0][1] *= scale; + values[0][2] *= scale; + values[1][0] *= scale; + values[1][1] *= scale; + values[1][2] *= scale; + values[2][0] *= scale; + values[2][1] *= scale; + values[2][2] *= scale; + } + float4x4 inverted() const { float4x4 result; diff --git a/source/blender/blenlib/BLI_mesh_intersect.hh b/source/blender/blenlib/BLI_mesh_intersect.hh index a7996939bb1..7000349c5af 100644 --- a/source/blender/blenlib/BLI_mesh_intersect.hh +++ b/source/blender/blenlib/BLI_mesh_intersect.hh @@ -357,6 +357,9 @@ IMesh trimesh_nary_intersect(const IMesh &tm_in, bool use_self, IMeshArena *arena); +/** Return an IMesh that is a triangulation of a mesh with general polygonal faces. */ +IMesh triangulate_polymesh(IMesh &imesh, IMeshArena *arena); + /** This has the side effect of populating verts in the #IMesh. */ void write_obj_mesh(IMesh &m, const std::string &objname); diff --git a/source/blender/blenlib/intern/BLI_dial_2d.c b/source/blender/blenlib/intern/BLI_dial_2d.c index b5f1d7818cf..786fdd219a3 100644 --- a/source/blender/blenlib/intern/BLI_dial_2d.c +++ b/source/blender/blenlib/intern/BLI_dial_2d.c @@ -78,7 +78,7 @@ float BLI_dial_angle(Dial *dial, const float current_position[2]) cosval = dot_v2v2(current_direction, dial->initial_direction); sinval = cross_v2v2(current_direction, dial->initial_direction); - /* clamp to avoid nans in #acos */ + /* Clamp to avoid NAN's in #acos */ angle = atan2f(sinval, cosval); /* change of sign, we passed the 180 degree threshold. This means we need to add a turn. diff --git a/source/blender/blenlib/intern/mesh_boolean.cc b/source/blender/blenlib/intern/mesh_boolean.cc index 16f2220af4c..25291b8c3b0 100644 --- a/source/blender/blenlib/intern/mesh_boolean.cc +++ b/source/blender/blenlib/intern/mesh_boolean.cc @@ -38,7 +38,6 @@ # include "BLI_math_mpq.hh" # include "BLI_mesh_intersect.hh" # include "BLI_mpq3.hh" -# include "BLI_polyfill_2d.h" # include "BLI_set.hh" # include "BLI_span.hh" # include "BLI_stack.hh" @@ -2680,224 +2679,10 @@ static IMesh raycast_patches_boolean(const IMesh &tm, } } BLI_bvhtree_free(tree); - ans.set_faces(out_faces); - return ans; -} -/** - * Tessellate face f into triangles and return an array of `const Face *` - * giving that triangulation. Intended to be used when f has > 4 vertices. - * Care is taken so that the original edge index associated with - * each edge in the output triangles either matches the original edge - * for the (identical) edge of f, or else is -1. So diagonals added - * for triangulation can later be identified by having #NO_INDEX for original. - * - * This code uses Blenlib's BLI_polyfill_calc to do triangulation, and is therefore quite fast. - * Unfortunately, it can product degenerate triangles that mesh_intersect will remove, leaving - * the mesh non-PWN. - */ -static Array<Face *> polyfill_triangulate_poly(Face *f, IMeshArena *arena) -{ - /* Similar to loop body in BM_mesh_calc_tesselation. */ - int flen = f->size(); - BLI_assert(flen > 4); - if (!f->plane_populated()) { - f->populate_plane(false); - } - /* Project along negative face normal so (x,y) can be used in 2d. */ - const double3 &poly_normal = f->plane->norm; - float no[3] = {float(poly_normal[0]), float(poly_normal[1]), float(poly_normal[2])}; - normalize_v3(no); - float axis_mat[3][3]; - float(*projverts)[2]; - unsigned int(*tris)[3]; - const int totfilltri = flen - 2; - /* Prepare projected vertices and array to receive triangles in tesselation. */ - tris = static_cast<unsigned int(*)[3]>(MEM_malloc_arrayN(totfilltri, sizeof(*tris), __func__)); - projverts = static_cast<float(*)[2]>(MEM_malloc_arrayN(flen, sizeof(*projverts), __func__)); - axis_dominant_v3_to_m3_negate(axis_mat, no); - for (int j = 0; j < flen; ++j) { - const double3 &dco = (*f)[j]->co; - float co[3] = {float(dco[0]), float(dco[1]), float(dco[2])}; - mul_v2_m3v3(projverts[j], axis_mat, co); - } - BLI_polyfill_calc(projverts, flen, 1, tris); - /* Put tesselation triangles into Face form. Record original edges where they exist. */ - Array<Face *> ans(totfilltri); - for (int t = 0; t < totfilltri; ++t) { - unsigned int *tri = tris[t]; - int eo[3]; - const Vert *v[3]; - for (int k = 0; k < 3; k++) { - BLI_assert(tri[k] < flen); - v[k] = (*f)[tri[k]]; - /* If tri edge goes between two successive indices in - * the original face, then it is an original edge. */ - if ((tri[k] + 1) % flen == tri[(k + 1) % 3]) { - eo[k] = f->edge_orig[tri[k]]; - } - else { - eo[k] = NO_INDEX; - } - ans[t] = arena->add_face( - {v[0], v[1], v[2]}, f->orig, {eo[0], eo[1], eo[2]}, {false, false, false}); - } - } - - MEM_freeN(tris); - MEM_freeN(projverts); - - return ans; -} - -/** - * Which CDT output edge index is for an edge between output verts - * v1 and v2 (in either order)? - * \return -1 if none. - */ -static int find_cdt_edge(const CDT_result<mpq_class> &cdt_out, int v1, int v2) -{ - for (int e : cdt_out.edge.index_range()) { - const std::pair<int, int> &edge = cdt_out.edge[e]; - if ((edge.first == v1 && edge.second == v2) || (edge.first == v2 && edge.second == v1)) { - return e; - } - } - return -1; -} - -/** - * Tessellate face f into triangles and return an array of `const Face *` - * giving that triangulation. - * Care is taken so that the original edge index associated with - * each edge in the output triangles either matches the original edge - * for the (identical) edge of f, or else is -1. So diagonals added - * for triangulation can later be identified by having #NO_INDEX for original. - * - * The method used is to use the CDT triangulation. Usually that triangulation - * will only use the existing vertices. However, if the face self-intersects - * then the CDT triangulation will include the intersection points. - * If this happens, we use the polyfill triangulator instead. We don't - * use the polyfill triangulator by default because it can create degenerate - * triangles (which we can handle but they'll create non-manifold meshes). - */ -static Array<Face *> triangulate_poly(Face *f, IMeshArena *arena) -{ - int flen = f->size(); - CDT_input<mpq_class> cdt_in; - cdt_in.vert = Array<mpq2>(flen); - cdt_in.face = Array<Vector<int>>(1); - cdt_in.face[0].reserve(flen); - for (int i : f->index_range()) { - cdt_in.face[0].append(i); - } - /* Project poly along dominant axis of normal to get 2d coords. */ - if (!f->plane_populated()) { - f->populate_plane(false); - } - const double3 &poly_normal = f->plane->norm; - int axis = double3::dominant_axis(poly_normal); - /* If project down y axis as opposed to x or z, the orientation - * of the polygon will be reversed. - * Yet another reversal happens if the poly normal in the dominant - * direction is opposite that of the positive dominant axis. */ - bool rev1 = (axis == 1); - bool rev2 = poly_normal[axis] < 0; - bool rev = rev1 ^ rev2; - for (int i = 0; i < flen; ++i) { - int ii = rev ? flen - i - 1 : i; - mpq2 &p2d = cdt_in.vert[ii]; - int k = 0; - for (int j = 0; j < 3; ++j) { - if (j != axis) { - p2d[k++] = (*f)[ii]->co_exact[j]; - } - } - } - CDT_result<mpq_class> cdt_out = delaunay_2d_calc(cdt_in, CDT_INSIDE); - int n_tris = cdt_out.face.size(); - Array<Face *> ans(n_tris); - for (int t = 0; t < n_tris; ++t) { - int i_v_out[3]; - const Vert *v[3]; - int eo[3]; - bool needs_steiner = false; - for (int i = 0; i < 3; ++i) { - i_v_out[i] = cdt_out.face[t][i]; - if (cdt_out.vert_orig[i_v_out[i]].size() == 0) { - needs_steiner = true; - break; - } - v[i] = (*f)[cdt_out.vert_orig[i_v_out[i]][0]]; - } - if (needs_steiner) { - /* Fall back on the polyfill triangulator. */ - return polyfill_triangulate_poly(f, arena); - } - for (int i = 0; i < 3; ++i) { - int e_out = find_cdt_edge(cdt_out, i_v_out[i], i_v_out[(i + 1) % 3]); - BLI_assert(e_out != -1); - eo[i] = NO_INDEX; - for (int orig : cdt_out.edge_orig[e_out]) { - if (orig != NO_INDEX) { - eo[i] = orig; - break; - } - } - } - if (rev) { - ans[t] = arena->add_face( - {v[0], v[2], v[1]}, f->orig, {eo[2], eo[1], eo[0]}, {false, false, false}); - } - else { - ans[t] = arena->add_face( - {v[0], v[1], v[2]}, f->orig, {eo[0], eo[1], eo[2]}, {false, false, false}); - } - } + ans.set_faces(out_faces); return ans; } - -/** - * Return an #IMesh that is a triangulation of a mesh with general - * polygonal faces, #IMesh. - * Added diagonals will be distinguishable by having edge original - * indices of #NO_INDEX. - */ -static IMesh triangulate_polymesh(IMesh &imesh, IMeshArena *arena) -{ - Vector<Face *> face_tris; - constexpr int estimated_tris_per_face = 3; - face_tris.reserve(estimated_tris_per_face * imesh.face_size()); - for (Face *f : imesh.faces()) { - /* Tessellate face f, following plan similar to #BM_face_calc_tesselation. */ - int flen = f->size(); - if (flen == 3) { - face_tris.append(f); - } - else if (flen == 4) { - const Vert *v0 = (*f)[0]; - const Vert *v1 = (*f)[1]; - const Vert *v2 = (*f)[2]; - const Vert *v3 = (*f)[3]; - int eo_01 = f->edge_orig[0]; - int eo_12 = f->edge_orig[1]; - int eo_23 = f->edge_orig[2]; - int eo_30 = f->edge_orig[3]; - Face *f0 = arena->add_face({v0, v1, v2}, f->orig, {eo_01, eo_12, -1}, {false, false, false}); - Face *f1 = arena->add_face({v0, v2, v3}, f->orig, {-1, eo_23, eo_30}, {false, false, false}); - face_tris.append(f0); - face_tris.append(f1); - } - else { - Array<Face *> tris = triangulate_poly(f, arena); - for (Face *tri : tris) { - face_tris.append(tri); - } - } - } - return IMesh(face_tris); -} - /** * If \a tri1 and \a tri2 have a common edge (in opposite orientation), * return the indices into \a tri1 and \a tri2 where that common edge starts. Else return (-1,-1). diff --git a/source/blender/blenlib/intern/mesh_intersect.cc b/source/blender/blenlib/intern/mesh_intersect.cc index ce3a5b55f98..636209883c3 100644 --- a/source/blender/blenlib/intern/mesh_intersect.cc +++ b/source/blender/blenlib/intern/mesh_intersect.cc @@ -39,6 +39,7 @@ # include "BLI_math_mpq.hh" # include "BLI_mpq2.hh" # include "BLI_mpq3.hh" +# include "BLI_polyfill_2d.h" # include "BLI_set.hh" # include "BLI_span.hh" # include "BLI_task.h" @@ -1576,6 +1577,9 @@ struct CDT_data { Vector<bool> is_reversed; /** Result of running CDT on input with (vert, edge, face). */ CDT_result<mpq_class> cdt_out; + /** To speed up get_cdt_edge_orig, sometimes populate this map from vertex pair to output edge. + */ + Map<std::pair<int, int>, int> verts_to_edge; int proj_axis; }; @@ -1734,6 +1738,30 @@ static CDT_data prepare_cdt_input_for_cluster(const IMesh &tm, return ans; } +/* Return a copy of the argument with the integers ordered in ascending order. */ +static inline std::pair<int, int> sorted_int_pair(std::pair<int, int> pair) +{ + if (pair.first <= pair.second) { + return pair; + } + return std::pair<int, int>(pair.second, pair.first); +} + +/** + * Build cd.verts_to_edge to map from a pair of cdt output indices to an index in cd.cdt_out.edge. + * Order the vertex indices so that the smaller one is first in the pair. + */ +static void populate_cdt_edge_map(Map<std::pair<int, int>, int> &verts_to_edge, + const CDT_result<mpq_class> &cdt_out) +{ + verts_to_edge.reserve(cdt_out.edge.size()); + for (int e : cdt_out.edge.index_range()) { + std::pair<int, int> vpair = sorted_int_pair(cdt_out.edge[e]); + /* There should be only one edge for each vertex pair. */ + verts_to_edge.add(vpair, e); + } +} + /** * Fills in cd.cdt_out with result of doing the cdt calculation on (vert, edge, face). */ @@ -1766,6 +1794,10 @@ static void do_cdt(CDT_data &cd) } cdt_in.epsilon = 0; /* TODO: needs attention for non-exact T. */ cd.cdt_out = blender::meshintersect::delaunay_2d_calc(cdt_in, CDT_INSIDE); + constexpr int make_edge_map_threshold = 15; + if (cd.cdt_out.edge.size() >= make_edge_map_threshold) { + populate_cdt_edge_map(cd.verts_to_edge, cd.cdt_out); + } if (dbg_level > 0) { std::cout << "\nCDT result\nVerts:\n"; for (int i : cd.cdt_out.vert.index_range()) { @@ -1802,39 +1834,52 @@ static int get_cdt_edge_orig( { int foff = cd.cdt_out.face_edge_offset; *r_is_intersect = false; - for (int e : cd.cdt_out.edge.index_range()) { - std::pair<int, int> edge = cd.cdt_out.edge[e]; - if ((edge.first == i0 && edge.second == i1) || (edge.first == i1 && edge.second == i0)) { - /* Pick an arbitrary orig, but not one equal to NO_INDEX, if we can help it. */ - /* TODO: if edge has origs from more than on part of the nary input, - * then want to set *r_is_intersect to true. */ - for (int orig_index : cd.cdt_out.edge_orig[e]) { - /* orig_index encodes the triangle and pos within the triangle of the input edge. */ - if (orig_index >= foff) { - int in_face_index = (orig_index / foff) - 1; - int pos = orig_index % foff; - /* We need to retrieve the edge orig field from the Face used to populate the - * in_face_index'th face of the CDT, at the pos'th position of the face. */ - int in_tm_face_index = cd.input_face[in_face_index]; - BLI_assert(in_tm_face_index < in_tm.face_size()); - const Face *facep = in_tm.face(in_tm_face_index); - BLI_assert(pos < facep->size()); - bool is_rev = cd.is_reversed[in_face_index]; - int eorig = is_rev ? facep->edge_orig[2 - pos] : facep->edge_orig[pos]; - if (eorig != NO_INDEX) { - return eorig; - } - } - else { - /* This edge came from an edge input to the CDT problem, - * so it is an intersect edge. */ - *r_is_intersect = true; - /* TODO: maybe there is an orig index: - * This happens if an input edge was formed by an input face having - * an edge that is co-planar with the cluster, while the face as a whole is not. */ - return NO_INDEX; - } + int e = NO_INDEX; + if (cd.verts_to_edge.size() > 0) { + /* Use the populated map to find the edge, if any, between vertices i0 and i1. */ + std::pair<int, int> vpair = sorted_int_pair(std::pair<int, int>(i0, i1)); + e = cd.verts_to_edge.lookup_default(vpair, NO_INDEX); + } + else { + for (int ee : cd.cdt_out.edge.index_range()) { + std::pair<int, int> edge = cd.cdt_out.edge[ee]; + if ((edge.first == i0 && edge.second == i1) || (edge.first == i1 && edge.second == i0)) { + e = ee; + break; } + } + } + if (e == NO_INDEX) { + return NO_INDEX; + } + + /* Pick an arbitrary orig, but not one equal to NO_INDEX, if we can help it. */ + /* TODO: if edge has origs from more than on part of the nary input, + * then want to set *r_is_intersect to true. */ + for (int orig_index : cd.cdt_out.edge_orig[e]) { + /* orig_index encodes the triangle and pos within the triangle of the input edge. */ + if (orig_index >= foff) { + int in_face_index = (orig_index / foff) - 1; + int pos = orig_index % foff; + /* We need to retrieve the edge orig field from the Face used to populate the + * in_face_index'th face of the CDT, at the pos'th position of the face. */ + int in_tm_face_index = cd.input_face[in_face_index]; + BLI_assert(in_tm_face_index < in_tm.face_size()); + const Face *facep = in_tm.face(in_tm_face_index); + BLI_assert(pos < facep->size()); + bool is_rev = cd.is_reversed[in_face_index]; + int eorig = is_rev ? facep->edge_orig[2 - pos] : facep->edge_orig[pos]; + if (eorig != NO_INDEX) { + return eorig; + } + } + else { + /* This edge came from an edge input to the CDT problem, + * so it is an intersect edge. */ + *r_is_intersect = true; + /* TODO: maybe there is an orig index: + * This happens if an input edge was formed by an input face having + * an edge that is co-planar with the cluster, while the face as a whole is not. */ return NO_INDEX; } } @@ -1842,6 +1887,256 @@ static int get_cdt_edge_orig( } /** + * Make a Face from the CDT output triangle cdt_out_t, which has corresponding input triangle + * cdt_in_t. The triangle indices that are in cd.input_face are from IMesh tm. + * Return a pointer to the newly constructed Face. + */ +static Face *cdt_tri_as_imesh_face( + int cdt_out_t, int cdt_in_t, const CDT_data &cd, const IMesh &tm, IMeshArena *arena) +{ + const CDT_result<mpq_class> &cdt_out = cd.cdt_out; + int t_orig = tm.face(cd.input_face[cdt_in_t])->orig; + BLI_assert(cdt_out.face[cdt_out_t].size() == 3); + int i0 = cdt_out.face[cdt_out_t][0]; + int i1 = cdt_out.face[cdt_out_t][1]; + int i2 = cdt_out.face[cdt_out_t][2]; + mpq3 v0co = unproject_cdt_vert(cd, cdt_out.vert[i0]); + mpq3 v1co = unproject_cdt_vert(cd, cdt_out.vert[i1]); + mpq3 v2co = unproject_cdt_vert(cd, cdt_out.vert[i2]); + /* No need to provide an original index: if coord matches + * an original one, then it will already be in the arena + * with the correct orig field. */ + const Vert *v0 = arena->add_or_find_vert(v0co, NO_INDEX); + const Vert *v1 = arena->add_or_find_vert(v1co, NO_INDEX); + const Vert *v2 = arena->add_or_find_vert(v2co, NO_INDEX); + Face *facep; + bool is_isect0; + bool is_isect1; + bool is_isect2; + if (cd.is_reversed[cdt_in_t]) { + int oe0 = get_cdt_edge_orig(i0, i2, cd, tm, &is_isect0); + int oe1 = get_cdt_edge_orig(i2, i1, cd, tm, &is_isect1); + int oe2 = get_cdt_edge_orig(i1, i0, cd, tm, &is_isect2); + facep = arena->add_face( + {v0, v2, v1}, t_orig, {oe0, oe1, oe2}, {is_isect0, is_isect1, is_isect2}); + } + else { + int oe0 = get_cdt_edge_orig(i0, i1, cd, tm, &is_isect0); + int oe1 = get_cdt_edge_orig(i1, i2, cd, tm, &is_isect1); + int oe2 = get_cdt_edge_orig(i2, i0, cd, tm, &is_isect2); + facep = arena->add_face( + {v0, v1, v2}, t_orig, {oe0, oe1, oe2}, {is_isect0, is_isect1, is_isect2}); + } + facep->populate_plane(false); + return facep; +} + +/** + * Tessellate face f into triangles and return an array of `const Face *` + * giving that triangulation. Intended to be used when f has > 4 vertices. + * Care is taken so that the original edge index associated with + * each edge in the output triangles either matches the original edge + * for the (identical) edge of f, or else is -1. So diagonals added + * for triangulation can later be identified by having #NO_INDEX for original. + * + * This code uses Blenlib's BLI_polyfill_calc to do triangulation, and is therefore quite fast. + * Unfortunately, it can product degenerate triangles that mesh_intersect will remove, leaving + * the mesh non-PWN. + */ +static Array<Face *> polyfill_triangulate_poly(Face *f, IMeshArena *arena) +{ + /* Similar to loop body in BM_mesh_calc_tesselation. */ + int flen = f->size(); + BLI_assert(flen > 4); + if (!f->plane_populated()) { + f->populate_plane(false); + } + /* Project along negative face normal so (x,y) can be used in 2d. */ + const double3 &poly_normal = f->plane->norm; + float no[3] = {float(poly_normal[0]), float(poly_normal[1]), float(poly_normal[2])}; + normalize_v3(no); + float axis_mat[3][3]; + float(*projverts)[2]; + unsigned int(*tris)[3]; + const int totfilltri = flen - 2; + /* Prepare projected vertices and array to receive triangles in tesselation. */ + tris = static_cast<unsigned int(*)[3]>(MEM_malloc_arrayN(totfilltri, sizeof(*tris), __func__)); + projverts = static_cast<float(*)[2]>(MEM_malloc_arrayN(flen, sizeof(*projverts), __func__)); + axis_dominant_v3_to_m3_negate(axis_mat, no); + for (int j = 0; j < flen; ++j) { + const double3 &dco = (*f)[j]->co; + float co[3] = {float(dco[0]), float(dco[1]), float(dco[2])}; + mul_v2_m3v3(projverts[j], axis_mat, co); + } + BLI_polyfill_calc(projverts, flen, 1, tris); + /* Put tesselation triangles into Face form. Record original edges where they exist. */ + Array<Face *> ans(totfilltri); + for (int t = 0; t < totfilltri; ++t) { + unsigned int *tri = tris[t]; + int eo[3]; + const Vert *v[3]; + for (int k = 0; k < 3; k++) { + BLI_assert(tri[k] < flen); + v[k] = (*f)[tri[k]]; + /* If tri edge goes between two successive indices in + * the original face, then it is an original edge. */ + if ((tri[k] + 1) % flen == tri[(k + 1) % 3]) { + eo[k] = f->edge_orig[tri[k]]; + } + else { + eo[k] = NO_INDEX; + } + ans[t] = arena->add_face( + {v[0], v[1], v[2]}, f->orig, {eo[0], eo[1], eo[2]}, {false, false, false}); + } + } + + MEM_freeN(tris); + MEM_freeN(projverts); + + return ans; +} + +/** + * Tessellate face f into triangles and return an array of `const Face *` + * giving that triangulation. + * Care is taken so that the original edge index associated with + * each edge in the output triangles either matches the original edge + * for the (identical) edge of f, or else is -1. So diagonals added + * for triangulation can later be identified by having #NO_INDEX for original. + * + * The method used is to use the CDT triangulation. Usually that triangulation + * will only use the existing vertices. However, if the face self-intersects + * then the CDT triangulation will include the intersection points. + * If this happens, we use the polyfill triangulator instead. We don't + * use the polyfill triangulator by default because it can create degenerate + * triangles (which we can handle but they'll create non-manifold meshes). + */ +static Array<Face *> triangulate_poly(Face *f, IMeshArena *arena) +{ + int flen = f->size(); + CDT_input<mpq_class> cdt_in; + cdt_in.vert = Array<mpq2>(flen); + cdt_in.face = Array<Vector<int>>(1); + cdt_in.face[0].reserve(flen); + for (int i : f->index_range()) { + cdt_in.face[0].append(i); + } + /* Project poly along dominant axis of normal to get 2d coords. */ + if (!f->plane_populated()) { + f->populate_plane(false); + } + const double3 &poly_normal = f->plane->norm; + int axis = double3::dominant_axis(poly_normal); + /* If project down y axis as opposed to x or z, the orientation + * of the polygon will be reversed. + * Yet another reversal happens if the poly normal in the dominant + * direction is opposite that of the positive dominant axis. */ + bool rev1 = (axis == 1); + bool rev2 = poly_normal[axis] < 0; + bool rev = rev1 ^ rev2; + for (int i = 0; i < flen; ++i) { + int ii = rev ? flen - i - 1 : i; + mpq2 &p2d = cdt_in.vert[ii]; + int k = 0; + for (int j = 0; j < 3; ++j) { + if (j != axis) { + p2d[k++] = (*f)[ii]->co_exact[j]; + } + } + } + CDT_result<mpq_class> cdt_out = delaunay_2d_calc(cdt_in, CDT_INSIDE); + int n_tris = cdt_out.face.size(); + Array<Face *> ans(n_tris); + for (int t = 0; t < n_tris; ++t) { + int i_v_out[3]; + const Vert *v[3]; + int eo[3]; + bool needs_steiner = false; + for (int i = 0; i < 3; ++i) { + i_v_out[i] = cdt_out.face[t][i]; + if (cdt_out.vert_orig[i_v_out[i]].size() == 0) { + needs_steiner = true; + break; + } + v[i] = (*f)[cdt_out.vert_orig[i_v_out[i]][0]]; + } + if (needs_steiner) { + /* Fall back on the polyfill triangulator. */ + return polyfill_triangulate_poly(f, arena); + } + Map<std::pair<int, int>, int> verts_to_edge; + populate_cdt_edge_map(verts_to_edge, cdt_out); + int foff = cdt_out.face_edge_offset; + for (int i = 0; i < 3; ++i) { + std::pair<int, int> vpair(i_v_out[i], i_v_out[(i + 1) % 3]); + std::pair<int, int> vpair_canon = sorted_int_pair(vpair); + int e_out = verts_to_edge.lookup_default(vpair_canon, NO_INDEX); + BLI_assert(e_out != NO_INDEX); + eo[i] = NO_INDEX; + for (int orig : cdt_out.edge_orig[e_out]) { + if (orig >= foff) { + int pos = orig % foff; + BLI_assert(pos < f->size()); + eo[i] = f->edge_orig[pos]; + break; + } + } + } + if (rev) { + ans[t] = arena->add_face( + {v[0], v[2], v[1]}, f->orig, {eo[2], eo[1], eo[0]}, {false, false, false}); + } + else { + ans[t] = arena->add_face( + {v[0], v[1], v[2]}, f->orig, {eo[0], eo[1], eo[2]}, {false, false, false}); + } + } + return ans; +} + +/** + * Return an #IMesh that is a triangulation of a mesh with general + * polygonal faces, #IMesh. + * Added diagonals will be distinguishable by having edge original + * indices of #NO_INDEX. + */ +IMesh triangulate_polymesh(IMesh &imesh, IMeshArena *arena) +{ + Vector<Face *> face_tris; + constexpr int estimated_tris_per_face = 3; + face_tris.reserve(estimated_tris_per_face * imesh.face_size()); + for (Face *f : imesh.faces()) { + /* Tessellate face f, following plan similar to #BM_face_calc_tesselation. */ + int flen = f->size(); + if (flen == 3) { + face_tris.append(f); + } + else if (flen == 4) { + const Vert *v0 = (*f)[0]; + const Vert *v1 = (*f)[1]; + const Vert *v2 = (*f)[2]; + const Vert *v3 = (*f)[3]; + int eo_01 = f->edge_orig[0]; + int eo_12 = f->edge_orig[1]; + int eo_23 = f->edge_orig[2]; + int eo_30 = f->edge_orig[3]; + Face *f0 = arena->add_face({v0, v1, v2}, f->orig, {eo_01, eo_12, -1}, {false, false, false}); + Face *f1 = arena->add_face({v0, v2, v3}, f->orig, {-1, eo_23, eo_30}, {false, false, false}); + face_tris.append(f0); + face_tris.append(f1); + } + else { + Array<Face *> tris = triangulate_poly(f, arena); + for (Face *tri : tris) { + face_tris.append(tri); + } + } + } + return IMesh(face_tris); +} + +/** * Using the result of CDT in cd.cdt_out, extract an #IMesh representing the subdivision * of input triangle t, which should be an element of cd.input_face. */ @@ -1862,55 +2157,17 @@ static IMesh extract_subdivided_tri(const CDT_data &cd, BLI_assert(false); return IMesh(); } - int t_orig = in_tm.face(t)->orig; constexpr int inline_buf_size = 20; Vector<Face *, inline_buf_size> faces; for (int f : cdt_out.face.index_range()) { if (cdt_out.face_orig[f].contains(t_in_cdt)) { - BLI_assert(cdt_out.face[f].size() == 3); - int i0 = cdt_out.face[f][0]; - int i1 = cdt_out.face[f][1]; - int i2 = cdt_out.face[f][2]; - mpq3 v0co = unproject_cdt_vert(cd, cdt_out.vert[i0]); - mpq3 v1co = unproject_cdt_vert(cd, cdt_out.vert[i1]); - mpq3 v2co = unproject_cdt_vert(cd, cdt_out.vert[i2]); - /* No need to provide an original index: if coord matches - * an original one, then it will already be in the arena - * with the correct orig field. */ - const Vert *v0 = arena->add_or_find_vert(v0co, NO_INDEX); - const Vert *v1 = arena->add_or_find_vert(v1co, NO_INDEX); - const Vert *v2 = arena->add_or_find_vert(v2co, NO_INDEX); - Face *facep; - bool is_isect0; - bool is_isect1; - bool is_isect2; - if (cd.is_reversed[t_in_cdt]) { - int oe0 = get_cdt_edge_orig(i0, i2, cd, in_tm, &is_isect0); - int oe1 = get_cdt_edge_orig(i2, i1, cd, in_tm, &is_isect1); - int oe2 = get_cdt_edge_orig(i1, i0, cd, in_tm, &is_isect2); - facep = arena->add_face( - {v0, v2, v1}, t_orig, {oe0, oe1, oe2}, {is_isect0, is_isect1, is_isect2}); - } - else { - int oe0 = get_cdt_edge_orig(i0, i1, cd, in_tm, &is_isect0); - int oe1 = get_cdt_edge_orig(i1, i2, cd, in_tm, &is_isect1); - int oe2 = get_cdt_edge_orig(i2, i0, cd, in_tm, &is_isect2); - facep = arena->add_face( - {v0, v1, v2}, t_orig, {oe0, oe1, oe2}, {is_isect0, is_isect1, is_isect2}); - } - facep->populate_plane(false); + Face *facep = cdt_tri_as_imesh_face(f, t_in_cdt, cd, in_tm, arena); faces.append(facep); } } return IMesh(faces); } -static IMesh extract_single_tri(const IMesh &tm, int t) -{ - Face *f = tm.face(t); - return IMesh({f}); -} - static bool bvhtreeverlap_cmp(const BVHTreeOverlap &a, const BVHTreeOverlap &b) { if (a.indexA < b.indexA) { @@ -2126,7 +2383,7 @@ static void calc_overlap_itts(Map<std::pair<int, int>, ITT_value> &itt_map, } /** - * Data needed for parallelization of calc_subdivided_tris. + * Data needed for parallelization of calc_subdivided_non_cluster_tris. */ struct OverlapTriRange { int tri_index; @@ -2203,14 +2460,13 @@ static void calc_subdivided_tri_range_func(void *__restrict userdata, * r_tri_subdivided with the result of intersecting it with * all the other triangles in the mesh, if it intersects any others. * But don't do this for triangles that are part of a cluster. - * Also, do nothing here if the answer is just the triangle itself. */ -static void calc_subdivided_tris(Array<IMesh> &r_tri_subdivided, - const IMesh &tm, - const Map<std::pair<int, int>, ITT_value> &itt_map, - const CoplanarClusterInfo &clinfo, - const TriOverlaps &ov, - IMeshArena *arena) +static void calc_subdivided_non_cluster_tris(Array<IMesh> &r_tri_subdivided, + const IMesh &tm, + const Map<std::pair<int, int>, ITT_value> &itt_map, + const CoplanarClusterInfo &clinfo, + const TriOverlaps &ov, + IMeshArena *arena) { const int dbg_level = 0; if (dbg_level > 0) { @@ -2250,6 +2506,54 @@ static void calc_subdivided_tris(Array<IMesh> &r_tri_subdivided, settings.use_threading = intersect_use_threading; BLI_task_parallel_range( 0, overlap_tri_range_tot, &data, calc_subdivided_tri_range_func, &settings); + /* Now have to put in the triangles that are the same as the input ones, and not in clusters. + */ + for (int t : tm.face_index_range()) { + if (r_tri_subdivided[t].face_size() == 0 && clinfo.tri_cluster(t) == NO_INDEX) { + r_tri_subdivided[t] = IMesh({tm.face(t)}); + } + } +} + +/** + * For each cluster in clinfo, extract the triangles from the cluster + * that correspond to each original triangle t that is part of the cluster, + * and put the resulting triangles into an IMesh in tri_subdivided[t]. + * We have already done the CDT for the triangles in the cluster, whose + * result is in cluster_subdivided[c] for each cluster c. + */ +static void calc_cluster_tris(Array<IMesh> &tri_subdivided, + const IMesh &tm, + const CoplanarClusterInfo &clinfo, + const Array<CDT_data> &cluster_subdivided, + IMeshArena *arena) +{ + for (int c : clinfo.index_range()) { + const CoplanarCluster &cl = clinfo.cluster(c); + const CDT_data &cd = cluster_subdivided[c]; + /* Each triangle in cluster c should be an input triangle in cd.input_faces. + * (See prepare_cdt_input_for_cluster.) + * So accumulate a Vector of Face* for each input face by going through the + * output faces and making a Face for each input face that it is part of. + * (The Boolean algorithm wants duplicates if a given output triangle is part + * of more than one input triangle.) + */ + int n_cluster_tris = cl.tot_tri(); + const CDT_result<mpq_class> &cdt_out = cd.cdt_out; + BLI_assert(cd.input_face.size() == n_cluster_tris); + Array<Vector<Face *>> face_vec(n_cluster_tris); + for (int cdt_out_t : cdt_out.face.index_range()) { + for (int cdt_in_t : cdt_out.face_orig[cdt_out_t]) { + Face *f = cdt_tri_as_imesh_face(cdt_out_t, cdt_in_t, cd, tm, arena); + face_vec[cdt_in_t].append(f); + } + } + for (int cdt_in_t : cd.input_face.index_range()) { + int tm_t = cd.input_face[cdt_in_t]; + BLI_assert(tri_subdivided[tm_t].face_size() == 0); + tri_subdivided[tm_t] = IMesh(face_vec[cdt_in_t]); + } + } } static CDT_data calc_cluster_subdivided(const CoplanarClusterInfo &clinfo, @@ -2622,10 +2926,11 @@ IMesh trimesh_nary_intersect(const IMesh &tm_in, doperfmax(2, tri_ov.overlap().size()); # endif Array<IMesh> tri_subdivided(tm_clean->face_size()); - calc_subdivided_tris(tri_subdivided, *tm_clean, itt_map, clinfo, tri_ov, arena); + calc_subdivided_non_cluster_tris(tri_subdivided, *tm_clean, itt_map, clinfo, tri_ov, arena); # ifdef PERFDEBUG double subdivided_tris_time = PIL_check_seconds_timer(); - std::cout << "subdivided tris found, time = " << subdivided_tris_time - itt_time << "\n"; + std::cout << "subdivided non-cluster tris found, time = " << subdivided_tris_time - itt_time + << "\n"; # endif Array<CDT_data> cluster_subdivided(clinfo.tot_cluster()); for (int c : clinfo.index_range()) { @@ -2636,19 +2941,11 @@ IMesh trimesh_nary_intersect(const IMesh &tm_in, std::cout << "subdivided clusters found, time = " << cluster_subdivide_time - subdivided_tris_time << "\n"; # endif - for (int t : tm_clean->face_index_range()) { - int c = clinfo.tri_cluster(t); - if (c != NO_INDEX) { - BLI_assert(tri_subdivided[t].face_size() == 0); - tri_subdivided[t] = extract_subdivided_tri(cluster_subdivided[c], *tm_clean, t, arena); - } - else if (tri_subdivided[t].face_size() == 0) { - tri_subdivided[t] = extract_single_tri(*tm_clean, t); - } - } + calc_cluster_tris(tri_subdivided, *tm_clean, clinfo, cluster_subdivided, arena); # ifdef PERFDEBUG double extract_time = PIL_check_seconds_timer(); - std::cout << "triangles extracted, time = " << extract_time - cluster_subdivide_time << "\n"; + std::cout << "subdivided cluster tris found, time = " << extract_time - cluster_subdivide_time + << "\n"; # endif IMesh combined = union_tri_subdivides(tri_subdivided); if (dbg_level > 1) { diff --git a/source/blender/blenlib/intern/storage.c b/source/blender/blenlib/intern/storage.c index 287334a34ee..cb2634e6fda 100644 --- a/source/blender/blenlib/intern/storage.c +++ b/source/blender/blenlib/intern/storage.c @@ -299,12 +299,16 @@ bool BLI_file_alias_target(const char *filepath, return false; } - IShellLinkW *Shortcut = NULL; - bool success = false; - CoInitializeEx(NULL, COINIT_MULTITHREADED); + HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED); + if (FAILED(hr)) { + return false; + } - HRESULT hr = CoCreateInstance( + IShellLinkW *Shortcut = NULL; + hr = CoCreateInstance( &CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, &IID_IShellLinkW, (LPVOID *)&Shortcut); + + bool success = false; if (SUCCEEDED(hr)) { IPersistFile *PersistFile; hr = Shortcut->lpVtbl->QueryInterface(Shortcut, &IID_IPersistFile, (LPVOID *)&PersistFile); @@ -328,6 +332,7 @@ bool BLI_file_alias_target(const char *filepath, Shortcut->lpVtbl->Release(Shortcut); } + CoUninitialize(); return (success && r_targetpath[0]); # else UNUSED_VARS(r_targetpath, filepath); diff --git a/source/blender/blenloader/intern/readfile.c b/source/blender/blenloader/intern/readfile.c index b4623425582..fe7d50bfa15 100644 --- a/source/blender/blenloader/intern/readfile.c +++ b/source/blender/blenloader/intern/readfile.c @@ -111,6 +111,7 @@ #include "SEQ_iterator.h" #include "SEQ_modifier.h" #include "SEQ_sequencer.h" +#include "SEQ_utils.h" #include "readfile.h" @@ -2696,7 +2697,7 @@ static int lib_link_seq_clipboard_cb(Sequence *seq, void *arg_pt) static void lib_link_clipboard_restore(struct IDNameLib_Map *id_map) { /* update IDs stored in sequencer clipboard */ - SEQ_iterator_seqbase_recursive_apply(&seqbase_clipboard, lib_link_seq_clipboard_cb, id_map); + SEQ_seqbase_recursive_apply(&seqbase_clipboard, lib_link_seq_clipboard_cb, id_map); } static int lib_link_main_data_restore_cb(LibraryIDLinkCallbackData *cb_data) @@ -2912,7 +2913,7 @@ static void lib_link_workspace_layout_restore(struct IDNameLib_Map *id_map, else if (sl->spacetype == SPACE_TEXT) { SpaceText *st = (SpaceText *)sl; - st->text = restore_pointer_by_name(id_map, (ID *)st->text, USER_REAL); + st->text = restore_pointer_by_name(id_map, (ID *)st->text, USER_IGNORE); if (st->text == NULL) { st->text = newmain->texts.first; } diff --git a/source/blender/blenloader/intern/versioning_290.c b/source/blender/blenloader/intern/versioning_290.c index 916c4bf0cad..2b6f44c694b 100644 --- a/source/blender/blenloader/intern/versioning_290.c +++ b/source/blender/blenloader/intern/versioning_290.c @@ -48,6 +48,7 @@ #include "DNA_screen_types.h" #include "DNA_shader_fx_types.h" #include "DNA_space_types.h" +#include "DNA_text_types.h" #include "DNA_tracking_types.h" #include "DNA_workspace_types.h" @@ -686,6 +687,15 @@ void do_versions_after_linking_290(Main *bmain, ReportList *UNUSED(reports)) } } + if (!MAIN_VERSION_ATLEAST(bmain, 293, 20)) { + /* Set zero user text objects to have a fake user. */ + LISTBASE_FOREACH (Text *, text, &bmain->texts) { + if (text->id.us == 0) { + id_fake_user_set(&text->id); + } + } + } + /** * Versioning code until next subversion bump goes here. * diff --git a/source/blender/blenloader/intern/versioning_300.c b/source/blender/blenloader/intern/versioning_300.c index 6b13b21f057..182231b5878 100644 --- a/source/blender/blenloader/intern/versioning_300.c +++ b/source/blender/blenloader/intern/versioning_300.c @@ -23,16 +23,27 @@ #include "BLI_listbase.h" #include "BLI_utildefines.h" +#include "DNA_brush_types.h" #include "DNA_genfile.h" #include "DNA_modifier_types.h" +#include "DNA_text_types.h" +#include "BKE_lib_id.h" #include "BKE_main.h" #include "BLO_readfile.h" #include "readfile.h" -void do_versions_after_linking_300(Main *UNUSED(bmain), ReportList *UNUSED(reports)) +void do_versions_after_linking_300(Main *bmain, ReportList *UNUSED(reports)) { + if (MAIN_VERSION_ATLEAST(bmain, 300, 0) && !MAIN_VERSION_ATLEAST(bmain, 300, 1)) { + /* Set zero user text objects to have a fake user. */ + LISTBASE_FOREACH (Text *, text, &bmain->texts) { + if (text->id.us == 0) { + id_fake_user_set(&text->id); + } + } + } /** * Versioning code until next subversion bump goes here. * @@ -51,19 +62,7 @@ void do_versions_after_linking_300(Main *UNUSED(bmain), ReportList *UNUSED(repor /* NOLINTNEXTLINE: readability-function-size */ void blo_do_versions_300(FileData *fd, Library *UNUSED(lib), Main *bmain) { - - /** - * Versioning code until next subversion bump goes here. - * - * \note Be sure to check when bumping the version: - * - "versioning_userdef.c", #blo_do_versions_userdef - * - "versioning_userdef.c", #do_versions_theme - * - * \note Keep this message at the bottom of the function. - */ - { - /* Keep this block, even when empty. */ - + if (!MAIN_VERSION_ATLEAST(bmain, 300, 1)) { /* Set default value for the new bisect_threshold parameter in the mirror modifier. */ if (!DNA_struct_elem_find(fd->filesdna, "MirrorModifierData", "float", "bisect_threshold")) { LISTBASE_FOREACH (Object *, ob, &bmain->objects) { @@ -76,5 +75,25 @@ void blo_do_versions_300(FileData *fd, Library *UNUSED(lib), Main *bmain) } } } + /* Grease Pencil: Set default value for dilate pixels. */ + if (!DNA_struct_elem_find(fd->filesdna, "BrushGpencilSettings", "int", "dilate_pixels")) { + LISTBASE_FOREACH (Brush *, brush, &bmain->brushes) { + if (brush->gpencil_settings) { + brush->gpencil_settings->dilate_pixels = 1; + } + } + } + } + /** + * Versioning code until next subversion bump goes here. + * + * \note Be sure to check when bumping the version: + * - "versioning_userdef.c", #blo_do_versions_userdef + * - "versioning_userdef.c", #do_versions_theme + * + * \note Keep this message at the bottom of the function. + */ + { + /* Keep this block, even when empty. */ } } diff --git a/source/blender/compositor/intern/COM_Device.h b/source/blender/compositor/intern/COM_Device.h index 2a43c1be2b4..c848672a405 100644 --- a/source/blender/compositor/intern/COM_Device.h +++ b/source/blender/compositor/intern/COM_Device.h @@ -30,6 +30,14 @@ namespace blender::compositor { class Device { public: + Device() = default; + + Device(const Device &other) = delete; + Device(Device &&other) noexcept = default; + + Device &operator=(const Device &other) = delete; + Device &operator=(Device &&other) = delete; + /** * \brief Declaration of the virtual destructor * \note resolve warning gcc 4.7 diff --git a/source/blender/compositor/intern/COM_OpenCLDevice.cc b/source/blender/compositor/intern/COM_OpenCLDevice.cc index b96dbe91434..0f6ed0dbd2b 100644 --- a/source/blender/compositor/intern/COM_OpenCLDevice.cc +++ b/source/blender/compositor/intern/COM_OpenCLDevice.cc @@ -50,6 +50,16 @@ OpenCLDevice::OpenCLDevice(cl_context context, this->m_queue = clCreateCommandQueue(this->m_context, this->m_device, 0, &error); } +OpenCLDevice::OpenCLDevice(OpenCLDevice &&other) noexcept + : m_context(other.m_context), + m_device(other.m_device), + m_program(other.m_program), + m_queue(other.m_queue), + m_vendorID(other.m_vendorID) +{ + other.m_queue = nullptr; +} + OpenCLDevice::~OpenCLDevice() { if (this->m_queue) { diff --git a/source/blender/compositor/intern/COM_OpenCLDevice.h b/source/blender/compositor/intern/COM_OpenCLDevice.h index 355451cef68..826b0457a49 100644 --- a/source/blender/compositor/intern/COM_OpenCLDevice.h +++ b/source/blender/compositor/intern/COM_OpenCLDevice.h @@ -67,6 +67,9 @@ class OpenCLDevice : public Device { * \param vendorID: */ OpenCLDevice(cl_context context, cl_device_id device, cl_program program, cl_int vendorId); + + OpenCLDevice(OpenCLDevice &&other) noexcept; + ~OpenCLDevice(); /** diff --git a/source/blender/compositor/intern/COM_WorkScheduler.cc b/source/blender/compositor/intern/COM_WorkScheduler.cc index ee3a6dedd44..d578ac24a4a 100644 --- a/source/blender/compositor/intern/COM_WorkScheduler.cc +++ b/source/blender/compositor/intern/COM_WorkScheduler.cc @@ -263,10 +263,10 @@ static void opencl_initialize(const bool use_opencl) if (error2 != CL_SUCCESS) { printf("CLERROR[%d]: %s\n", error2, clewErrorString(error2)); } - g_work_scheduler.opencl.devices.append(OpenCLDevice(g_work_scheduler.opencl.context, - device, - g_work_scheduler.opencl.program, - vendorID)); + g_work_scheduler.opencl.devices.append_as(g_work_scheduler.opencl.context, + device, + g_work_scheduler.opencl.program, + vendorID); } } MEM_freeN(cldevices); @@ -368,7 +368,7 @@ static void threading_model_queue_initialize(const int num_cpu_threads) /* Initialize CPU threads. */ if (!g_work_scheduler.queue.initialized) { for (int index = 0; index < num_cpu_threads; index++) { - g_work_scheduler.queue.devices.append(CPUDevice(index)); + g_work_scheduler.queue.devices.append_as(index); } BLI_thread_local_create(g_thread_device); g_work_scheduler.queue.initialized = true; diff --git a/source/blender/draw/engines/eevee/eevee_materials.c b/source/blender/draw/engines/eevee/eevee_materials.c index 131f9a954cf..a4325675ea9 100644 --- a/source/blender/draw/engines/eevee/eevee_materials.c +++ b/source/blender/draw/engines/eevee/eevee_materials.c @@ -623,6 +623,11 @@ static EeveeMaterialCache material_opaque(EEVEE_Data *vedata, /* This GPUShader has already been used by another material. * Add new shading group just after to avoid shader switching cost. */ grp = DRW_shgroup_create_sub(*grp_p); + + /* Per material uniforms. */ + if (use_ssrefract) { + DRW_shgroup_uniform_float_copy(grp, "refractionDepth", ma->refract_depth); + } } else { *grp_p = grp = DRW_shgroup_create(sh, shading_pass); @@ -961,22 +966,9 @@ void EEVEE_material_renderpasses_init(EEVEE_Data *vedata) } } -static void material_renderpass_init(EEVEE_FramebufferList *fbl, - GPUTexture **output_tx, - const eGPUTextureFormat format, - const bool do_clear) +static void material_renderpass_init(GPUTexture **output_tx, const eGPUTextureFormat format) { DRW_texture_ensure_fullscreen_2d(output_tx, format, 0); - /* Clear texture. */ - if (do_clear) { - const float clear[4] = {0.0f, 0.0f, 0.0f, 0.0f}; - /* TODO(fclem): replace by GPU_texture_clear once it is fast. */ - GPU_framebuffer_texture_attach(fbl->material_accum_fb, *output_tx, 0, 0); - GPU_framebuffer_bind(fbl->material_accum_fb); - GPU_framebuffer_clear_color(fbl->material_accum_fb, clear); - GPU_framebuffer_bind(fbl->main_fb); - GPU_framebuffer_texture_detach(fbl->material_accum_fb, *output_tx); - } } void EEVEE_material_output_init(EEVEE_ViewLayerData *sldata, EEVEE_Data *vedata, uint tot_samples) @@ -991,33 +983,32 @@ void EEVEE_material_output_init(EEVEE_ViewLayerData *sldata, EEVEE_Data *vedata, /* Should be enough precision for many samples. */ const eGPUTextureFormat texture_format = (tot_samples > 128) ? GPU_RGBA32F : GPU_RGBA16F; - const bool do_clear = (effects->taa_current_sample == 1); /* Create FrameBuffer. */ GPU_framebuffer_ensure_config(&fbl->material_accum_fb, {GPU_ATTACHMENT_TEXTURE(dtxl->depth), GPU_ATTACHMENT_LEAVE}); if (pd->render_passes & EEVEE_RENDER_PASS_ENVIRONMENT) { - material_renderpass_init(fbl, &txl->env_accum, texture_format, do_clear); + material_renderpass_init(&txl->env_accum, texture_format); } if (pd->render_passes & EEVEE_RENDER_PASS_EMIT) { - material_renderpass_init(fbl, &txl->emit_accum, texture_format, do_clear); + material_renderpass_init(&txl->emit_accum, texture_format); } if (pd->render_passes & EEVEE_RENDER_PASS_DIFFUSE_COLOR) { - material_renderpass_init(fbl, &txl->diff_color_accum, texture_format, do_clear); + material_renderpass_init(&txl->diff_color_accum, texture_format); } if (pd->render_passes & EEVEE_RENDER_PASS_DIFFUSE_LIGHT) { - material_renderpass_init(fbl, &txl->diff_light_accum, texture_format, do_clear); + material_renderpass_init(&txl->diff_light_accum, texture_format); } if (pd->render_passes & EEVEE_RENDER_PASS_SPECULAR_COLOR) { - material_renderpass_init(fbl, &txl->spec_color_accum, texture_format, do_clear); + material_renderpass_init(&txl->spec_color_accum, texture_format); } if (pd->render_passes & EEVEE_RENDER_PASS_AOV) { for (int aov_index = 0; aov_index < pd->num_aovs_used; aov_index++) { - material_renderpass_init(fbl, &txl->aov_surface_accum[aov_index], texture_format, do_clear); + material_renderpass_init(&txl->aov_surface_accum[aov_index], texture_format); } } if (pd->render_passes & EEVEE_RENDER_PASS_SPECULAR_LIGHT) { - material_renderpass_init(fbl, &txl->spec_light_accum, texture_format, do_clear); + material_renderpass_init(&txl->spec_light_accum, texture_format); if (effects->enabled_effects & EFFECT_SSR) { EEVEE_reflection_output_init(sldata, vedata, tot_samples); @@ -1025,7 +1016,8 @@ void EEVEE_material_output_init(EEVEE_ViewLayerData *sldata, EEVEE_Data *vedata, } } -static void material_renderpass_accumulate(EEVEE_FramebufferList *fbl, +static void material_renderpass_accumulate(EEVEE_EffectsInfo *effects, + EEVEE_FramebufferList *fbl, DRWPass *renderpass, DRWPass *renderpass2, EEVEE_PrivateData *pd, @@ -1035,6 +1027,11 @@ static void material_renderpass_accumulate(EEVEE_FramebufferList *fbl, GPU_framebuffer_texture_attach(fbl->material_accum_fb, output_tx, 0, 0); GPU_framebuffer_bind(fbl->material_accum_fb); + if (effects->taa_current_sample == 1) { + const float clear[4] = {0.0f, 0.0f, 0.0f, 0.0f}; + GPU_framebuffer_clear_color(fbl->material_accum_fb, clear); + } + pd->renderpass_ubo = renderpass_option_ubo; DRW_draw_pass(renderpass); if (renderpass2) { @@ -1056,15 +1053,21 @@ void EEVEE_material_output_accumulate(EEVEE_ViewLayerData *sldata, EEVEE_Data *v DRWPass *material_accum_ps = psl->material_accum_ps; DRWPass *background_accum_ps = psl->background_accum_ps; if (pd->render_passes & EEVEE_RENDER_PASS_ENVIRONMENT) { - material_renderpass_accumulate( - fbl, background_accum_ps, NULL, pd, txl->env_accum, sldata->renderpass_ubo.environment); + material_renderpass_accumulate(effects, + fbl, + background_accum_ps, + NULL, + pd, + txl->env_accum, + sldata->renderpass_ubo.environment); } if (pd->render_passes & EEVEE_RENDER_PASS_EMIT) { material_renderpass_accumulate( - fbl, material_accum_ps, NULL, pd, txl->emit_accum, sldata->renderpass_ubo.emit); + effects, fbl, material_accum_ps, NULL, pd, txl->emit_accum, sldata->renderpass_ubo.emit); } if (pd->render_passes & EEVEE_RENDER_PASS_DIFFUSE_COLOR) { - material_renderpass_accumulate(fbl, + material_renderpass_accumulate(effects, + fbl, material_accum_ps, NULL, pd, @@ -1072,7 +1075,8 @@ void EEVEE_material_output_accumulate(EEVEE_ViewLayerData *sldata, EEVEE_Data *v sldata->renderpass_ubo.diff_color); } if (pd->render_passes & EEVEE_RENDER_PASS_DIFFUSE_LIGHT) { - material_renderpass_accumulate(fbl, + material_renderpass_accumulate(effects, + fbl, material_accum_ps, NULL, pd, @@ -1090,7 +1094,8 @@ void EEVEE_material_output_accumulate(EEVEE_ViewLayerData *sldata, EEVEE_Data *v sldata->common_data.ssr_toggle = false; GPU_uniformbuf_update(sldata->common_ubo, &sldata->common_data); } - material_renderpass_accumulate(fbl, + material_renderpass_accumulate(effects, + fbl, material_accum_ps, NULL, pd, @@ -1102,7 +1107,8 @@ void EEVEE_material_output_accumulate(EEVEE_ViewLayerData *sldata, EEVEE_Data *v } } if (pd->render_passes & EEVEE_RENDER_PASS_SPECULAR_LIGHT) { - material_renderpass_accumulate(fbl, + material_renderpass_accumulate(effects, + fbl, material_accum_ps, NULL, pd, @@ -1115,7 +1121,8 @@ void EEVEE_material_output_accumulate(EEVEE_ViewLayerData *sldata, EEVEE_Data *v } if (pd->render_passes & EEVEE_RENDER_PASS_AOV) { for (int aov_index = 0; aov_index < pd->num_aovs_used; aov_index++) { - material_renderpass_accumulate(fbl, + material_renderpass_accumulate(effects, + fbl, material_accum_ps, background_accum_ps, pd, diff --git a/source/blender/draw/engines/eevee/eevee_mist.c b/source/blender/draw/engines/eevee/eevee_mist.c index 2b448695528..d4490d6fd4c 100644 --- a/source/blender/draw/engines/eevee/eevee_mist.c +++ b/source/blender/draw/engines/eevee/eevee_mist.c @@ -40,12 +40,9 @@ void EEVEE_mist_output_init(EEVEE_ViewLayerData *sldata, EEVEE_Data *vedata) EEVEE_TextureList *txl = vedata->txl; EEVEE_StorageList *stl = vedata->stl; EEVEE_PassList *psl = vedata->psl; - EEVEE_EffectsInfo *effects = stl->effects; EEVEE_PrivateData *g_data = stl->g_data; Scene *scene = draw_ctx->scene; - const float clear[4] = {0.0f, 0.0f, 0.0f, 0.0f}; - /* Create FrameBuffer. */ /* Should be enough precision for many samples. */ DRW_texture_ensure_fullscreen_2d(&txl->mist_accum, GPU_R32F, 0); @@ -53,12 +50,6 @@ void EEVEE_mist_output_init(EEVEE_ViewLayerData *sldata, EEVEE_Data *vedata) GPU_framebuffer_ensure_config(&fbl->mist_accum_fb, {GPU_ATTACHMENT_NONE, GPU_ATTACHMENT_TEXTURE(txl->mist_accum)}); - /* Clear texture. */ - if (effects->taa_current_sample == 1) { - GPU_framebuffer_bind(fbl->mist_accum_fb); - GPU_framebuffer_clear_color(fbl->mist_accum_fb, clear); - } - /* Mist settings. */ if (scene && scene->world) { g_data->mist_start = scene->world->miststa; @@ -103,9 +94,17 @@ void EEVEE_mist_output_accumulate(EEVEE_ViewLayerData *UNUSED(sldata), EEVEE_Dat { EEVEE_FramebufferList *fbl = vedata->fbl; EEVEE_PassList *psl = vedata->psl; + EEVEE_EffectsInfo *effects = vedata->stl->effects; if (fbl->mist_accum_fb != NULL) { GPU_framebuffer_bind(fbl->mist_accum_fb); + + /* Clear texture. */ + if (effects->taa_current_sample == 1) { + const float clear[4] = {0.0f, 0.0f, 0.0f, 0.0f}; + GPU_framebuffer_clear_color(fbl->mist_accum_fb, clear); + } + DRW_draw_pass(psl->mist_accum_ps); /* Restore */ diff --git a/source/blender/draw/engines/eevee/eevee_occlusion.c b/source/blender/draw/engines/eevee/eevee_occlusion.c index 19f34fa6108..4c2024a6f65 100644 --- a/source/blender/draw/engines/eevee/eevee_occlusion.c +++ b/source/blender/draw/engines/eevee/eevee_occlusion.c @@ -120,7 +120,6 @@ void EEVEE_occlusion_output_init(EEVEE_ViewLayerData *sldata, EEVEE_Data *vedata const eGPUTextureFormat texture_format = (tot_samples > 128) ? GPU_R32F : GPU_R16F; DefaultTextureList *dtxl = DRW_viewport_texture_list_get(); - const float clear[4] = {0.0f, 0.0f, 0.0f, 0.0f}; /* Should be enough precision for many samples. */ DRW_texture_ensure_fullscreen_2d(&txl->ao_accum, texture_format, 0); @@ -128,12 +127,6 @@ void EEVEE_occlusion_output_init(EEVEE_ViewLayerData *sldata, EEVEE_Data *vedata GPU_framebuffer_ensure_config(&fbl->ao_accum_fb, {GPU_ATTACHMENT_NONE, GPU_ATTACHMENT_TEXTURE(txl->ao_accum)}); - /* Clear texture. */ - if (effects->taa_current_sample == 1) { - GPU_framebuffer_bind(fbl->ao_accum_fb); - GPU_framebuffer_clear_color(fbl->ao_accum_fb, clear); - } - /* Accumulation pass */ DRWState state = DRW_STATE_WRITE_COLOR | DRW_STATE_BLEND_ADD; DRW_PASS_CREATE(psl->ao_accum_ps, state); @@ -246,6 +239,7 @@ void EEVEE_occlusion_output_accumulate(EEVEE_ViewLayerData *sldata, EEVEE_Data * { EEVEE_FramebufferList *fbl = vedata->fbl; EEVEE_PassList *psl = vedata->psl; + EEVEE_EffectsInfo *effects = vedata->stl->effects; if (fbl->ao_accum_fb != NULL) { DefaultTextureList *dtxl = DRW_viewport_texture_list_get(); @@ -255,6 +249,13 @@ void EEVEE_occlusion_output_accumulate(EEVEE_ViewLayerData *sldata, EEVEE_Data * EEVEE_occlusion_compute(sldata, vedata); GPU_framebuffer_bind(fbl->ao_accum_fb); + + /* Clear texture. */ + if (effects->taa_current_sample == 1) { + const float clear[4] = {0.0f, 0.0f, 0.0f, 0.0f}; + GPU_framebuffer_clear_color(fbl->ao_accum_fb, clear); + } + DRW_draw_pass(psl->ao_accum_ps); /* Restore */ diff --git a/source/blender/draw/engines/eevee/eevee_screen_raytrace.c b/source/blender/draw/engines/eevee/eevee_screen_raytrace.c index 54f23073bd0..7af0f60748b 100644 --- a/source/blender/draw/engines/eevee/eevee_screen_raytrace.c +++ b/source/blender/draw/engines/eevee/eevee_screen_raytrace.c @@ -234,10 +234,6 @@ void EEVEE_reflection_output_init(EEVEE_ViewLayerData *UNUSED(sldata), { EEVEE_FramebufferList *fbl = vedata->fbl; EEVEE_TextureList *txl = vedata->txl; - EEVEE_StorageList *stl = vedata->stl; - EEVEE_EffectsInfo *effects = stl->effects; - - const float clear[4] = {0.0f, 0.0f, 0.0f, 0.0f}; /* Create FrameBuffer. */ const eGPUTextureFormat texture_format = (tot_samples > 256) ? GPU_RGBA32F : GPU_RGBA16F; @@ -245,12 +241,6 @@ void EEVEE_reflection_output_init(EEVEE_ViewLayerData *UNUSED(sldata), GPU_framebuffer_ensure_config(&fbl->ssr_accum_fb, {GPU_ATTACHMENT_NONE, GPU_ATTACHMENT_TEXTURE(txl->ssr_accum)}); - - /* Clear texture. */ - if (effects->taa_current_sample == 1) { - GPU_framebuffer_bind(fbl->ssr_accum_fb); - GPU_framebuffer_clear_color(fbl->ssr_accum_fb, clear); - } } void EEVEE_reflection_output_accumulate(EEVEE_ViewLayerData *UNUSED(sldata), EEVEE_Data *vedata) @@ -258,9 +248,17 @@ void EEVEE_reflection_output_accumulate(EEVEE_ViewLayerData *UNUSED(sldata), EEV EEVEE_FramebufferList *fbl = vedata->fbl; EEVEE_PassList *psl = vedata->psl; EEVEE_StorageList *stl = vedata->stl; + EEVEE_EffectsInfo *effects = vedata->stl->effects; if (stl->g_data->valid_double_buffer) { GPU_framebuffer_bind(fbl->ssr_accum_fb); + + /* Clear texture. */ + if (effects->taa_current_sample == 1) { + const float clear[4] = {0.0f, 0.0f, 0.0f, 0.0f}; + GPU_framebuffer_clear_color(fbl->ssr_accum_fb, clear); + } + DRW_draw_pass(psl->ssr_resolve); } } diff --git a/source/blender/draw/engines/eevee/eevee_shadows.c b/source/blender/draw/engines/eevee/eevee_shadows.c index 51fd1cad41e..6a98c3316f3 100644 --- a/source/blender/draw/engines/eevee/eevee_shadows.c +++ b/source/blender/draw/engines/eevee/eevee_shadows.c @@ -361,12 +361,8 @@ void EEVEE_shadow_output_init(EEVEE_ViewLayerData *sldata, EEVEE_FramebufferList *fbl = vedata->fbl; EEVEE_TextureList *txl = vedata->txl; EEVEE_PassList *psl = vedata->psl; - EEVEE_StorageList *stl = vedata->stl; - EEVEE_EffectsInfo *effects = stl->effects; DefaultTextureList *dtxl = DRW_viewport_texture_list_get(); - const float clear[4] = {0.0f, 0.0f, 0.0f, 0.0f}; - /* Create FrameBuffer. */ const eGPUTextureFormat texture_format = GPU_R32F; DRW_texture_ensure_fullscreen_2d(&txl->shadow_accum, texture_format, 0); @@ -374,12 +370,6 @@ void EEVEE_shadow_output_init(EEVEE_ViewLayerData *sldata, GPU_framebuffer_ensure_config(&fbl->shadow_accum_fb, {GPU_ATTACHMENT_NONE, GPU_ATTACHMENT_TEXTURE(txl->shadow_accum)}); - /* Clear texture. */ - if (effects->taa_current_sample == 1) { - GPU_framebuffer_bind(fbl->shadow_accum_fb); - GPU_framebuffer_clear_color(fbl->shadow_accum_fb, clear); - } - /* Create Pass and shgroup. */ DRW_PASS_CREATE(psl->shadow_accum_pass, DRW_STATE_WRITE_COLOR | DRW_STATE_DEPTH_ALWAYS | DRW_STATE_BLEND_ADD_FULL); @@ -404,9 +394,17 @@ void EEVEE_shadow_output_accumulate(EEVEE_ViewLayerData *UNUSED(sldata), EEVEE_D { EEVEE_FramebufferList *fbl = vedata->fbl; EEVEE_PassList *psl = vedata->psl; + EEVEE_EffectsInfo *effects = vedata->stl->effects; if (fbl->shadow_accum_fb != NULL) { GPU_framebuffer_bind(fbl->shadow_accum_fb); + + /* Clear texture. */ + if (effects->taa_current_sample == 1) { + const float clear[4] = {0.0f, 0.0f, 0.0f, 0.0f}; + GPU_framebuffer_clear_color(fbl->shadow_accum_fb, clear); + } + DRW_draw_pass(psl->shadow_accum_pass); /* Restore */ diff --git a/source/blender/draw/engines/eevee/eevee_volumes.c b/source/blender/draw/engines/eevee/eevee_volumes.c index d228d26cd52..3d24cd6fab5 100644 --- a/source/blender/draw/engines/eevee/eevee_volumes.c +++ b/source/blender/draw/engines/eevee/eevee_volumes.c @@ -394,10 +394,9 @@ static bool eevee_volume_object_grids_init(Object *ob, ListBase *gpu_grids, DRWS * - Grid exists and texture was loaded -> use texture. * - Grid exists but has zero size or failed to load -> use zero. * - Grid does not exist -> use default value. */ - GPUTexture *grid_tex = (drw_grid) ? drw_grid->texture : - (volume_grid) ? - e_data.dummy_zero : - eevee_volume_default_texture(gpu_grid->default_value); + GPUTexture *grid_tex = (drw_grid) ? drw_grid->texture : + (volume_grid) ? e_data.dummy_zero : + eevee_volume_default_texture(gpu_grid->default_value); DRW_shgroup_uniform_texture(grp, gpu_grid->sampler_name, grid_tex); @@ -800,8 +799,6 @@ void EEVEE_volumes_output_init(EEVEE_ViewLayerData *sldata, EEVEE_Data *vedata, EEVEE_PassList *psl = vedata->psl; EEVEE_EffectsInfo *effects = stl->effects; - const float clear[4] = {0.0f, 0.0f, 0.0f, 0.0f}; - /* Create FrameBuffer. */ /* Should be enough precision for many samples. */ @@ -814,12 +811,6 @@ void EEVEE_volumes_output_init(EEVEE_ViewLayerData *sldata, EEVEE_Data *vedata, GPU_ATTACHMENT_TEXTURE(txl->volume_scatter_accum), GPU_ATTACHMENT_TEXTURE(txl->volume_transmittance_accum)}); - /* Clear texture. */ - if (effects->taa_current_sample == 1) { - GPU_framebuffer_bind(fbl->volumetric_accum_fb); - GPU_framebuffer_clear_color(fbl->volumetric_accum_fb, clear); - } - /* Create Pass and shgroup. */ DRW_PASS_CREATE(psl->volumetric_accum_ps, DRW_STATE_WRITE_COLOR | DRW_STATE_BLEND_ADD_FULL); DRWShadingGroup *grp = NULL; @@ -843,10 +834,18 @@ void EEVEE_volumes_output_accumulate(EEVEE_ViewLayerData *UNUSED(sldata), EEVEE_ { EEVEE_FramebufferList *fbl = vedata->fbl; EEVEE_PassList *psl = vedata->psl; + EEVEE_EffectsInfo *effects = vedata->stl->effects; if (fbl->volumetric_accum_fb != NULL) { /* Accum pass */ GPU_framebuffer_bind(fbl->volumetric_accum_fb); + + /* Clear texture. */ + if (effects->taa_current_sample == 1) { + const float clear[4] = {0.0f, 0.0f, 0.0f, 0.0f}; + GPU_framebuffer_clear_color(fbl->volumetric_accum_fb, clear); + } + DRW_draw_pass(psl->volumetric_accum_ps); /* Restore */ diff --git a/source/blender/draw/engines/eevee/shaders/effect_translucency_frag.glsl b/source/blender/draw/engines/eevee/shaders/effect_translucency_frag.glsl index 737ef7dc509..356ed102928 100644 --- a/source/blender/draw/engines/eevee/shaders/effect_translucency_frag.glsl +++ b/source/blender/draw/engines/eevee/shaders/effect_translucency_frag.glsl @@ -183,16 +183,70 @@ vec3 light_translucent(LightData ld, vec3 P, vec3 N, vec4 l_vector, vec2 rand, f #undef scube #undef scsmd +/* Similar to https://atyuwen.github.io/posts/normal-reconstruction/. + * This samples the depth buffer 4 time for each direction to get the most correct + * implicit normal reconstruction out of the depth buffer. */ +vec3 view_position_derivative_from_depth(vec2 uvs, vec2 ofs, vec3 vP, float depth_center) +{ + vec2 uv1 = uvs - ofs * 2.0; + vec2 uv2 = uvs - ofs; + vec2 uv3 = uvs + ofs; + vec2 uv4 = uvs + ofs * 2.0; + vec4 H; + H.x = textureLod(depthBuffer, uv1, 0.0).r; + H.y = textureLod(depthBuffer, uv2, 0.0).r; + H.z = textureLod(depthBuffer, uv3, 0.0).r; + H.w = textureLod(depthBuffer, uv4, 0.0).r; + /* Fix issue with depth precision. Take even larger diff. */ + vec4 diff = abs(vec4(depth_center, H.yzw) - H.x); + if (max_v4(diff) < 2.4e-7 && all(lessThan(diff.xyz, diff.www))) { + return 0.25 * (get_view_space_from_depth(uv3, H.w) - get_view_space_from_depth(uv1, H.x)); + } + /* Simplified (H.xw + 2.0 * (H.yz - H.xw)) - depth_center */ + vec2 deltas = abs((2.0 * H.yz - H.xw) - depth_center); + if (deltas.x < deltas.y) { + return vP - get_view_space_from_depth(uv2, H.y); + } + else { + return get_view_space_from_depth(uv3, H.z) - vP; + } +} + +/* TODO(fclem) port to a common place for other effects to use. */ +bool reconstruct_view_position_and_normal_from_depth(vec2 uvs, out vec3 vP, out vec3 vNg) +{ + vec2 texel_size = vec2(abs(dFdx(uvs.x)), abs(dFdy(uvs.y))); + float depth_center = textureLod(depthBuffer, uvs, 0.0).r; + + vP = get_view_space_from_depth(uvs, depth_center); + + vec3 dPdx = view_position_derivative_from_depth(uvs, texel_size * vec2(1, 0), vP, depth_center); + vec3 dPdy = view_position_derivative_from_depth(uvs, texel_size * vec2(0, 1), vP, depth_center); + + vNg = safe_normalize(cross(dPdx, dPdy)); + + /* Background case. */ + if (depth_center == 1.0) { + return false; + } + + return true; +} + void main(void) { vec2 uvs = uvcoordsvar.xy; float sss_scale = texture(sssRadius, uvs).r; - vec3 P = get_world_space_from_depth(uvs, texture(depthBuffer, uvs).r); - vec3 N = normalize(cross(dFdx(P), dFdy(P))); vec3 rand = texelfetch_noise_tex(gl_FragCoord.xy).zwy; rand.xy *= fast_sqrt(rand.z); + vec3 vP, vNg; + reconstruct_view_position_and_normal_from_depth(uvs, vP, vNg); + + vec3 P = point_view_to_world(vP); + vec3 Ng = normal_view_to_world(vNg); + vec3 accum = vec3(0.0); for (int i = 0; i < MAX_LIGHT && i < laNumLight; i++) { LightData ld = lights_data[i]; @@ -211,7 +265,7 @@ void main(void) continue; } - accum += att * ld.l_color * light_translucent(ld, P, -N, l_vector, rand.xy, sss_scale); + accum += att * ld.l_color * light_translucent(ld, P, -Ng, l_vector, rand.xy, sss_scale); } FragColor = vec4(accum, 1.0); diff --git a/source/blender/draw/engines/gpencil/gpencil_shader_fx.c b/source/blender/draw/engines/gpencil/gpencil_shader_fx.c index ab90831f4ac..21d55357a2a 100644 --- a/source/blender/draw/engines/gpencil/gpencil_shader_fx.c +++ b/source/blender/draw/engines/gpencil/gpencil_shader_fx.c @@ -427,7 +427,7 @@ static void gpencil_vfx_glow(GlowShaderFxData *fx, Object *UNUSED(ob), gpIterVfx float ref_col[4]; if (fx->mode == eShaderFxGlowMode_Luminance) { - /* Only pass in the first value for luminace. */ + /* Only pass in the first value for luminance. */ ref_col[0] = fx->threshold; ref_col[1] = -1.0f; ref_col[2] = -1.0f; diff --git a/source/blender/draw/intern/draw_cache_impl_mesh.c b/source/blender/draw/intern/draw_cache_impl_mesh.c index f11fd0f3906..af54b57b162 100644 --- a/source/blender/draw/intern/draw_cache_impl_mesh.c +++ b/source/blender/draw/intern/draw_cache_impl_mesh.c @@ -557,12 +557,18 @@ static void mesh_batch_cache_discard_surface_batches(MeshBatchCache *cache) static void mesh_batch_cache_discard_shaded_tri(MeshBatchCache *cache) { FOREACH_MESH_BUFFER_CACHE (cache, mbufcache) { - GPU_VERTBUF_DISCARD_SAFE(mbufcache->vbo.pos_nor); GPU_VERTBUF_DISCARD_SAFE(mbufcache->vbo.uv); GPU_VERTBUF_DISCARD_SAFE(mbufcache->vbo.tan); GPU_VERTBUF_DISCARD_SAFE(mbufcache->vbo.vcol); GPU_VERTBUF_DISCARD_SAFE(mbufcache->vbo.orco); } + /* Discard batches using vbo.uv. */ + GPU_BATCH_DISCARD_SAFE(cache->batch.edituv_faces); + GPU_BATCH_DISCARD_SAFE(cache->batch.edituv_faces_stretch_area); + GPU_BATCH_DISCARD_SAFE(cache->batch.edituv_faces_stretch_angle); + GPU_BATCH_DISCARD_SAFE(cache->batch.edituv_edges); + GPU_BATCH_DISCARD_SAFE(cache->batch.edituv_verts); + mesh_batch_cache_discard_surface_batches(cache); mesh_cd_layers_type_clear(&cache->cd_used); } @@ -659,8 +665,17 @@ void DRW_mesh_batch_cache_dirty_tag(Mesh *me, eMeshBatchDirtyMode mode) GPU_VERTBUF_DISCARD_SAFE(mbufcache->vbo.lnor); } GPU_BATCH_DISCARD_SAFE(cache->batch.surface); + /* Discard batches using vbo.pos_nor. */ GPU_BATCH_DISCARD_SAFE(cache->batch.wire_loops); GPU_BATCH_DISCARD_SAFE(cache->batch.wire_edges); + GPU_BATCH_DISCARD_SAFE(cache->batch.all_verts); + GPU_BATCH_DISCARD_SAFE(cache->batch.all_edges); + GPU_BATCH_DISCARD_SAFE(cache->batch.loose_edges); + GPU_BATCH_DISCARD_SAFE(cache->batch.edge_detection); + GPU_BATCH_DISCARD_SAFE(cache->batch.surface_weights); + GPU_BATCH_DISCARD_SAFE(cache->batch.edit_mesh_analysis); + /* Discard batches using vbo.lnor. */ + GPU_BATCH_DISCARD_SAFE(cache->batch.edit_lnor); mesh_batch_cache_discard_surface_batches(cache); cache->batch_ready &= ~(MBC_SURFACE | MBC_WIRE_EDGES | MBC_WIRE_LOOPS); break; diff --git a/source/blender/editors/gpencil/gpencil_fill.c b/source/blender/editors/gpencil/gpencil_fill.c index 3f86e5474c5..62d64c67b08 100644 --- a/source/blender/editors/gpencil/gpencil_fill.c +++ b/source/blender/editors/gpencil/gpencil_fill.c @@ -1029,9 +1029,15 @@ static void gpencil_invert_image(tGPDfill *tgpf) /* Red->Green */ else if (color[0] == 1.0f) { set_pixel(ibuf, v, fill_col[1]); - /* Add thickness of 2 pixels to avoid too thin lines. */ - int offset = (v % ibuf->x < center) ? 1 : -1; - set_pixel(ibuf, v + offset, fill_col[1]); + /* Add thickness of 2 pixels to avoid too thin lines, but avoid extremes of the pixel line. + */ + int row = v / ibuf->x; + int lowpix = row * ibuf->x; + int highpix = lowpix + ibuf->x - 1; + if ((v > lowpix) && (v < highpix)) { + int offset = (v % ibuf->x < center) ? 1 : -1; + set_pixel(ibuf, v + offset, fill_col[1]); + } } else { /* Set to Transparent. */ @@ -1241,6 +1247,7 @@ static bool dilate_shape(ImBuf *ibuf) static void gpencil_get_outline_points(tGPDfill *tgpf, const bool dilate) { ImBuf *ibuf; + Brush *brush = tgpf->brush; float rgba[4]; void *lock; int v[2]; @@ -1273,7 +1280,9 @@ static void gpencil_get_outline_points(tGPDfill *tgpf, const bool dilate) /* Dilate. */ if (dilate) { - dilate_shape(ibuf); + for (int i = 0; i < brush->gpencil_settings->dilate_pixels; i++) { + dilate_shape(ibuf); + } } for (int idx = imagesize - 1; idx != 0; idx--) { diff --git a/source/blender/editors/interface/interface_region_menu_popup.c b/source/blender/editors/interface/interface_region_menu_popup.c index 6906309fd30..58a74a3473e 100644 --- a/source/blender/editors/interface/interface_region_menu_popup.c +++ b/source/blender/editors/interface/interface_region_menu_popup.c @@ -403,7 +403,7 @@ uiPopupMenu *UI_popup_menu_begin_ex(bContext *C, pup->layout = UI_block_layout( pup->block, UI_LAYOUT_VERTICAL, UI_LAYOUT_MENU, 0, 0, 200, 0, UI_MENU_PADDING, style); - /* note, this intentionally differs from the menu & submenu default because many operators + /* note, this intentionally differs from the menu & sub-menu default because many operators * use popups like this to select one of their options - * where having invoke doesn't make sense */ uiLayoutSetOperatorContext(pup->layout, WM_OP_EXEC_REGION_WIN); diff --git a/source/blender/editors/interface/interface_templates.c b/source/blender/editors/interface/interface_templates.c index 6ca0f196280..e3df9704826 100644 --- a/source/blender/editors/interface/interface_templates.c +++ b/source/blender/editors/interface/interface_templates.c @@ -1110,7 +1110,7 @@ static void template_ID(const bContext *C, UI_but_flag_enable(but, UI_BUT_REDALERT); } - if (id->lib == NULL && !(ELEM(GS(id->name), ID_GR, ID_SCE, ID_SCR, ID_TXT, ID_OB, ID_WS)) && + if (id->lib == NULL && !(ELEM(GS(id->name), ID_GR, ID_SCE, ID_SCR, ID_OB, ID_WS)) && (hide_buttons == false)) { uiDefIconButR(block, UI_BTYPE_ICON_TOGGLE, @@ -6830,6 +6830,7 @@ void uiTemplateReportsBanner(uiLayout *layout, bContext *C) uiLayout *ui_abs = uiLayoutAbsolute(layout, false); uiBlock *block = uiLayoutGetBlock(ui_abs); + eUIEmbossType previous_emboss = UI_block_emboss_get(block); UI_fontstyle_set(&style->widgetlabel); int width = BLF_width(style->widgetlabel.uifont_id, report->message, report->len); @@ -6904,6 +6905,8 @@ void uiTemplateReportsBanner(uiLayout *layout, bContext *C) width + UI_UNIT_X, UI_UNIT_Y, "Show in Info Log"); + + UI_block_emboss_set(block, previous_emboss); } void uiTemplateInputStatus(uiLayout *layout, struct bContext *C) diff --git a/source/blender/editors/mesh/editmesh_select.c b/source/blender/editors/mesh/editmesh_select.c index 0aeaee57548..6cb103460f6 100644 --- a/source/blender/editors/mesh/editmesh_select.c +++ b/source/blender/editors/mesh/editmesh_select.c @@ -2547,6 +2547,7 @@ bool EDBM_selectmode_set_multi(bContext *C, const short selectmode) changed = true; } } + MEM_freeN(objects); if (changed) { WM_main_add_notifier(NC_SCENE | ND_TOOLSETTINGS, NULL); diff --git a/source/blender/editors/object/object_add.c b/source/blender/editors/object/object_add.c index 4f77a3690a8..ce441e7b551 100644 --- a/source/blender/editors/object/object_add.c +++ b/source/blender/editors/object/object_add.c @@ -860,8 +860,9 @@ static int effector_add_exec(bContext *C, wmOperator *op) float mat[4][4]; ED_object_new_primitive_matrix(C, ob, loc, rot, mat); + mul_mat3_m4_fl(mat, dia); BLI_addtail(&cu->editnurb->nurbs, - ED_curve_add_nurbs_primitive(C, ob, mat, CU_NURBS | CU_PRIM_PATH, dia)); + ED_curve_add_nurbs_primitive(C, ob, mat, CU_NURBS | CU_PRIM_PATH, 1)); if (!enter_editmode) { ED_object_editmode_exit_ex(bmain, scene, ob, EM_FREEDATA); } diff --git a/source/blender/editors/render/render_internal.c b/source/blender/editors/render/render_internal.c index 0b5a8db0115..0bec509cd7e 100644 --- a/source/blender/editors/render/render_internal.c +++ b/source/blender/editors/render/render_internal.c @@ -574,9 +574,12 @@ static void image_rect_update(void *rjv, RenderResult *rr, volatile rcti *renrec return; } if (rj->image_outdated) { - /* update entire render */ + /* Free all render buffer caches when switching slots, with lock to ensure main + * thread is not drawing the buffer at the same time. */ rj->image_outdated = false; - BKE_image_signal(rj->main, ima, NULL, IMA_SIGNAL_COLORMANAGE); + ibuf = BKE_image_acquire_ibuf(ima, &rj->iuser, &lock); + BKE_image_free_buffers(ima); + BKE_image_release_ibuf(ima, ibuf, lock); *(rj->do_update) = true; return; } diff --git a/source/blender/editors/sound/sound_ops.c b/source/blender/editors/sound/sound_ops.c index 85616f6356d..fe0a53ae964 100644 --- a/source/blender/editors/sound/sound_ops.c +++ b/source/blender/editors/sound/sound_ops.c @@ -52,6 +52,7 @@ #include "RNA_enum_types.h" #include "SEQ_iterator.h" +#include "SEQ_utils.h" #include "UI_interface.h" @@ -258,7 +259,7 @@ static void sound_update_animation_flags(Scene *scene) scene->id.tag |= LIB_TAG_DOIT; SEQ_ALL_BEGIN (scene->ed, seq) { - SEQ_iterator_recursive_apply(seq, sound_update_animation_flags_fn, scene); + SEQ_recursive_apply(seq, sound_update_animation_flags_fn, scene); } SEQ_ALL_END; diff --git a/source/blender/editors/space_file/file_intern.h b/source/blender/editors/space_file/file_intern.h index 309b280177c..f1d0197b9ae 100644 --- a/source/blender/editors/space_file/file_intern.h +++ b/source/blender/editors/space_file/file_intern.h @@ -63,6 +63,7 @@ void FILE_OT_bookmark_move(struct wmOperatorType *ot); void FILE_OT_reset_recent(wmOperatorType *ot); void FILE_OT_hidedot(struct wmOperatorType *ot); void FILE_OT_execute(struct wmOperatorType *ot); +void FILE_OT_mouse_execute(struct wmOperatorType *ot); void FILE_OT_cancel(struct wmOperatorType *ot); void FILE_OT_parent(struct wmOperatorType *ot); void FILE_OT_directory_new(struct wmOperatorType *ot); diff --git a/source/blender/editors/space_file/file_ops.c b/source/blender/editors/space_file/file_ops.c index 856bd5b1bc3..61f3c046550 100644 --- a/source/blender/editors/space_file/file_ops.c +++ b/source/blender/editors/space_file/file_ops.c @@ -536,6 +536,14 @@ void FILE_OT_select_box(wmOperatorType *ot) /** \name Select Pick Operator * \{ */ +static rcti file_select_mval_to_select_rect(const int mval[2]) +{ + rcti rect; + rect.xmin = rect.xmax = mval[0]; + rect.ymin = rect.ymax = mval[1]; + return rect; +} + static int file_select_invoke(bContext *C, wmOperator *op, const wmEvent *event) { ARegion *region = CTX_wm_region(C); @@ -551,8 +559,7 @@ static int file_select_invoke(bContext *C, wmOperator *op, const wmEvent *event) return OPERATOR_CANCELLED; } - rect.xmin = rect.xmax = event->mval[0]; - rect.ymin = rect.ymax = event->mval[1]; + rect = file_select_mval_to_select_rect(event->mval); if (!ED_fileselect_layout_is_inside_pt(sfile->layout, ®ion->v2d, rect.xmin, rect.ymin)) { return OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH; @@ -1711,14 +1718,14 @@ bool file_draw_check_exists(SpaceFile *sfile) /** \name Execute File Window Operator * \{ */ -static int file_exec(bContext *C, wmOperator *exec_op) +/** + * Execute the active file, as set in the file select params. + */ +static bool file_execute(bContext *C, SpaceFile *sfile) { Main *bmain = CTX_data_main(C); - wmWindowManager *wm = CTX_wm_manager(C); - SpaceFile *sfile = CTX_wm_space_file(C); FileSelectParams *params = ED_fileselect_get_active_params(sfile); - struct FileDirEntry *file = filelist_file(sfile->files, params->active_file); - char filepath[FILE_MAX]; + FileDirEntry *file = filelist_file(sfile->files, params->active_file); if (file && file->redirection_path) { /* redirection_path is an absolute path that takes precedence @@ -1753,22 +1760,7 @@ static int file_exec(bContext *C, wmOperator *exec_op) /* opening file - sends events now, so things get handled on windowqueue level */ else if (sfile->op) { wmOperator *op = sfile->op; - - /* When used as a macro, for double-click, to prevent closing when double-clicking on item. */ - if (RNA_boolean_get(exec_op->ptr, "need_active")) { - const int numfiles = filelist_files_ensure(sfile->files); - int i, active = 0; - - for (i = 0; i < numfiles; i++) { - if (filelist_entry_select_index_get(sfile->files, i, CHECK_ALL)) { - active = 1; - break; - } - } - if (active == 0) { - return OPERATOR_CANCELLED; - } - } + char filepath[FILE_MAX]; sfile->op = NULL; @@ -1788,50 +1780,94 @@ static int file_exec(bContext *C, wmOperator *exec_op) BKE_appdir_folder_id_create(BLENDER_USER_CONFIG, NULL), BLENDER_BOOKMARK_FILE); fsmenu_write_file(ED_fsmenu_get(), filepath); - WM_event_fileselect_event(wm, op, EVT_FILESELECT_EXEC); + WM_event_fileselect_event(CTX_wm_manager(C), op, EVT_FILESELECT_EXEC); } return OPERATOR_FINISHED; } -static int file_exec_invoke(bContext *C, wmOperator *op, const wmEvent *event) +static int file_exec(bContext *C, wmOperator *UNUSED(op)) { - ARegion *region = CTX_wm_region(C); SpaceFile *sfile = CTX_wm_space_file(C); - if (!ED_fileselect_layout_is_inside_pt( - sfile->layout, ®ion->v2d, event->mval[0], event->mval[1])) { - return OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH; + if (!file_execute(C, sfile)) { + return OPERATOR_CANCELLED; } - return file_exec(C, op); + return OPERATOR_FINISHED; } void FILE_OT_execute(struct wmOperatorType *ot) { - PropertyRNA *prop; - /* identifiers */ ot->name = "Execute File Window"; ot->description = "Execute selected file"; ot->idname = "FILE_OT_execute"; /* api callbacks */ - ot->invoke = file_exec_invoke; ot->exec = file_exec; /* Important since handler is on window level. * * Avoid using #file_operator_poll since this is also used for entering directories * which is used even when the file manager doesn't have an operator. */ ot->poll = ED_operator_file_active; +} - /* properties */ - prop = RNA_def_boolean(ot->srna, - "need_active", - 0, - "Need Active", - "Only execute if there's an active selected file in the file list"); - RNA_def_property_flag(prop, PROP_SKIP_SAVE); +/** + * \returns false if the mouse doesn't hover a selectable item. + */ +static bool file_ensure_hovered_is_active(bContext *C, const wmEvent *event) +{ + rcti rect = file_select_mval_to_select_rect(event->mval); + if (file_select(C, &rect, FILE_SEL_ADD, false, false) == FILE_SELECT_NOTHING) { + return false; + } + + return true; +} + +static int file_execute_mouse_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *event) +{ + ARegion *region = CTX_wm_region(C); + SpaceFile *sfile = CTX_wm_space_file(C); + + if (!ED_fileselect_layout_is_inside_pt( + sfile->layout, ®ion->v2d, event->mval[0], event->mval[1])) { + return OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH; + } + + /* Note that this isn't needed practically, because the keymap already activates the hovered item + * on mouse-press. This execute operator is called afterwards on the double-click event then. + * However relying on this would be fragile and could break with keymap changes, so better to + * have this mouse-execute operator that makes sure once more that the hovered file is active. */ + if (!file_ensure_hovered_is_active(C, event)) { + return OPERATOR_CANCELLED; + } + + if (!file_execute(C, sfile)) { + return OPERATOR_CANCELLED; + } + + return OPERATOR_FINISHED; +} + +/** + * Variation of #FILE_OT_execute that accounts for some mouse specific handling. Otherwise calls + * the same logic. + */ +void FILE_OT_mouse_execute(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Execute File"; + ot->description = + "Perform the current execute action for the file under the cursor (e.g. open the file)"; + ot->idname = "FILE_OT_mouse_execute"; + + /* api callbacks */ + ot->invoke = file_execute_mouse_invoke; + ot->poll = ED_operator_file_active; + + ot->flag = OPTYPE_INTERNAL; } /** \} */ diff --git a/source/blender/editors/space_file/space_file.c b/source/blender/editors/space_file/space_file.c index 4422a685af1..12bc0a68ca6 100644 --- a/source/blender/editors/space_file/space_file.c +++ b/source/blender/editors/space_file/space_file.c @@ -663,6 +663,7 @@ static void file_operatortypes(void) WM_operatortype_append(FILE_OT_highlight); WM_operatortype_append(FILE_OT_sort_column_ui_context); WM_operatortype_append(FILE_OT_execute); + WM_operatortype_append(FILE_OT_mouse_execute); WM_operatortype_append(FILE_OT_cancel); WM_operatortype_append(FILE_OT_parent); WM_operatortype_append(FILE_OT_previous); diff --git a/source/blender/editors/space_info/info_ops.c b/source/blender/editors/space_info/info_ops.c index 0583628be43..aaf9852e212 100644 --- a/source/blender/editors/space_info/info_ops.c +++ b/source/blender/editors/space_info/info_ops.c @@ -56,7 +56,9 @@ #include "info_intern.h" -/********************* pack blend file libraries operator *********************/ +/* -------------------------------------------------------------------- */ +/** \name Pack Blend File Libraries Operator + * \{ */ static int pack_libraries_exec(bContext *C, wmOperator *op) { @@ -70,9 +72,11 @@ static int pack_libraries_exec(bContext *C, wmOperator *op) void FILE_OT_pack_libraries(wmOperatorType *ot) { /* identifiers */ - ot->name = "Pack Blender Libraries"; + ot->name = "Pack Linked Libraries"; ot->idname = "FILE_OT_pack_libraries"; - ot->description = "Pack all used Blender library files into the current .blend"; + ot->description = + "Store all data-blocks linked from other .blend files in the current .blend file. " + "Library references are preserved so the linked data-blocks can be unpacked again"; /* api callbacks */ ot->exec = pack_libraries_exec; @@ -90,18 +94,24 @@ static int unpack_libraries_exec(bContext *C, wmOperator *op) return OPERATOR_FINISHED; } +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Unpack Blend File Libraries Operator + * \{ */ + static int unpack_libraries_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event)) { return WM_operator_confirm_message( - C, op, "Unpack Blender Libraries - creates directories, all new paths should work"); + C, op, "Unpack Linked Libraries - creates directories, all new paths should work"); } void FILE_OT_unpack_libraries(wmOperatorType *ot) { /* identifiers */ - ot->name = "Unpack Blender Libraries"; + ot->name = "Unpack Linked Libraries"; ot->idname = "FILE_OT_unpack_libraries"; - ot->description = "Unpack all used Blender library files from this .blend file"; + ot->description = "Restore all packed linked data-blocks to their original locations"; /* api callbacks */ ot->invoke = unpack_libraries_invoke; @@ -111,7 +121,11 @@ void FILE_OT_unpack_libraries(wmOperatorType *ot) ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; } -/********************* toggle auto-pack operator *********************/ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Toggle Auto-Pack Operator + * \{ */ static int autopack_toggle_exec(bContext *C, wmOperator *op) { @@ -131,7 +145,7 @@ static int autopack_toggle_exec(bContext *C, wmOperator *op) void FILE_OT_autopack_toggle(wmOperatorType *ot) { /* identifiers */ - ot->name = "Automatically Pack Into .blend"; + ot->name = "Automatically Pack Resources"; ot->idname = "FILE_OT_autopack_toggle"; ot->description = "Automatically pack all external files into the .blend file"; @@ -142,7 +156,11 @@ void FILE_OT_autopack_toggle(wmOperatorType *ot) ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; } -/********************* pack all operator *********************/ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Pack All Operator + * \{ */ static int pack_all_exec(bContext *C, wmOperator *op) { @@ -176,9 +194,9 @@ static int pack_all_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(ev void FILE_OT_pack_all(wmOperatorType *ot) { /* identifiers */ - ot->name = "Pack All Into .blend"; + ot->name = "Pack Resources"; ot->idname = "FILE_OT_pack_all"; - ot->description = "Pack all used external files into the .blend"; + ot->description = "Pack all used external files into this .blend"; /* api callbacks */ ot->exec = pack_all_exec; @@ -188,7 +206,11 @@ void FILE_OT_pack_all(wmOperatorType *ot) ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; } -/********************* unpack all operator *********************/ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Unpack All Operator + * \{ */ static const EnumPropertyItem unpack_all_method_items[] = { {PF_USE_LOCAL, "USE_LOCAL", 0, "Use files in current directory (create when necessary)", ""}, @@ -263,7 +285,7 @@ static int unpack_all_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED( void FILE_OT_unpack_all(wmOperatorType *ot) { /* identifiers */ - ot->name = "Unpack All Into Files"; + ot->name = "Unpack Resources"; ot->idname = "FILE_OT_unpack_all"; ot->description = "Unpack all files packed into this .blend to external ones"; @@ -279,7 +301,11 @@ void FILE_OT_unpack_all(wmOperatorType *ot) ot->srna, "method", unpack_all_method_items, PF_USE_LOCAL, "Method", "How to unpack"); } -/********************* unpack single item operator *********************/ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Unpack Single Item Operator + * \{ */ static const EnumPropertyItem unpack_item_method_items[] = { {PF_USE_LOCAL, "USE_LOCAL", 0, "Use file from current directory (create when necessary)", ""}, @@ -373,7 +399,11 @@ void FILE_OT_unpack_item(wmOperatorType *ot) INT_MAX); } -/********************* make paths relative operator *********************/ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Make Paths Relative Operator + * \{ */ static int make_paths_relative_exec(bContext *C, wmOperator *op) { @@ -395,7 +425,7 @@ static int make_paths_relative_exec(bContext *C, wmOperator *op) void FILE_OT_make_paths_relative(wmOperatorType *ot) { /* identifiers */ - ot->name = "Make All Paths Relative"; + ot->name = "Make Paths Relative"; ot->idname = "FILE_OT_make_paths_relative"; ot->description = "Make all paths to external files relative to current .blend"; @@ -406,7 +436,11 @@ void FILE_OT_make_paths_relative(wmOperatorType *ot) ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; } -/********************* make paths absolute operator *********************/ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Make Paths Absolute Operator + * \{ */ static int make_paths_absolute_exec(bContext *C, wmOperator *op) { @@ -428,7 +462,7 @@ static int make_paths_absolute_exec(bContext *C, wmOperator *op) void FILE_OT_make_paths_absolute(wmOperatorType *ot) { /* identifiers */ - ot->name = "Make All Paths Absolute"; + ot->name = "Make Paths Absolute"; ot->idname = "FILE_OT_make_paths_absolute"; ot->description = "Make all paths to external files absolute"; @@ -439,7 +473,11 @@ void FILE_OT_make_paths_absolute(wmOperatorType *ot) ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; } -/********************* report missing files operator *********************/ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Report Missing Files Operator + * \{ */ static int report_missing_files_exec(bContext *C, wmOperator *op) { @@ -465,7 +503,11 @@ void FILE_OT_report_missing_files(wmOperatorType *ot) ot->flag = 0; /* only reports so no need to undo/register */ } -/********************* find missing files operator *********************/ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Find Missing Files Operator + * \{ */ static int find_missing_files_exec(bContext *C, wmOperator *op) { @@ -516,7 +558,11 @@ void FILE_OT_find_missing_files(wmOperatorType *ot) FILE_SORT_DEFAULT); } -/********************* report box operator *********************/ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Report Box Operator + * \{ */ /* Hard to decide whether to keep this as an operator, * or turn it into a hardcoded ui control feature, @@ -621,3 +667,5 @@ void INFO_OT_reports_display_update(wmOperatorType *ot) } /* report operators */ + +/** \} */ diff --git a/source/blender/editors/space_sequencer/sequencer_add.c b/source/blender/editors/space_sequencer/sequencer_add.c index c4fd0dd5d73..cd75bc98b15 100644 --- a/source/blender/editors/space_sequencer/sequencer_add.c +++ b/source/blender/editors/space_sequencer/sequencer_add.c @@ -95,6 +95,7 @@ typedef struct SequencerAddData { #define SEQPROP_NOPATHS (1 << 2) #define SEQPROP_NOCHAN (1 << 3) #define SEQPROP_FIT_METHOD (1 << 4) +#define SEQPROP_VIEW_TRANSFORM (1 << 5) static const EnumPropertyItem scale_fit_methods[] = { {SEQ_SCALE_TO_FIT, "FIT", 0, "Scale to Fit", "Scale image to fit within the canvas"}, @@ -152,6 +153,14 @@ static void sequencer_generic_props__internal(wmOperatorType *ot, int flag) "Fit Method", "Scale fit method"); } + + if (flag & SEQPROP_VIEW_TRANSFORM) { + ot->prop = RNA_def_boolean(ot->srna, + "set_view_transform", + true, + "Set view transform", + "Set appropriate view transform based on media colorspace"); + } } static void sequencer_generic_invoke_path__internal(bContext *C, @@ -284,6 +293,11 @@ static void load_data_init_from_operator(SeqLoadData *load_data, bContext *C, wm load_data->flags |= SEQ_LOAD_MOVIE_SYNC_FPS; } + if ((prop = RNA_struct_find_property(op->ptr, "set_view_transform")) && + RNA_property_boolean_get(op->ptr, prop)) { + load_data->flags |= SEQ_LOAD_SET_VIEW_TRANSFORM; + } + if ((prop = RNA_struct_find_property(op->ptr, "use_multiview")) && RNA_property_boolean_get(op->ptr, prop)) { if (op->customdata) { @@ -368,8 +382,23 @@ static int sequencer_add_scene_strip_exec(bContext *C, wmOperator *op) return OPERATOR_FINISHED; } +static void sequencer_disable_one_time_properties(bContext *C, wmOperator *op) +{ + Editing *ed = SEQ_editing_get(CTX_data_scene(C), false); + /* Disable following properties if there are any existing strips, unless overridden by user. */ + if (ed && ed->seqbasep && ed->seqbasep->first) { + if (RNA_struct_find_property(op->ptr, "use_framerate")) { + RNA_boolean_set(op->ptr, "use_framerate", false); + } + if (RNA_struct_find_property(op->ptr, "set_view_transform")) { + RNA_boolean_set(op->ptr, "set_view_transform", false); + } + } +} + static int sequencer_add_scene_strip_invoke(bContext *C, wmOperator *op, const wmEvent *event) { + sequencer_disable_one_time_properties(C, op); if (!RNA_struct_property_is_set(op->ptr, "scene")) { return WM_enum_search_invoke(C, op, event); } @@ -702,13 +731,9 @@ static int sequencer_add_movie_strip_invoke(bContext *C, { PropertyRNA *prop; Scene *scene = CTX_data_scene(C); - Editing *ed = SEQ_editing_get(scene, false); - /* Only enable "use_framerate" if there aren't any existing strips, unless overridden by user. - */ - if (ed && ed->seqbasep && ed->seqbasep->first) { - RNA_boolean_set(op->ptr, "use_framerate", false); - } + sequencer_disable_one_time_properties(C, op); + RNA_enum_set(op->ptr, "fit_method", SEQ_tool_settings_fit_method_get(scene)); /* This is for drag and drop. */ @@ -775,7 +800,8 @@ void SEQUENCER_OT_movie_strip_add(struct wmOperatorType *ot) WM_FILESEL_SHOW_PROPS | WM_FILESEL_DIRECTORY, FILE_DEFAULTDISPLAY, FILE_SORT_DEFAULT); - sequencer_generic_props__internal(ot, SEQPROP_STARTFRAME | SEQPROP_FIT_METHOD); + sequencer_generic_props__internal( + ot, SEQPROP_STARTFRAME | SEQPROP_FIT_METHOD | SEQPROP_VIEW_TRANSFORM); RNA_def_boolean(ot->srna, "sound", true, "Sound", "Load sound with the movie"); RNA_def_boolean(ot->srna, "use_framerate", @@ -1053,6 +1079,8 @@ static int sequencer_add_image_strip_invoke(bContext *C, PropertyRNA *prop; Scene *scene = CTX_data_scene(C); + sequencer_disable_one_time_properties(C, op); + const SequencerToolSettings *tool_settings = scene->toolsettings->sequencer_tool_settings; RNA_enum_set(op->ptr, "fit_method", tool_settings->fit_method); @@ -1100,8 +1128,8 @@ void SEQUENCER_OT_image_strip_add(struct wmOperatorType *ot) WM_FILESEL_SHOW_PROPS | WM_FILESEL_DIRECTORY, FILE_DEFAULTDISPLAY, FILE_SORT_DEFAULT); - sequencer_generic_props__internal(ot, - SEQPROP_STARTFRAME | SEQPROP_ENDFRAME | SEQPROP_FIT_METHOD); + sequencer_generic_props__internal( + ot, SEQPROP_STARTFRAME | SEQPROP_ENDFRAME | SEQPROP_FIT_METHOD | SEQPROP_VIEW_TRANSFORM); RNA_def_boolean(ot->srna, "use_placeholders", diff --git a/source/blender/editors/space_sequencer/sequencer_edit.c b/source/blender/editors/space_sequencer/sequencer_edit.c index 7279fdd2da2..9c204373ffc 100644 --- a/source/blender/editors/space_sequencer/sequencer_edit.c +++ b/source/blender/editors/space_sequencer/sequencer_edit.c @@ -1608,7 +1608,7 @@ static int sequencer_add_duplicate_exec(bContext *C, wmOperator *UNUSED(op)) BLI_movelisttolist(ed->seqbasep, &nseqbase); for (; seq; seq = seq->next) { - SEQ_iterator_recursive_apply(seq, apply_unique_name_fn, scene); + SEQ_recursive_apply(seq, apply_unique_name_fn, scene); } WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene); @@ -2465,7 +2465,7 @@ static int sequencer_paste_exec(bContext *C, wmOperator *op) for (iseq = iseq_first; iseq; iseq = iseq->next) { /* Make sure, that pasted strips have unique names. */ - SEQ_iterator_recursive_apply(iseq, apply_unique_name_fn, scene); + SEQ_recursive_apply(iseq, apply_unique_name_fn, scene); /* Translate after name has been changed, otherwise this will affect animdata of original * strip. */ SEQ_transform_translate_sequence(scene, iseq, ofs); diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.cc b/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.cc index bd459944cff..02ffa1259fc 100644 --- a/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.cc +++ b/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.cc @@ -276,25 +276,31 @@ std::unique_ptr<ColumnValues> InstancesDataSource::get_column_values( const int size = this->tot_rows(); if (STREQ(column_id.name, "Name")) { - Span<InstancedData> instance_data = component_->instanced_data(); + Span<int> reference_handles = component_->instance_reference_handles(); + Span<InstanceReference> references = component_->references(); std::unique_ptr<ColumnValues> values = column_values_from_function( - "Name", size, [instance_data](int index, CellValue &r_cell_value) { - const InstancedData &data = instance_data[index]; - if (data.type == INSTANCE_DATA_TYPE_OBJECT) { - if (data.data.object != nullptr) { - r_cell_value.value_object = ObjectCellValue{data.data.object}; + "Name", size, [reference_handles, references](int index, CellValue &r_cell_value) { + const InstanceReference &reference = references[reference_handles[index]]; + switch (reference.type()) { + case InstanceReference::Type::Object: { + Object &object = reference.object(); + r_cell_value.value_object = ObjectCellValue{&object}; + break; } - } - else if (data.type == INSTANCE_DATA_TYPE_COLLECTION) { - if (data.data.collection != nullptr) { - r_cell_value.value_collection = CollectionCellValue{data.data.collection}; + case InstanceReference::Type::Collection: { + Collection &collection = reference.collection(); + r_cell_value.value_collection = CollectionCellValue{&collection}; + break; + } + case InstanceReference::Type::None: { + break; } } }); values->default_width = 8.0f; return values; } - Span<float4x4> transforms = component_->transforms(); + Span<float4x4> transforms = component_->instance_transforms(); if (STREQ(column_id.name, "Position")) { return column_values_from_function( column_id.name, @@ -322,7 +328,7 @@ std::unique_ptr<ColumnValues> InstancesDataSource::get_column_values( }, default_float3_column_width); } - Span<int> ids = component_->ids(); + Span<int> ids = component_->instance_ids(); if (STREQ(column_id.name, "ID")) { /* Make the column a bit wider by default, since the IDs tend to be large numbers. */ return column_values_from_function( diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_draw.cc b/source/blender/editors/space_spreadsheet/spreadsheet_draw.cc index d6379c740e8..b911c80fa63 100644 --- a/source/blender/editors/space_spreadsheet/spreadsheet_draw.cc +++ b/source/blender/editors/space_spreadsheet/spreadsheet_draw.cc @@ -142,7 +142,9 @@ static void draw_left_column_content(const int scroll_offset_y, ARegion *region, const SpreadsheetDrawer &drawer) { - GPU_scissor_test(true); + int old_scissor[4]; + GPU_scissor_get(old_scissor); + GPU_scissor(0, 0, drawer.left_column_width, region->winy - drawer.top_row_height); uiBlock *left_column_block = UI_block_begin(C, region, __func__, UI_EMBOSS_NONE); @@ -165,7 +167,7 @@ static void draw_left_column_content(const int scroll_offset_y, UI_block_end(C, left_column_block); UI_block_draw(C, left_column_block); - GPU_scissor_test(false); + GPU_scissor(UNPACK4(old_scissor)); } static void draw_top_row_content(const bContext *C, @@ -173,7 +175,9 @@ static void draw_top_row_content(const bContext *C, const SpreadsheetDrawer &drawer, const int scroll_offset_x) { - GPU_scissor_test(true); + int old_scissor[4]; + GPU_scissor_get(old_scissor); + GPU_scissor(drawer.left_column_width + 1, region->winy - drawer.top_row_height, region->winx - drawer.left_column_width, @@ -200,7 +204,7 @@ static void draw_top_row_content(const bContext *C, UI_block_end(C, first_row_block); UI_block_draw(C, first_row_block); - GPU_scissor_test(false); + GPU_scissor(UNPACK4(old_scissor)); } static void draw_cell_contents(const bContext *C, @@ -209,7 +213,9 @@ static void draw_cell_contents(const bContext *C, const int scroll_offset_x, const int scroll_offset_y) { - GPU_scissor_test(true); + int old_scissor[4]; + GPU_scissor_get(old_scissor); + GPU_scissor(drawer.left_column_width + 1, 0, region->winx - drawer.left_column_width, @@ -248,7 +254,7 @@ static void draw_cell_contents(const bContext *C, UI_block_end(C, cells_block); UI_block_draw(C, cells_block); - GPU_scissor_test(false); + GPU_scissor(UNPACK4(old_scissor)); } static void update_view2d_tot_rect(const SpreadsheetDrawer &drawer, diff --git a/source/blender/editors/space_text/text_ops.c b/source/blender/editors/space_text/text_ops.c index e6803d12a42..9ec759ce4ae 100644 --- a/source/blender/editors/space_text/text_ops.c +++ b/source/blender/editors/space_text/text_ops.c @@ -266,8 +266,6 @@ static int text_new_exec(bContext *C, wmOperator *UNUSED(op)) PropertyRNA *prop; text = BKE_text_add(bmain, "Text"); - /* Texts have no user by default... Only the 'real' user flag. */ - id_us_min(&text->id); /* hook into UI */ UI_context_active_but_prop_get_templateID(C, &ptr, &prop); diff --git a/source/blender/gpu/intern/gpu_context.cc b/source/blender/gpu/intern/gpu_context.cc index a9d32dcf297..b5a437b46f7 100644 --- a/source/blender/gpu/intern/gpu_context.cc +++ b/source/blender/gpu/intern/gpu_context.cc @@ -24,7 +24,7 @@ * Use these instead of glGenBuffers & its friends * - alloc must be called from a thread that is bound * to the context that will be used for drawing with - * this vao. + * this VAO. * - free can be called from any thread */ diff --git a/source/blender/gpu/intern/gpu_framebuffer_private.hh b/source/blender/gpu/intern/gpu_framebuffer_private.hh index ab4e22ebb80..2b00c239af4 100644 --- a/source/blender/gpu/intern/gpu_framebuffer_private.hh +++ b/source/blender/gpu/intern/gpu_framebuffer_private.hh @@ -111,7 +111,7 @@ class FrameBuffer { public: /* Reference of a pointer that needs to be cleaned when deallocating the frame-buffer. - * Points to BPyGPUFrameBuffer::fb */ + * Points to #BPyGPUFrameBuffer::fb */ void **ref = nullptr; public: diff --git a/source/blender/gpu/intern/gpu_matrix.cc b/source/blender/gpu/intern/gpu_matrix.cc index 2ae50d913da..569b51a407a 100644 --- a/source/blender/gpu/intern/gpu_matrix.cc +++ b/source/blender/gpu/intern/gpu_matrix.cc @@ -543,7 +543,7 @@ bool GPU_matrix_unproject_precalc(struct GPUMatrixUnproject_Precalc *precalc, &precalc->dims.zmax); if (isinf(precalc->dims.zmax)) { /* We cannot retrieve the actual value of the clip_end. - * Use `FLT_MAX` to avoid nans. */ + * Use `FLT_MAX` to avoid NAN's. */ precalc->dims.zmax = FLT_MAX; } for (int i = 0; i < 4; i++) { diff --git a/source/blender/gpu/intern/gpu_shader.cc b/source/blender/gpu/intern/gpu_shader.cc index 75fe7652715..aea27756708 100644 --- a/source/blender/gpu/intern/gpu_shader.cc +++ b/source/blender/gpu/intern/gpu_shader.cc @@ -723,7 +723,7 @@ void GPU_shader_uniform_4fv_array(GPUShader *sh, const char *name, int len, cons static int g_shader_builtin_srgb_transform = 0; static bool g_shader_builtin_srgb_is_dirty = false; -static bool gpu_shader_srgb_uniform_dirty_get(void) +static bool gpu_shader_srgb_uniform_dirty_get() { return g_shader_builtin_srgb_is_dirty; } diff --git a/source/blender/gpu/opengl/gl_batch.cc b/source/blender/gpu/opengl/gl_batch.cc index 976a31ab5cb..fc1d1006d0d 100644 --- a/source/blender/gpu/opengl/gl_batch.cc +++ b/source/blender/gpu/opengl/gl_batch.cc @@ -44,7 +44,7 @@ using namespace blender::gpu; /* -------------------------------------------------------------------- */ -/** \name Vao cache +/** \name VAO Cache * * Each #GLBatch has a small cache of VAO objects that are used to avoid VAO reconfiguration. * TODO(fclem): Could be revisited to avoid so much cross references. diff --git a/source/blender/gpu/opengl/gl_batch.hh b/source/blender/gpu/opengl/gl_batch.hh index 444ae1c0ab1..704b6471dd5 100644 --- a/source/blender/gpu/opengl/gl_batch.hh +++ b/source/blender/gpu/opengl/gl_batch.hh @@ -43,7 +43,7 @@ class GLShaderInterface; #define GPU_VAO_STATIC_LEN 3 -/* Vao management: remembers all geometry state (vertex attribute bindings & element buffer) +/* VAO management: remembers all geometry state (vertex attribute bindings & element buffer) * for each shader interface. Start with a static number of vaos and fallback to dynamic count * if necessary. Once a batch goes dynamic it does not go back. */ class GLVaoCache { diff --git a/source/blender/gpu/opengl/gl_immediate.hh b/source/blender/gpu/opengl/gl_immediate.hh index d0bf0fcdf1f..b42f439f9ec 100644 --- a/source/blender/gpu/opengl/gl_immediate.hh +++ b/source/blender/gpu/opengl/gl_immediate.hh @@ -38,7 +38,7 @@ namespace blender::gpu { class GLImmediate : public Immediate { private: - /* Use two buffers for strict and unstrict vertex count to + /* Use two buffers for strict and non-strict vertex count to * avoid some huge driver slowdown (see T70922). * Use accessor functions to get / modify. */ struct { diff --git a/source/blender/imbuf/intern/anim_movie.c b/source/blender/imbuf/intern/anim_movie.c index 96cd1fb61a4..432b62d172a 100644 --- a/source/blender/imbuf/intern/anim_movie.c +++ b/source/blender/imbuf/intern/anim_movie.c @@ -817,7 +817,7 @@ static void ffmpeg_postprocess(struct anim *anim) # if defined(__x86_64__) || defined(_M_X64) /* Scale and flip image over Y axis in one go, using negative strides. - * This doesn't work with arm/powerpc though and may be misusing the API. + * This doesn't work with ARM/PowerPC though and may be misusing the API. * Limit it x86_64 where it appears to work. * http://trac.ffmpeg.org/ticket/9060 */ int *dstStride = anim->pFrameRGB->linesize; diff --git a/source/blender/io/collada/DocumentImporter.cpp b/source/blender/io/collada/DocumentImporter.cpp index 214b5207a96..beadfc98c74 100644 --- a/source/blender/io/collada/DocumentImporter.cpp +++ b/source/blender/io/collada/DocumentImporter.cpp @@ -817,6 +817,8 @@ void DocumentImporter::write_profile_COMMON(COLLADAFW::EffectCommon *ef, Materia matNode.set_ambient(ef->getAmbient()); matNode.set_specular(ef->getSpecular()); matNode.set_reflective(ef->getReflective()); + + matNode.update_material_nodetree(); } /** diff --git a/source/blender/io/collada/Materials.cpp b/source/blender/io/collada/Materials.cpp index 6ba31599fcd..2cc80de2ec1 100644 --- a/source/blender/io/collada/Materials.cpp +++ b/source/blender/io/collada/Materials.cpp @@ -25,8 +25,6 @@ MaterialNode::MaterialNode(bContext *C, Material *ma, KeyImageMap &key_image_map shader_node = add_node(SH_NODE_BSDF_PRINCIPLED, 0, 300, ""); output_node = add_node(SH_NODE_OUTPUT_MATERIAL, 300, 300, ""); add_link(shader_node, 0, output_node, 0); - - ntreeUpdateTree(CTX_data_main(C), ntree); } } @@ -61,8 +59,6 @@ MaterialNode::MaterialNode(bContext *C, shader_node = add_node(SH_NODE_BSDF_PRINCIPLED, 0, 300, ""); output_node = add_node(SH_NODE_OUTPUT_MATERIAL, 300, 300, ""); add_link(shader_node, 0, output_node, 0); - - ntreeUpdateTree(CTX_data_main(C), ntree); #endif } @@ -109,6 +105,11 @@ bNodeTree *MaterialNode::prepare_material_nodetree() return ntree; } +void MaterialNode::update_material_nodetree() +{ + ntreeUpdateTree(CTX_data_main(mContext), ntree); +} + bNode *MaterialNode::add_node(int node_type, int locx, int locy, std::string label) { bNode *node = nodeAddStaticNode(mContext, ntree, node_type); diff --git a/source/blender/io/collada/Materials.h b/source/blender/io/collada/Materials.h index f671a00758d..2d8c823a4c2 100644 --- a/source/blender/io/collada/Materials.h +++ b/source/blender/io/collada/Materials.h @@ -68,4 +68,6 @@ class MaterialNode { void set_alpha(COLLADAFW::EffectCommon::OpaqueMode mode, COLLADAFW::ColorOrTexture &cot, COLLADAFW::FloatOrParam &val); + + void update_material_nodetree(); }; diff --git a/source/blender/makesdna/DNA_brush_types.h b/source/blender/makesdna/DNA_brush_types.h index a11e7d77c67..986c009ac26 100644 --- a/source/blender/makesdna/DNA_brush_types.h +++ b/source/blender/makesdna/DNA_brush_types.h @@ -133,7 +133,8 @@ typedef struct BrushGpencilSettings { /** Factor to extend stroke extremes using fill tool. */ float fill_extend_fac; - char _pad3[4]; + /** Number of pixels to dilate fill area. */ + int dilate_pixels; struct CurveMapping *curve_sensitivity; struct CurveMapping *curve_strength; diff --git a/source/blender/makesrna/intern/rna_attribute.c b/source/blender/makesrna/intern/rna_attribute.c index b5d39157164..a256002ffc1 100644 --- a/source/blender/makesrna/intern/rna_attribute.c +++ b/source/blender/makesrna/intern/rna_attribute.c @@ -71,7 +71,7 @@ const EnumPropertyItem rna_enum_attribute_domain_items[] = { {ATTR_DOMAIN_CORNER, "CORNER", 0, "Face Corner", "Attribute on mesh face corner"}, /* Not implement yet */ // {ATTR_DOMAIN_GRIDS, "GRIDS", 0, "Grids", "Attribute on mesh multires grids"}, - {ATTR_DOMAIN_CURVE, "CURVE", 0, "Curve", "Attribute on hair curve"}, + {ATTR_DOMAIN_CURVE, "CURVE", 0, "Spline", "Attribute on spline"}, {0, NULL, 0, NULL, NULL}, }; @@ -81,6 +81,7 @@ const EnumPropertyItem rna_enum_attribute_domain_with_auto_items[] = { {ATTR_DOMAIN_EDGE, "EDGE", 0, "Edge", "Attribute on mesh edge"}, {ATTR_DOMAIN_FACE, "FACE", 0, "Face", "Attribute on mesh faces"}, {ATTR_DOMAIN_CORNER, "CORNER", 0, "Face Corner", "Attribute on mesh face corner"}, + {ATTR_DOMAIN_CURVE, "CURVE", 0, "Spline", "Attribute on spline"}, {0, NULL, 0, NULL, NULL}, }; diff --git a/source/blender/makesrna/intern/rna_brush.c b/source/blender/makesrna/intern/rna_brush.c index e7daa55af6c..044e6ca2da1 100644 --- a/source/blender/makesrna/intern/rna_brush.c +++ b/source/blender/makesrna/intern/rna_brush.c @@ -1617,6 +1617,15 @@ static void rna_def_gpencil_options(BlenderRNA *brna) prop, "Stroke Extension", "Strokes end extension for closing gaps, use zero to disable"); RNA_def_parameter_clear_flags(prop, PROP_ANIMATABLE, 0); + /* Number of pixels to dilate fill area. */ + prop = RNA_def_property(srna, "dilate_pixels", PROP_INT, PROP_NONE); + RNA_def_property_int_sdna(prop, NULL, "dilate_pixels"); + RNA_def_property_range(prop, 0, 20); + RNA_def_property_int_default(prop, 1); + RNA_def_property_ui_text(prop, "Dilate", "Number of pixels to dilate fill area"); + RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); + RNA_def_property_update(prop, NC_GPENCIL | ND_DATA, NULL); + /* Flags */ prop = RNA_def_property(srna, "use_pressure", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_BRUSH_USE_PRESSURE); diff --git a/source/blender/makesrna/intern/rna_fcurve.c b/source/blender/makesrna/intern/rna_fcurve.c index d4697721bc2..1b89d866aba 100644 --- a/source/blender/makesrna/intern/rna_fcurve.c +++ b/source/blender/makesrna/intern/rna_fcurve.c @@ -1160,6 +1160,7 @@ static void rna_def_fmodifier_generator(BlenderRNA *brna) /* define common props */ prop = RNA_def_property(srna, "use_additive", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "flag", FCM_GENERATOR_ADDITIVE); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_ui_text(prop, "Additive", "Values generated by this modifier are applied on top of " @@ -1168,12 +1169,14 @@ static void rna_def_fmodifier_generator(BlenderRNA *brna) prop = RNA_def_property(srna, "mode", PROP_ENUM, PROP_NONE); RNA_def_property_enum_items(prop, generator_mode_items); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_ui_text(prop, "Mode", "Type of generator to use"); RNA_def_property_update( prop, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, "rna_FModifier_verify_data_update"); /* order of the polynomial */ prop = RNA_def_property(srna, "poly_order", PROP_INT, PROP_NONE); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_ui_text( prop, "Polynomial Order", @@ -1184,6 +1187,7 @@ static void rna_def_fmodifier_generator(BlenderRNA *brna) /* coefficients array */ prop = RNA_def_property(srna, "coefficients", PROP_FLOAT, PROP_NONE); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_array(prop, 32); RNA_def_property_flag(prop, PROP_DYNAMIC); RNA_def_property_dynamic_array_funcs(prop, "rna_FModifierGenerator_coefficients_get_length"); @@ -1221,25 +1225,30 @@ static void rna_def_fmodifier_function_generator(BlenderRNA *brna) /* coefficients */ prop = RNA_def_property(srna, "amplitude", PROP_FLOAT, PROP_NONE); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_ui_text( prop, "Amplitude", "Scale factor determining the maximum/minimum values"); RNA_def_property_update(prop, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, "rna_FModifier_update"); prop = RNA_def_property(srna, "phase_multiplier", PROP_FLOAT, PROP_NONE); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_ui_text( prop, "Phase Multiple", "Scale factor determining the 'speed' of the function"); RNA_def_property_update(prop, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, "rna_FModifier_update"); prop = RNA_def_property(srna, "phase_offset", PROP_FLOAT, PROP_NONE); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_ui_text(prop, "Phase Offset", "Constant factor to offset time by for function"); RNA_def_property_update(prop, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, "rna_FModifier_update"); prop = RNA_def_property(srna, "value_offset", PROP_FLOAT, PROP_NONE); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_ui_text(prop, "Value Offset", "Constant factor to offset values by"); RNA_def_property_update(prop, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, "rna_FModifier_update"); /* flags */ prop = RNA_def_property(srna, "use_additive", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_boolean_sdna(prop, NULL, "flag", FCM_GENERATOR_ADDITIVE); RNA_def_property_ui_text(prop, "Additive", @@ -1248,6 +1257,7 @@ static void rna_def_fmodifier_function_generator(BlenderRNA *brna) RNA_def_property_update(prop, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, "rna_FModifier_update"); prop = RNA_def_property(srna, "function_type", PROP_ENUM, PROP_NONE); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_enum_sdna(prop, NULL, "type"); RNA_def_property_enum_items(prop, prop_type_items); RNA_def_property_ui_text(prop, "Type", "Type of built-in function to use"); @@ -1271,17 +1281,20 @@ static void rna_def_fmodifier_envelope_ctrl(BlenderRNA *brna) */ prop = RNA_def_property(srna, "min", PROP_FLOAT, PROP_NONE); RNA_def_property_float_sdna(prop, NULL, "min"); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_ui_text(prop, "Minimum Value", "Lower bound of envelope at this control-point"); RNA_def_property_update(prop, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, "rna_FModifier_update"); prop = RNA_def_property(srna, "max", PROP_FLOAT, PROP_NONE); RNA_def_property_float_sdna(prop, NULL, "max"); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_ui_text(prop, "Maximum Value", "Upper bound of envelope at this control-point"); RNA_def_property_update(prop, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, "rna_FModifier_update"); /* Frame */ prop = RNA_def_property(srna, "frame", PROP_FLOAT, PROP_TIME); RNA_def_property_float_sdna(prop, NULL, "time"); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_ui_text(prop, "Frame", "Frame this control-point occurs on"); RNA_def_property_update(prop, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, "rna_FModifier_update"); @@ -1340,6 +1353,7 @@ static void rna_def_fmodifier_envelope(BlenderRNA *brna) /* Collections */ prop = RNA_def_property(srna, "control_points", PROP_COLLECTION, PROP_NONE); RNA_def_property_collection_sdna(prop, NULL, "data", "totvert"); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_struct_type(prop, "FModifierEnvelopeControlPoint"); RNA_def_property_ui_text( prop, "Control Points", "Control points defining the shape of the envelope"); @@ -1348,18 +1362,21 @@ static void rna_def_fmodifier_envelope(BlenderRNA *brna) /* Range Settings */ prop = RNA_def_property(srna, "reference_value", PROP_FLOAT, PROP_NONE); RNA_def_property_float_sdna(prop, NULL, "midval"); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_ui_text( prop, "Reference Value", "Value that envelope's influence is centered around / based on"); RNA_def_property_update(prop, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, "rna_FModifier_update"); prop = RNA_def_property(srna, "default_min", PROP_FLOAT, PROP_NONE); RNA_def_property_float_sdna(prop, NULL, "min"); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_ui_text( prop, "Default Minimum", "Lower distance from Reference Value for 1:1 default influence"); RNA_def_property_update(prop, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, "rna_FModifier_update"); prop = RNA_def_property(srna, "default_max", PROP_FLOAT, PROP_NONE); RNA_def_property_float_sdna(prop, NULL, "max"); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_ui_text( prop, "Default Maximum", "Upper distance from Reference Value for 1:1 default influence"); RNA_def_property_update(prop, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, "rna_FModifier_update"); @@ -1395,12 +1412,14 @@ static void rna_def_fmodifier_cycles(BlenderRNA *brna) /* before */ prop = RNA_def_property(srna, "mode_before", PROP_ENUM, PROP_NONE); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_enum_sdna(prop, NULL, "before_mode"); RNA_def_property_enum_items(prop, prop_type_items); RNA_def_property_ui_text(prop, "Before Mode", "Cycling mode to use before first keyframe"); RNA_def_property_update(prop, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, "rna_FModifier_update"); prop = RNA_def_property(srna, "cycles_before", PROP_INT, PROP_NONE); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_int_sdna(prop, NULL, "before_cycles"); RNA_def_property_ui_text( prop, @@ -1410,12 +1429,14 @@ static void rna_def_fmodifier_cycles(BlenderRNA *brna) /* after */ prop = RNA_def_property(srna, "mode_after", PROP_ENUM, PROP_NONE); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_enum_sdna(prop, NULL, "after_mode"); RNA_def_property_enum_items(prop, prop_type_items); RNA_def_property_ui_text(prop, "After Mode", "Cycling mode to use after last keyframe"); RNA_def_property_update(prop, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, "rna_FModifier_update"); prop = RNA_def_property(srna, "cycles_after", PROP_INT, PROP_NONE); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_int_sdna(prop, NULL, "after_cycles"); RNA_def_property_ui_text(prop, "After Cycles", @@ -1450,26 +1471,31 @@ static void rna_def_fmodifier_limits(BlenderRNA *brna) prop = RNA_def_property(srna, "use_min_x", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "flag", FCM_LIMIT_XMIN); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_ui_text(prop, "Minimum X", "Use the minimum X value"); RNA_def_property_update(prop, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, "rna_FModifier_update"); prop = RNA_def_property(srna, "use_min_y", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "flag", FCM_LIMIT_YMIN); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_ui_text(prop, "Minimum Y", "Use the minimum Y value"); RNA_def_property_update(prop, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, "rna_FModifier_update"); prop = RNA_def_property(srna, "use_max_x", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "flag", FCM_LIMIT_XMAX); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_ui_text(prop, "Maximum X", "Use the maximum X value"); RNA_def_property_update(prop, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, "rna_FModifier_update"); prop = RNA_def_property(srna, "use_max_y", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "flag", FCM_LIMIT_YMAX); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_ui_text(prop, "Maximum Y", "Use the maximum Y value"); RNA_def_property_update(prop, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, "rna_FModifier_update"); prop = RNA_def_property(srna, "min_x", PROP_FLOAT, PROP_NONE); RNA_def_property_float_sdna(prop, NULL, "rect.xmin"); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_float_funcs( prop, NULL, "rna_FModifierLimits_minx_set", "rna_FModifierLimits_minx_range"); RNA_def_property_ui_text(prop, "Minimum X", "Lowest X value to allow"); @@ -1477,6 +1503,7 @@ static void rna_def_fmodifier_limits(BlenderRNA *brna) prop = RNA_def_property(srna, "min_y", PROP_FLOAT, PROP_NONE); RNA_def_property_float_sdna(prop, NULL, "rect.ymin"); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_float_funcs( prop, NULL, "rna_FModifierLimits_miny_set", "rna_FModifierLimits_miny_range"); RNA_def_property_ui_text(prop, "Minimum Y", "Lowest Y value to allow"); @@ -1484,6 +1511,7 @@ static void rna_def_fmodifier_limits(BlenderRNA *brna) prop = RNA_def_property(srna, "max_x", PROP_FLOAT, PROP_NONE); RNA_def_property_float_sdna(prop, NULL, "rect.xmax"); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_float_funcs( prop, NULL, "rna_FModifierLimits_maxx_set", "rna_FModifierLimits_maxx_range"); RNA_def_property_ui_text(prop, "Maximum X", "Highest X value to allow"); @@ -1491,6 +1519,7 @@ static void rna_def_fmodifier_limits(BlenderRNA *brna) prop = RNA_def_property(srna, "max_y", PROP_FLOAT, PROP_NONE); RNA_def_property_float_sdna(prop, NULL, "rect.ymax"); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_float_funcs( prop, NULL, "rna_FModifierLimits_maxy_set", "rna_FModifierLimits_maxy_range"); RNA_def_property_ui_text(prop, "Maximum Y", "Highest Y value to allow"); @@ -1519,16 +1548,19 @@ static void rna_def_fmodifier_noise(BlenderRNA *brna) prop = RNA_def_property(srna, "blend_type", PROP_ENUM, PROP_NONE); RNA_def_property_enum_sdna(prop, NULL, "modification"); RNA_def_property_enum_items(prop, prop_modification_items); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_ui_text(prop, "Blend Type", "Method of modifying the existing F-Curve"); RNA_def_property_update(prop, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, "rna_FModifier_update"); prop = RNA_def_property(srna, "scale", PROP_FLOAT, PROP_NONE); RNA_def_property_float_sdna(prop, NULL, "size"); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_ui_text(prop, "Scale", "Scaling (in time) of the noise"); RNA_def_property_update(prop, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, "rna_FModifier_update"); prop = RNA_def_property(srna, "strength", PROP_FLOAT, PROP_NONE); RNA_def_property_float_sdna(prop, NULL, "strength"); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_ui_text( prop, "Strength", @@ -1537,16 +1569,19 @@ static void rna_def_fmodifier_noise(BlenderRNA *brna) prop = RNA_def_property(srna, "phase", PROP_FLOAT, PROP_NONE); RNA_def_property_float_sdna(prop, NULL, "phase"); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_ui_text(prop, "Phase", "A random seed for the noise effect"); RNA_def_property_update(prop, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, "rna_FModifier_update"); prop = RNA_def_property(srna, "offset", PROP_FLOAT, PROP_NONE); RNA_def_property_float_sdna(prop, NULL, "offset"); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_ui_text(prop, "Offset", "Time offset for the noise effect"); RNA_def_property_update(prop, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, "rna_FModifier_update"); prop = RNA_def_property(srna, "depth", PROP_INT, PROP_UNSIGNED); RNA_def_property_int_sdna(prop, NULL, "depth"); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_ui_text(prop, "Depth", "Amount of fine level detail present in the noise"); RNA_def_property_update(prop, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, "rna_FModifier_update"); } @@ -1569,11 +1604,13 @@ static void rna_def_fmodifier_stepped(BlenderRNA *brna) /* properties */ prop = RNA_def_property(srna, "frame_step", PROP_FLOAT, PROP_NONE); RNA_def_property_float_sdna(prop, NULL, "step_size"); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_ui_text(prop, "Step Size", "Number of frames to hold each value"); RNA_def_property_update(prop, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, "rna_FModifier_update"); prop = RNA_def_property(srna, "frame_offset", PROP_FLOAT, PROP_NONE); RNA_def_property_float_sdna(prop, NULL, "offset"); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_ui_text(prop, "Offset", "Reference number of frames before frames get held " @@ -1582,18 +1619,21 @@ static void rna_def_fmodifier_stepped(BlenderRNA *brna) prop = RNA_def_property(srna, "use_frame_start", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "flag", FCM_STEPPED_NO_BEFORE); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_ui_text( prop, "Use Start Frame", "Restrict modifier to only act after its 'start' frame"); RNA_def_property_update(prop, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, "rna_FModifier_update"); prop = RNA_def_property(srna, "use_frame_end", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "flag", FCM_STEPPED_NO_AFTER); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_ui_text( prop, "Use End Frame", "Restrict modifier to only act before its 'end' frame"); RNA_def_property_update(prop, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, "rna_FModifier_update"); prop = RNA_def_property(srna, "frame_start", PROP_FLOAT, PROP_NONE); RNA_def_property_float_sdna(prop, NULL, "start_frame"); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_float_funcs(prop, NULL, "rna_FModifierStepped_frame_start_set", @@ -1604,6 +1644,7 @@ static void rna_def_fmodifier_stepped(BlenderRNA *brna) prop = RNA_def_property(srna, "frame_end", PROP_FLOAT, PROP_NONE); RNA_def_property_float_sdna(prop, NULL, "end_frame"); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_float_funcs( prop, NULL, "rna_FModifierStepped_frame_end_set", "rna_FModifierStepped_end_frame_range"); RNA_def_property_ui_text( @@ -1641,12 +1682,14 @@ static void rna_def_fmodifier(BlenderRNA *brna) prop = RNA_def_property(srna, "show_expanded", PROP_BOOLEAN, PROP_NONE); RNA_def_property_flag(prop, PROP_NO_DEG_UPDATE); RNA_def_property_boolean_sdna(prop, NULL, "ui_expand_flag", 0); + // RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_boolean_funcs(prop, NULL, "rna_FModifier_show_expanded_set"); RNA_def_property_ui_text(prop, "Expanded", "F-Curve Modifier's panel is expanded in UI"); RNA_def_property_ui_icon(prop, ICON_DISCLOSURE_TRI_RIGHT, 1); prop = RNA_def_property(srna, "mute", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "flag", FMODIFIER_FLAG_MUTED); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_ui_text(prop, "Enabled", "Enable F-Curve modifier evaluation"); RNA_def_property_update(prop, NC_ANIMATION | ND_KEYFRAME_PROP, "rna_FModifier_update"); RNA_def_property_ui_icon(prop, ICON_CHECKBOX_HLT, -1); @@ -1654,6 +1697,7 @@ static void rna_def_fmodifier(BlenderRNA *brna) prop = RNA_def_property(srna, "is_valid", PROP_BOOLEAN, PROP_NONE); RNA_def_property_clear_flag(prop, PROP_EDITABLE); RNA_def_property_boolean_negative_sdna(prop, NULL, "flag", FMODIFIER_FLAG_DISABLED); + // RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_ui_text( prop, "Disabled", "F-Curve Modifier has invalid settings and will not be evaluated"); RNA_def_property_update(prop, NC_ANIMATION | ND_KEYFRAME_PROP, "rna_FModifier_update"); @@ -1661,6 +1705,7 @@ static void rna_def_fmodifier(BlenderRNA *brna) /* TODO: setting this to true must ensure that all others in stack are turned off too... */ prop = RNA_def_property(srna, "active", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "flag", FMODIFIER_FLAG_ACTIVE); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_ui_text(prop, "Active", "F-Curve modifier will show settings in the editor"); RNA_def_property_boolean_funcs(prop, NULL, "rna_FModifier_active_set"); RNA_def_property_update(prop, NC_ANIMATION | ND_KEYFRAME_PROP, "rna_FModifier_active_update"); @@ -1669,6 +1714,7 @@ static void rna_def_fmodifier(BlenderRNA *brna) /* restricted range */ prop = RNA_def_property(srna, "use_restricted_range", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "flag", FMODIFIER_FLAG_RANGERESTRICT); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_ui_text( prop, "Restrict Frame Range", @@ -1678,6 +1724,7 @@ static void rna_def_fmodifier(BlenderRNA *brna) prop = RNA_def_property(srna, "frame_start", PROP_FLOAT, PROP_NONE); RNA_def_property_float_sdna(prop, NULL, "sfra"); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_float_funcs( prop, NULL, "rna_FModifier_start_frame_set", "rna_FModifier_start_frame_range"); RNA_def_property_ui_text( @@ -1688,6 +1735,7 @@ static void rna_def_fmodifier(BlenderRNA *brna) prop = RNA_def_property(srna, "frame_end", PROP_FLOAT, PROP_NONE); RNA_def_property_float_sdna(prop, NULL, "efra"); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_float_funcs( prop, NULL, "rna_FModifier_end_frame_set", "rna_FModifier_end_frame_range"); RNA_def_property_ui_text( @@ -1698,6 +1746,7 @@ static void rna_def_fmodifier(BlenderRNA *brna) prop = RNA_def_property(srna, "blend_in", PROP_FLOAT, PROP_NONE); RNA_def_property_float_sdna(prop, NULL, "blendin"); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_float_funcs(prop, NULL, NULL, "rna_FModifier_blending_range"); RNA_def_property_ui_text( prop, "Blend In", "Number of frames from start frame for influence to take effect"); @@ -1705,6 +1754,7 @@ static void rna_def_fmodifier(BlenderRNA *brna) prop = RNA_def_property(srna, "blend_out", PROP_FLOAT, PROP_NONE); RNA_def_property_float_sdna(prop, NULL, "blendout"); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_float_funcs(prop, NULL, NULL, "rna_FModifier_blending_range"); RNA_def_property_ui_text( prop, "Blend Out", "Number of frames from end frame for influence to fade out"); @@ -1713,12 +1763,14 @@ static void rna_def_fmodifier(BlenderRNA *brna) /* influence */ prop = RNA_def_property(srna, "use_influence", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "flag", FMODIFIER_FLAG_USEINFLUENCE); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_ui_text( prop, "Use Influence", "F-Curve Modifier's effects will be tempered by a default factor"); RNA_def_property_update(prop, NC_ANIMATION | ND_KEYFRAME_PROP, "rna_FModifier_update"); prop = RNA_def_property(srna, "influence", PROP_FLOAT, PROP_FACTOR); RNA_def_property_float_sdna(prop, NULL, "influence"); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_range(prop, 0.0f, 1.0f); RNA_def_property_float_default(prop, 1.0f); RNA_def_property_ui_text( @@ -2162,6 +2214,7 @@ static void rna_def_fcurve_modifiers(BlenderRNA *brna, PropertyRNA *cprop) /* Collection active property */ prop = RNA_def_property(srna, "active", PROP_POINTER, PROP_NONE); RNA_def_property_struct_type(prop, "FModifier"); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_pointer_funcs( prop, "rna_FCurve_active_modifier_get", "rna_FCurve_active_modifier_set", NULL, NULL); RNA_def_property_flag(prop, PROP_EDITABLE); diff --git a/source/blender/makesrna/intern/rna_material.c b/source/blender/makesrna/intern/rna_material.c index 7ef1904fc34..a4052430d9a 100644 --- a/source/blender/makesrna/intern/rna_material.c +++ b/source/blender/makesrna/intern/rna_material.c @@ -840,6 +840,8 @@ void RNA_def_material(BlenderRNA *brna) prop = RNA_def_property(srna, "node_tree", PROP_POINTER, PROP_NONE); RNA_def_property_pointer_sdna(prop, NULL, "nodetree"); RNA_def_property_clear_flag(prop, PROP_PTR_NO_OWNERSHIP); + /* XXX: remove once overrides in material node trees are supported. */ + RNA_def_property_override_flag(prop, PROPOVERRIDE_IGNORE); RNA_def_property_ui_text(prop, "Node Tree", "Node tree for node based materials"); prop = RNA_def_property(srna, "use_nodes", PROP_BOOLEAN, PROP_NONE); diff --git a/source/blender/makesrna/intern/rna_mesh.c b/source/blender/makesrna/intern/rna_mesh.c index 03b3d92eea8..f772f9b5573 100644 --- a/source/blender/makesrna/intern/rna_mesh.c +++ b/source/blender/makesrna/intern/rna_mesh.c @@ -958,13 +958,14 @@ static void rna_MeshPaintMaskLayer_data_begin(CollectionPropertyIterator *iter, { Mesh *me = rna_mesh(ptr); CustomDataLayer *layer = (CustomDataLayer *)ptr->data; - rna_iterator_array_begin(iter, layer->data, sizeof(MFloatProperty), me->totvert, 0, NULL); + rna_iterator_array_begin( + iter, layer->data, sizeof(MFloatProperty), (me->edit_mesh) ? 0 : me->totvert, 0, NULL); } static int rna_MeshPaintMaskLayer_data_length(PointerRNA *ptr) { Mesh *me = rna_mesh(ptr); - return me->totvert; + return (me->edit_mesh) ? 0 : me->totvert; } /* End paint mask */ @@ -987,13 +988,14 @@ static void rna_MeshFaceMapLayer_data_begin(CollectionPropertyIterator *iter, Po { Mesh *me = rna_mesh(ptr); CustomDataLayer *layer = (CustomDataLayer *)ptr->data; - rna_iterator_array_begin(iter, layer->data, sizeof(int), me->totpoly, 0, NULL); + rna_iterator_array_begin( + iter, layer->data, sizeof(int), (me->edit_mesh) ? 0 : me->totpoly, 0, NULL); } static int rna_MeshFaceMapLayer_data_length(PointerRNA *ptr) { Mesh *me = rna_mesh(ptr); - return me->totpoly; + return (me->edit_mesh) ? 0 : me->totpoly; } static PointerRNA rna_Mesh_face_map_new(struct Mesh *me, ReportList *reports, const char *name) diff --git a/source/blender/makesrna/intern/rna_movieclip.c b/source/blender/makesrna/intern/rna_movieclip.c index c9520c939f4..f8fa2aab5e7 100644 --- a/source/blender/makesrna/intern/rna_movieclip.c +++ b/source/blender/makesrna/intern/rna_movieclip.c @@ -402,7 +402,7 @@ static void rna_def_movieclip(BlenderRNA *brna) "Start Frame", "Global scene frame number at which this movie starts playing " "(affects all data associated with a clip)"); - RNA_def_property_update(prop, NC_MOVIECLIP | ND_DISPLAY, "rna_MovieClip_reload_update"); + RNA_def_property_update(prop, NC_MOVIECLIP | ND_DISPLAY, NULL); /* frame_offset */ prop = RNA_def_property(srna, "frame_offset", PROP_INT, PROP_NONE); @@ -412,7 +412,7 @@ static void rna_def_movieclip(BlenderRNA *brna) "Frame Offset", "Offset of footage first frame relative to its file name " "(affects only how footage is loading, does not change data associated with a clip)"); - RNA_def_property_update(prop, NC_MOVIECLIP | ND_DISPLAY, "rna_MovieClip_reload_update"); + RNA_def_property_update(prop, NC_MOVIECLIP | ND_DISPLAY, NULL); /* length */ prop = RNA_def_property(srna, "frame_duration", PROP_INT, PROP_NONE); diff --git a/source/blender/makesrna/intern/rna_sequencer.c b/source/blender/makesrna/intern/rna_sequencer.c index f411566b623..b2e399d41f5 100644 --- a/source/blender/makesrna/intern/rna_sequencer.c +++ b/source/blender/makesrna/intern/rna_sequencer.c @@ -505,7 +505,7 @@ static Sequence *sequence_get_by_transform(Editing *ed, StripTransform *transfor data.data = transform; /* irritating we need to search for our sequence! */ - SEQ_iterator_seqbase_recursive_apply(&ed->seqbase, transform_seq_cmp_fn, &data); + SEQ_seqbase_recursive_apply(&ed->seqbase, transform_seq_cmp_fn, &data); return data.seq; } @@ -557,7 +557,7 @@ static Sequence *sequence_get_by_crop(Editing *ed, StripCrop *crop) data.data = crop; /* irritating we need to search for our sequence! */ - SEQ_iterator_seqbase_recursive_apply(&ed->seqbase, crop_seq_cmp_fn, &data); + SEQ_seqbase_recursive_apply(&ed->seqbase, crop_seq_cmp_fn, &data); return data.seq; } @@ -951,7 +951,7 @@ static Sequence *sequence_get_by_proxy(Editing *ed, StripProxy *proxy) data.seq = NULL; data.data = proxy; - SEQ_iterator_seqbase_recursive_apply(&ed->seqbase, seqproxy_seq_cmp_fn, &data); + SEQ_seqbase_recursive_apply(&ed->seqbase, seqproxy_seq_cmp_fn, &data); return data.seq; } @@ -1016,7 +1016,7 @@ static Sequence *sequence_get_by_colorbalance(Editing *ed, data.data = cb; /* irritating we need to search for our sequence! */ - SEQ_iterator_seqbase_recursive_apply(&ed->seqbase, colbalance_seq_cmp_fn, &data); + SEQ_seqbase_recursive_apply(&ed->seqbase, colbalance_seq_cmp_fn, &data); *r_smd = data.smd; @@ -1140,7 +1140,7 @@ static Sequence *sequence_get_by_modifier(Editing *ed, SequenceModifierData *smd data.data = smd; /* irritating we need to search for our sequence! */ - SEQ_iterator_seqbase_recursive_apply(&ed->seqbase, modifier_seq_cmp_fn, &data); + SEQ_seqbase_recursive_apply(&ed->seqbase, modifier_seq_cmp_fn, &data); return data.seq; } diff --git a/source/blender/makesrna/intern/rna_space.c b/source/blender/makesrna/intern/rna_space.c index e3e3f2c2cda..9ddeb67f075 100644 --- a/source/blender/makesrna/intern/rna_space.c +++ b/source/blender/makesrna/intern/rna_space.c @@ -3050,6 +3050,10 @@ static void rna_SpaceSpreadsheet_geometry_component_type_update(Main *UNUSED(bma if (sspreadsheet->geometry_component_type == GEO_COMPONENT_TYPE_POINT_CLOUD) { sspreadsheet->attribute_domain = ATTR_DOMAIN_POINT; } + if (sspreadsheet->geometry_component_type == GEO_COMPONENT_TYPE_CURVE && + !ELEM(sspreadsheet->attribute_domain, ATTR_DOMAIN_POINT, ATTR_DOMAIN_CURVE)) { + sspreadsheet->attribute_domain = ATTR_DOMAIN_POINT; + } } const EnumPropertyItem *rna_SpaceSpreadsheet_attribute_domain_itemf(bContext *UNUSED(C), @@ -3095,6 +3099,11 @@ const EnumPropertyItem *rna_SpaceSpreadsheet_attribute_domain_itemf(bContext *UN continue; } } + if (component_type == GEO_COMPONENT_TYPE_CURVE) { + if (!ELEM(item->value, ATTR_DOMAIN_POINT, ATTR_DOMAIN_CURVE)) { + continue; + } + } if (item->value == ATTR_DOMAIN_POINT && component_type == GEO_COMPONENT_TYPE_MESH) { RNA_enum_item_add(&item_array, &items_len, &mesh_vertex_domain_item); } @@ -7495,6 +7504,11 @@ static void rna_def_space_spreadsheet(BlenderRNA *brna) ICON_POINTCLOUD_DATA, "Point Cloud", "Point cloud component containing only point data"}, + {GEO_COMPONENT_TYPE_CURVE, + "CURVE", + ICON_CURVE_DATA, + "Curve", + "Curve component containing spline and control point data"}, {GEO_COMPONENT_TYPE_INSTANCES, "INSTANCES", ICON_EMPTY_AXIS, diff --git a/source/blender/modifiers/intern/MOD_nodes.cc b/source/blender/modifiers/intern/MOD_nodes.cc index 0d4cf86635f..71a47a24f54 100644 --- a/source/blender/modifiers/intern/MOD_nodes.cc +++ b/source/blender/modifiers/intern/MOD_nodes.cc @@ -183,7 +183,7 @@ static void add_object_relation(const ModifierUpdateDepsgraphContext *ctx, Objec if (object.type == OB_EMPTY && object.instance_collection != nullptr) { add_collection_relation(ctx, *object.instance_collection); } - else if (ELEM(object.type, OB_MESH, OB_POINTCLOUD, OB_VOLUME)) { + else if (ELEM(object.type, OB_MESH, OB_POINTCLOUD, OB_VOLUME, OB_CURVE)) { DEG_add_object_relation(ctx->node, &object, DEG_OB_COMP_GEOMETRY, "Nodes Modifier"); DEG_add_customdata_mask(ctx->node, &object, &dependency_data_mask); } diff --git a/source/blender/nodes/CMakeLists.txt b/source/blender/nodes/CMakeLists.txt index 38814646d75..c116ceb8968 100644 --- a/source/blender/nodes/CMakeLists.txt +++ b/source/blender/nodes/CMakeLists.txt @@ -160,6 +160,7 @@ set(SRC geometry/nodes/node_geo_bounding_box.cc geometry/nodes/node_geo_collection_info.cc geometry/nodes/node_geo_common.cc + geometry/nodes/node_geo_curve_to_mesh.cc geometry/nodes/node_geo_edge_split.cc geometry/nodes/node_geo_is_viewport.cc geometry/nodes/node_geo_join_geometry.cc diff --git a/source/blender/nodes/NOD_geometry.h b/source/blender/nodes/NOD_geometry.h index af2a1665b3f..14c9e235bb6 100644 --- a/source/blender/nodes/NOD_geometry.h +++ b/source/blender/nodes/NOD_geometry.h @@ -48,6 +48,7 @@ void register_node_type_geo_attribute_remove(void); void register_node_type_geo_boolean(void); void register_node_type_geo_bounding_box(void); void register_node_type_geo_collection_info(void); +void register_node_type_geo_curve_to_mesh(void); void register_node_type_geo_edge_split(void); void register_node_type_geo_is_viewport(void); void register_node_type_geo_join_geometry(void); diff --git a/source/blender/nodes/NOD_static_types.h b/source/blender/nodes/NOD_static_types.h index 8b498df0a81..2e4187229f4 100644 --- a/source/blender/nodes/NOD_static_types.h +++ b/source/blender/nodes/NOD_static_types.h @@ -312,6 +312,7 @@ DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_CLAMP, def_geo_attribute_clamp, "ATTRIB DefNode(GeometryNode, GEO_NODE_BOUNDING_BOX, 0, "BOUNDING_BOX", BoundBox, "Bounding Box", "") DefNode(GeometryNode, GEO_NODE_SWITCH, def_geo_switch, "SWITCH", Switch, "Switch", "") DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_TRANSFER, def_geo_attribute_transfer, "ATTRIBUTE_TRANSFER", AttributeTransfer, "Attribute Transfer", "") +DefNode(GeometryNode, GEO_NODE_CURVE_TO_MESH, 0, "CURVE_TO_MESH", CurveToMesh, "Curve to Mesh", "") /* undefine macros */ #undef DefNode diff --git a/source/blender/nodes/geometry/nodes/node_geo_align_rotation_to_vector.cc b/source/blender/nodes/geometry/nodes/node_geo_align_rotation_to_vector.cc index c4d37a82617..720ca9731a8 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_align_rotation_to_vector.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_align_rotation_to_vector.cc @@ -15,6 +15,7 @@ */ #include "BLI_math_rotation.h" +#include "BLI_task.hh" #include "UI_interface.h" #include "UI_resources.h" @@ -55,42 +56,44 @@ static void align_rotations_auto_pivot(const VArray<float3> &vectors, const float3 local_main_axis, const MutableSpan<float3> rotations) { - for (const int i : IndexRange(vectors.size())) { - const float3 vector = vectors[i]; - if (is_zero_v3(vector)) { - continue; - } + parallel_for(IndexRange(vectors.size()), 128, [&](IndexRange range) { + for (const int i : range) { + const float3 vector = vectors[i]; + if (is_zero_v3(vector)) { + continue; + } - float old_rotation[3][3]; - eul_to_mat3(old_rotation, rotations[i]); - float3 old_axis; - mul_v3_m3v3(old_axis, old_rotation, local_main_axis); + float old_rotation[3][3]; + eul_to_mat3(old_rotation, rotations[i]); + float3 old_axis; + mul_v3_m3v3(old_axis, old_rotation, local_main_axis); - const float3 new_axis = vector.normalized(); - float3 rotation_axis = float3::cross_high_precision(old_axis, new_axis); - if (is_zero_v3(rotation_axis)) { - /* The vectors are linearly dependent, so we fall back to another axis. */ - rotation_axis = float3::cross_high_precision(old_axis, float3(1, 0, 0)); + const float3 new_axis = vector.normalized(); + float3 rotation_axis = float3::cross_high_precision(old_axis, new_axis); if (is_zero_v3(rotation_axis)) { - /* This is now guaranteed to not be zero. */ - rotation_axis = float3::cross_high_precision(old_axis, float3(0, 1, 0)); + /* The vectors are linearly dependent, so we fall back to another axis. */ + rotation_axis = float3::cross_high_precision(old_axis, float3(1, 0, 0)); + if (is_zero_v3(rotation_axis)) { + /* This is now guaranteed to not be zero. */ + rotation_axis = float3::cross_high_precision(old_axis, float3(0, 1, 0)); + } } - } - const float full_angle = angle_normalized_v3v3(old_axis, new_axis); - const float angle = factors[i] * full_angle; + const float full_angle = angle_normalized_v3v3(old_axis, new_axis); + const float angle = factors[i] * full_angle; - float rotation[3][3]; - axis_angle_to_mat3(rotation, rotation_axis, angle); + float rotation[3][3]; + axis_angle_to_mat3(rotation, rotation_axis, angle); - float new_rotation_matrix[3][3]; - mul_m3_m3m3(new_rotation_matrix, rotation, old_rotation); + float new_rotation_matrix[3][3]; + mul_m3_m3m3(new_rotation_matrix, rotation, old_rotation); - float3 new_rotation; - mat3_to_eul(new_rotation, new_rotation_matrix); + float3 new_rotation; + mat3_to_eul(new_rotation, new_rotation_matrix); - rotations[i] = new_rotation; - } + rotations[i] = new_rotation; + } + }); } static void align_rotations_fixed_pivot(const VArray<float3> &vectors, @@ -104,37 +107,39 @@ static void align_rotations_fixed_pivot(const VArray<float3> &vectors, return; } - for (const int i : IndexRange(vectors.size())) { - const float3 vector = vectors[i]; - if (is_zero_v3(vector)) { - continue; - } + parallel_for(IndexRange(vectors.size()), 128, [&](IndexRange range) { + for (const int i : range) { + const float3 vector = vectors[i]; + if (is_zero_v3(vector)) { + continue; + } - float old_rotation[3][3]; - eul_to_mat3(old_rotation, rotations[i]); - float3 old_axis; - mul_v3_m3v3(old_axis, old_rotation, local_main_axis); - float3 pivot_axis; - mul_v3_m3v3(pivot_axis, old_rotation, local_pivot_axis); - - float full_angle = angle_signed_on_axis_v3v3_v3(vector, old_axis, pivot_axis); - if (full_angle > M_PI) { - /* Make sure the point is rotated as little as possible. */ - full_angle -= 2.0f * M_PI; - } - const float angle = factors[i] * full_angle; + float old_rotation[3][3]; + eul_to_mat3(old_rotation, rotations[i]); + float3 old_axis; + mul_v3_m3v3(old_axis, old_rotation, local_main_axis); + float3 pivot_axis; + mul_v3_m3v3(pivot_axis, old_rotation, local_pivot_axis); + + float full_angle = angle_signed_on_axis_v3v3_v3(vector, old_axis, pivot_axis); + if (full_angle > M_PI) { + /* Make sure the point is rotated as little as possible. */ + full_angle -= 2.0f * M_PI; + } + const float angle = factors[i] * full_angle; - float rotation[3][3]; - axis_angle_to_mat3(rotation, pivot_axis, angle); + float rotation[3][3]; + axis_angle_to_mat3(rotation, pivot_axis, angle); - float new_rotation_matrix[3][3]; - mul_m3_m3m3(new_rotation_matrix, rotation, old_rotation); + float new_rotation_matrix[3][3]; + mul_m3_m3m3(new_rotation_matrix, rotation, old_rotation); - float3 new_rotation; - mat3_to_eul(new_rotation, new_rotation_matrix); + float3 new_rotation; + mat3_to_eul(new_rotation, new_rotation_matrix); - rotations[i] = new_rotation; - } + rotations[i] = new_rotation; + } + }); } static void align_rotations_on_component(GeometryComponent &component, @@ -183,6 +188,9 @@ static void geo_node_align_rotation_to_vector_exec(GeoNodeExecParams params) align_rotations_on_component(geometry_set.get_component_for_write<PointCloudComponent>(), params); } + if (geometry_set.has<CurveComponent>()) { + align_rotations_on_component(geometry_set.get_component_for_write<CurveComponent>(), params); + } params.set_output("Geometry", geometry_set); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_clamp.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_clamp.cc index 1943971d49f..21538db5455 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_clamp.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_clamp.cc @@ -255,6 +255,9 @@ static void geo_node_attribute_clamp_exec(GeoNodeExecParams params) if (geometry_set.has<PointCloudComponent>()) { clamp_attribute(geometry_set.get_component_for_write<PointCloudComponent>(), params); } + if (geometry_set.has<CurveComponent>()) { + clamp_attribute(geometry_set.get_component_for_write<CurveComponent>(), params); + } params.set_output("Geometry", geometry_set); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_color_ramp.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_color_ramp.cc index 9e697226a01..26ddb0da515 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_color_ramp.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_color_ramp.cc @@ -14,6 +14,8 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +#include "BLI_task.hh" + #include "BKE_colorband.h" #include "UI_interface.h" @@ -85,9 +87,11 @@ static void execute_on_component(const GeoNodeExecParams ¶ms, GeometryCompon MutableSpan<Color4f> results = attribute_result.as_span(); ColorBand *color_ramp = &node_storage->color_ramp; - for (const int i : IndexRange(attribute_in.size())) { - BKE_colorband_evaluate(color_ramp, attribute_in[i], results[i]); - } + parallel_for(IndexRange(attribute_in.size()), 512, [&](IndexRange range) { + for (const int i : range) { + BKE_colorband_evaluate(color_ramp, attribute_in[i], results[i]); + } + }); attribute_result.save(); } @@ -104,6 +108,9 @@ static void geo_node_attribute_color_ramp_exec(GeoNodeExecParams params) if (geometry_set.has<PointCloudComponent>()) { execute_on_component(params, geometry_set.get_component_for_write<PointCloudComponent>()); } + if (geometry_set.has<CurveComponent>()) { + execute_on_component(params, geometry_set.get_component_for_write<CurveComponent>()); + } params.set_output("Geometry", std::move(geometry_set)); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_combine_xyz.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_combine_xyz.cc index d7ed8b6caf8..d8c52d16f41 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_combine_xyz.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_combine_xyz.cc @@ -127,6 +127,9 @@ static void geo_node_attribute_combine_xyz_exec(GeoNodeExecParams params) if (geometry_set.has<PointCloudComponent>()) { combine_attributes(geometry_set.get_component_for_write<PointCloudComponent>(), params); } + if (geometry_set.has<CurveComponent>()) { + combine_attributes(geometry_set.get_component_for_write<CurveComponent>(), params); + } params.set_output("Geometry", geometry_set); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_compare.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_compare.cc index ccfaeb9bb47..a2ff1668a06 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_compare.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_compare.cc @@ -334,6 +334,9 @@ static void geo_node_attribute_compare_exec(GeoNodeExecParams params) if (geometry_set.has<PointCloudComponent>()) { attribute_compare_calc(geometry_set.get_component_for_write<PointCloudComponent>(), params); } + if (geometry_set.has<CurveComponent>()) { + attribute_compare_calc(geometry_set.get_component_for_write<CurveComponent>(), params); + } params.set_output("Geometry", geometry_set); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_convert.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_convert.cc index 28fd06a8cc1..7b40456b180 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_convert.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_convert.cc @@ -167,6 +167,14 @@ static void geo_node_attribute_convert_exec(GeoNodeExecParams params) data_type, domain); } + if (geometry_set.has<CurveComponent>()) { + attribute_convert_calc(geometry_set.get_component_for_write<CurveComponent>(), + params, + source_name, + result_name, + data_type, + domain); + } params.set_output("Geometry", geometry_set); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_fill.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_fill.cc index 3df64625b99..60522fd0f72 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_fill.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_fill.cc @@ -143,6 +143,9 @@ static void geo_node_attribute_fill_exec(GeoNodeExecParams params) if (geometry_set.has<PointCloudComponent>()) { fill_attribute(geometry_set.get_component_for_write<PointCloudComponent>(), params); } + if (geometry_set.has<CurveComponent>()) { + fill_attribute(geometry_set.get_component_for_write<CurveComponent>(), params); + } params.set_output("Geometry", geometry_set); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_map_range.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_map_range.cc index 5d82897e9db..40fe675bd6c 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_map_range.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_map_range.cc @@ -15,6 +15,7 @@ */ #include "BLI_math_base_safe.h" +#include "BLI_task.hh" #include "UI_interface.h" #include "UI_resources.h" @@ -208,28 +209,36 @@ static void map_range_float(const VArray<float> &attribute_input, switch (interpolation_type) { case NODE_MAP_RANGE_LINEAR: { - for (int i : span.index_range()) { - results[i] = map_linear(span[i], min_from, max_from, min_to, max_to); - } + parallel_for(span.index_range(), 2048, [&](IndexRange range) { + for (const int i : range) { + results[i] = map_linear(span[i], min_from, max_from, min_to, max_to); + } + }); break; } case NODE_MAP_RANGE_STEPPED: { const float steps = params.get_input<float>("Steps"); - for (int i : span.index_range()) { - results[i] = map_stepped(span[i], min_from, max_from, min_to, max_to, steps); - } + parallel_for(span.index_range(), 1024, [&](IndexRange range) { + for (const int i : range) { + results[i] = map_stepped(span[i], min_from, max_from, min_to, max_to, steps); + } + }); break; } case NODE_MAP_RANGE_SMOOTHSTEP: { - for (int i : span.index_range()) { - results[i] = map_smoothstep(span[i], min_from, max_from, min_to, max_to); - } + parallel_for(span.index_range(), 1024, [&](IndexRange range) { + for (const int i : range) { + results[i] = map_smoothstep(span[i], min_from, max_from, min_to, max_to); + } + }); break; } case NODE_MAP_RANGE_SMOOTHERSTEP: { - for (int i : span.index_range()) { - results[i] = map_smootherstep(span[i], min_from, max_from, min_to, max_to); - } + parallel_for(span.index_range(), 1024, [&](IndexRange range) { + for (const int i : range) { + results[i] = map_smootherstep(span[i], min_from, max_from, min_to, max_to); + } + }); break; } } @@ -240,9 +249,11 @@ static void map_range_float(const VArray<float> &attribute_input, const float clamp_min = min_to < max_to ? min_to : max_to; const float clamp_max = min_to < max_to ? max_to : min_to; - for (int i : results.index_range()) { - results[i] = std::clamp(results[i], clamp_min, clamp_max); - } + parallel_for(results.index_range(), 2048, [&](IndexRange range) { + for (const int i : range) { + results[i] = std::clamp(results[i], clamp_min, clamp_max); + } + }); } } @@ -262,36 +273,47 @@ static void map_range_float3(const VArray<float3> &attribute_input, switch (interpolation_type) { case NODE_MAP_RANGE_LINEAR: { - for (int i : span.index_range()) { - results[i].x = map_linear(span[i].x, min_from.x, max_from.x, min_to.x, max_to.x); - results[i].y = map_linear(span[i].y, min_from.y, max_from.y, min_to.y, max_to.y); - results[i].z = map_linear(span[i].z, min_from.z, max_from.z, min_to.z, max_to.z); - } + parallel_for(span.index_range(), 1024, [&](IndexRange range) { + for (const int i : range) { + results[i].x = map_linear(span[i].x, min_from.x, max_from.x, min_to.x, max_to.x); + results[i].y = map_linear(span[i].y, min_from.y, max_from.y, min_to.y, max_to.y); + results[i].z = map_linear(span[i].z, min_from.z, max_from.z, min_to.z, max_to.z); + } + }); break; } case NODE_MAP_RANGE_STEPPED: { const float3 steps = params.get_input<float3>("Steps_001"); - for (int i : span.index_range()) { - results[i].x = map_stepped(span[i].x, min_from.x, max_from.x, min_to.x, max_to.x, steps.x); - results[i].y = map_stepped(span[i].y, min_from.y, max_from.y, min_to.y, max_to.y, steps.y); - results[i].z = map_stepped(span[i].z, min_from.z, max_from.z, min_to.z, max_to.z, steps.z); - } + parallel_for(span.index_range(), 1024, [&](IndexRange range) { + for (const int i : range) { + results[i].x = map_stepped( + span[i].x, min_from.x, max_from.x, min_to.x, max_to.x, steps.x); + results[i].y = map_stepped( + span[i].y, min_from.y, max_from.y, min_to.y, max_to.y, steps.y); + results[i].z = map_stepped( + span[i].z, min_from.z, max_from.z, min_to.z, max_to.z, steps.z); + } + }); break; } case NODE_MAP_RANGE_SMOOTHSTEP: { - for (int i : span.index_range()) { - results[i].x = map_smoothstep(span[i].x, min_from.x, max_from.x, min_to.x, max_to.x); - results[i].y = map_smoothstep(span[i].y, min_from.y, max_from.y, min_to.y, max_to.y); - results[i].z = map_smoothstep(span[i].z, min_from.z, max_from.z, min_to.z, max_to.z); - } + parallel_for(span.index_range(), 1024, [&](IndexRange range) { + for (const int i : range) { + results[i].x = map_smoothstep(span[i].x, min_from.x, max_from.x, min_to.x, max_to.x); + results[i].y = map_smoothstep(span[i].y, min_from.y, max_from.y, min_to.y, max_to.y); + results[i].z = map_smoothstep(span[i].z, min_from.z, max_from.z, min_to.z, max_to.z); + } + }); break; } case NODE_MAP_RANGE_SMOOTHERSTEP: { - for (int i : span.index_range()) { - results[i].x = map_smootherstep(span[i].x, min_from.x, max_from.x, min_to.x, max_to.x); - results[i].y = map_smootherstep(span[i].y, min_from.y, max_from.y, min_to.y, max_to.y); - results[i].z = map_smootherstep(span[i].z, min_from.z, max_from.z, min_to.z, max_to.z); - } + parallel_for(span.index_range(), 1024, [&](IndexRange range) { + for (const int i : range) { + results[i].x = map_smootherstep(span[i].x, min_from.x, max_from.x, min_to.x, max_to.x); + results[i].y = map_smootherstep(span[i].y, min_from.y, max_from.y, min_to.y, max_to.y); + results[i].z = map_smootherstep(span[i].z, min_from.z, max_from.z, min_to.z, max_to.z); + } + }); break; } } @@ -388,6 +410,9 @@ static void geo_node_attribute_map_range_exec(GeoNodeExecParams params) if (geometry_set.has<PointCloudComponent>()) { map_range_attribute(geometry_set.get_component_for_write<PointCloudComponent>(), params); } + if (geometry_set.has<CurveComponent>()) { + map_range_attribute(geometry_set.get_component_for_write<CurveComponent>(), params); + } params.set_output("Geometry", geometry_set); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_math.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_math.cc index 00ae6b373b2..ce0ca31cc2b 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_math.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_math.cc @@ -14,6 +14,8 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +#include "BLI_task.hh" + #include "UI_interface.h" #include "UI_resources.h" @@ -157,9 +159,11 @@ static void do_math_operation(const VArray<float> &span_a, { bool success = try_dispatch_float_math_fl_fl_fl_to_fl( operation, [&](auto math_function, const FloatMathOperationInfo &UNUSED(info)) { - for (const int i : IndexRange(span_result.size())) { - span_result[i] = math_function(span_a[i], span_b[i], span_c[i]); - } + parallel_for(IndexRange(span_result.size()), 512, [&](IndexRange range) { + for (const int i : range) { + span_result[i] = math_function(span_a[i], span_b[i], span_c[i]); + } + }); }); BLI_assert(success); UNUSED_VARS_NDEBUG(success); @@ -172,9 +176,11 @@ static void do_math_operation(const VArray<float> &span_a, { bool success = try_dispatch_float_math_fl_fl_to_fl( operation, [&](auto math_function, const FloatMathOperationInfo &UNUSED(info)) { - for (const int i : IndexRange(span_result.size())) { - span_result[i] = math_function(span_a[i], span_b[i]); - } + parallel_for(IndexRange(span_result.size()), 1024, [&](IndexRange range) { + for (const int i : range) { + span_result[i] = math_function(span_a[i], span_b[i]); + } + }); }); BLI_assert(success); UNUSED_VARS_NDEBUG(success); @@ -186,9 +192,11 @@ static void do_math_operation(const VArray<float> &span_input, { bool success = try_dispatch_float_math_fl_to_fl( operation, [&](auto math_function, const FloatMathOperationInfo &UNUSED(info)) { - for (const int i : IndexRange(span_result.size())) { - span_result[i] = math_function(span_input[i]); - } + parallel_for(IndexRange(span_result.size()), 1024, [&](IndexRange range) { + for (const int i : range) { + span_result[i] = math_function(span_input[i]); + } + }); }); BLI_assert(success); UNUSED_VARS_NDEBUG(success); @@ -271,6 +279,9 @@ static void geo_node_attribute_math_exec(GeoNodeExecParams params) if (geometry_set.has<PointCloudComponent>()) { attribute_math_calc(geometry_set.get_component_for_write<PointCloudComponent>(), params); } + if (geometry_set.has<CurveComponent>()) { + attribute_math_calc(geometry_set.get_component_for_write<CurveComponent>(), params); + } params.set_output("Geometry", geometry_set); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_mix.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_mix.cc index 7129679117d..3e5326edbf6 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_mix.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_mix.cc @@ -14,6 +14,8 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +#include "BLI_task.hh" + #include "BKE_material.h" #include "DNA_material_types.h" @@ -64,14 +66,16 @@ static void do_mix_operation_float(const int blend_mode, VMutableArray<float> &results) { const int size = results.size(); - for (const int i : IndexRange(size)) { - const float factor = factors[i]; - float3 a{inputs_a[i]}; - const float3 b{inputs_b[i]}; - ramp_blend(blend_mode, a, factor, b); - const float result = a.x; - results.set(i, result); - } + parallel_for(IndexRange(size), 512, [&](IndexRange range) { + for (const int i : range) { + const float factor = factors[i]; + float3 a{inputs_a[i]}; + const float3 b{inputs_b[i]}; + ramp_blend(blend_mode, a, factor, b); + const float result = a.x; + results.set(i, result); + } + }); } static void do_mix_operation_float3(const int blend_mode, @@ -81,13 +85,15 @@ static void do_mix_operation_float3(const int blend_mode, VMutableArray<float3> &results) { const int size = results.size(); - for (const int i : IndexRange(size)) { - const float factor = factors[i]; - float3 a = inputs_a[i]; - const float3 b = inputs_b[i]; - ramp_blend(blend_mode, a, factor, b); - results.set(i, a); - } + parallel_for(IndexRange(size), 512, [&](IndexRange range) { + for (const int i : range) { + const float factor = factors[i]; + float3 a = inputs_a[i]; + const float3 b = inputs_b[i]; + ramp_blend(blend_mode, a, factor, b); + results.set(i, a); + } + }); } static void do_mix_operation_color4f(const int blend_mode, @@ -97,13 +103,15 @@ static void do_mix_operation_color4f(const int blend_mode, VMutableArray<Color4f> &results) { const int size = results.size(); - for (const int i : IndexRange(size)) { - const float factor = factors[i]; - Color4f a = inputs_a[i]; - const Color4f b = inputs_b[i]; - ramp_blend(blend_mode, a, factor, b); - results.set(i, a); - } + parallel_for(IndexRange(size), 512, [&](IndexRange range) { + for (const int i : range) { + const float factor = factors[i]; + Color4f a = inputs_a[i]; + const Color4f b = inputs_b[i]; + ramp_blend(blend_mode, a, factor, b); + results.set(i, a); + } + }); } static void do_mix_operation(const CustomDataType result_type, @@ -201,6 +209,9 @@ static void geo_node_attribute_mix_exec(GeoNodeExecParams params) if (geometry_set.has<PointCloudComponent>()) { attribute_mix_calc(geometry_set.get_component_for_write<PointCloudComponent>(), params); } + if (geometry_set.has<CurveComponent>()) { + attribute_mix_calc(geometry_set.get_component_for_write<CurveComponent>(), params); + } params.set_output("Geometry", geometry_set); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_proximity.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_proximity.cc index 8e9892f00b6..9c22b7fa87f 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_proximity.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_proximity.cc @@ -253,6 +253,10 @@ static void geo_node_attribute_proximity_exec(GeoNodeExecParams params) attribute_calc_proximity( geometry_set.get_component_for_write<PointCloudComponent>(), geometry_set_target, params); } + if (geometry_set.has<CurveComponent>()) { + attribute_calc_proximity( + geometry_set.get_component_for_write<CurveComponent>(), geometry_set_target, params); + } params.set_output("Geometry", geometry_set); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_randomize.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_randomize.cc index 523c66d7b40..12524d00584 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_randomize.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_randomize.cc @@ -130,34 +130,36 @@ static void randomize_attribute(MutableSpan<T> span, /* The operations could be templated too, but it doesn't make the code much shorter. */ switch (operation) { case GEO_NODE_ATTRIBUTE_RANDOMIZE_REPLACE_CREATE: - parallel_for(span.index_range(), 100, [&](IndexRange range) { - ProfileTask subtask; - BLI_profile_task_begin_range( - &subtask, &profile_task, range.start(), range.one_after_last()); + parallel_for(span.index_range(), 512, [&](IndexRange range) { for (const int i : range) { const T random_value = random_value_in_range<T>(ids[i], seed, min, max); span[i] = random_value; } - BLI_profile_task_end(&subtask); }); break; case GEO_NODE_ATTRIBUTE_RANDOMIZE_ADD: - for (const int i : span.index_range()) { - const T random_value = random_value_in_range<T>(ids[i], seed, min, max); - span[i] = span[i] + random_value; - } + parallel_for(span.index_range(), 512, [&](IndexRange range) { + for (const int i : range) { + const T random_value = random_value_in_range<T>(ids[i], seed, min, max); + span[i] = span[i] + random_value; + } + }); break; case GEO_NODE_ATTRIBUTE_RANDOMIZE_SUBTRACT: - for (const int i : span.index_range()) { - const T random_value = random_value_in_range<T>(ids[i], seed, min, max); - span[i] = span[i] - random_value; - } + parallel_for(span.index_range(), 512, [&](IndexRange range) { + for (const int i : range) { + const T random_value = random_value_in_range<T>(ids[i], seed, min, max); + span[i] = span[i] - random_value; + } + }); break; case GEO_NODE_ATTRIBUTE_RANDOMIZE_MULTIPLY: - for (const int i : span.index_range()) { - const T random_value = random_value_in_range<T>(ids[i], seed, min, max); - span[i] = span[i] * random_value; - } + parallel_for(span.index_range(), 512, [&](IndexRange range) { + for (const int i : range) { + const T random_value = random_value_in_range<T>(ids[i], seed, min, max); + span[i] = span[i] * random_value; + } + }); break; default: BLI_assert(false); @@ -174,10 +176,12 @@ static void randomize_attribute_bool(MutableSpan<bool> span, { BLI_assert(operation == GEO_NODE_ATTRIBUTE_RANDOMIZE_REPLACE_CREATE); UNUSED_VARS_NDEBUG(operation); - for (const int i : span.index_range()) { - const bool random_value = BLI_hash_int_2d_to_float(ids[i], seed) > 0.5f; - span[i] = random_value; - } + parallel_for(span.index_range(), 512, [&](IndexRange range) { + for (const int i : range) { + const bool random_value = BLI_hash_int_2d_to_float(ids[i], seed) > 0.5f; + span[i] = random_value; + } + }); } Array<uint32_t> get_geometry_element_ids_as_uints(const GeometryComponent &component, @@ -192,9 +196,11 @@ Array<uint32_t> get_geometry_element_ids_as_uints(const GeometryComponent &compo BLI_assert(hashes.size() == hash_attribute->size()); const CPPType &cpp_type = hash_attribute->type(); GVArray_GSpan items{*hash_attribute}; - for (const int i : hashes.index_range()) { - hashes[i] = cpp_type.hash(items[i]); - } + parallel_for(hashes.index_range(), 512, [&](IndexRange range) { + for (const int i : range) { + hashes[i] = cpp_type.hash(items[i]); + } + }); } else { /* If there is no "id" attribute for per-point variation, just create it here. */ @@ -315,6 +321,14 @@ static void geo_node_random_attribute_exec(GeoNodeExecParams params) operation, seed); } + if (geometry_set.has<CurveComponent>()) { + randomize_attribute_on_component(geometry_set.get_component_for_write<CurveComponent>(), + params, + attribute_name, + data_type, + operation, + seed); + } params.set_output("Geometry", geometry_set); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_remove.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_remove.cc index 837f0c3629a..e4f3230ebb9 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_remove.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_remove.cc @@ -70,6 +70,10 @@ static void geo_node_attribute_remove_exec(GeoNodeExecParams params) remove_attribute( geometry_set.get_component_for_write<PointCloudComponent>(), params, attribute_names); } + if (geometry_set.has<CurveComponent>()) { + remove_attribute( + geometry_set.get_component_for_write<CurveComponent>(), params, attribute_names); + } params.set_output("Geometry", geometry_set); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_sample_texture.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_sample_texture.cc index fa11ef936cd..59790e5e46c 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_sample_texture.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_sample_texture.cc @@ -15,6 +15,7 @@ */ #include "BLI_compiler_attrs.h" +#include "BLI_task.hh" #include "DNA_texture_types.h" @@ -95,14 +96,17 @@ static void execute_on_component(GeometryComponent &component, const GeoNodeExec mapping_name, result_domain, {0, 0, 0}); MutableSpan<Color4f> colors = attribute_out.as_span(); - for (const int i : IndexRange(mapping_attribute.size())) { - TexResult texture_result = {0}; - const float3 position = mapping_attribute[i]; - /* For legacy reasons we have to map [0, 1] to [-1, 1] to support uv mappings. */ - const float3 remapped_position = position * 2.0f - float3(1.0f); - BKE_texture_get_value(nullptr, texture, remapped_position, &texture_result, false); - colors[i] = {texture_result.tr, texture_result.tg, texture_result.tb, texture_result.ta}; - } + parallel_for(IndexRange(mapping_attribute.size()), 128, [&](IndexRange range) { + for (const int i : range) { + TexResult texture_result = {0}; + const float3 position = mapping_attribute[i]; + /* For legacy reasons we have to map [0, 1] to [-1, 1] to support uv mappings. */ + const float3 remapped_position = position * 2.0f - float3(1.0f); + BKE_texture_get_value(nullptr, texture, remapped_position, &texture_result, false); + colors[i] = {texture_result.tr, texture_result.tg, texture_result.tb, texture_result.ta}; + } + }); + attribute_out.save(); } @@ -118,6 +122,9 @@ static void geo_node_attribute_sample_texture_exec(GeoNodeExecParams params) if (geometry_set.has<PointCloudComponent>()) { execute_on_component(geometry_set.get_component_for_write<PointCloudComponent>(), params); } + if (geometry_set.has<CurveComponent>()) { + execute_on_component(geometry_set.get_component_for_write<CurveComponent>(), params); + } params.set_output("Geometry", geometry_set); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_separate_xyz.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_separate_xyz.cc index bbc6cb71032..137a72bb707 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_separate_xyz.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_separate_xyz.cc @@ -148,6 +148,9 @@ static void geo_node_attribute_separate_xyz_exec(GeoNodeExecParams params) if (geometry_set.has<PointCloudComponent>()) { separate_attribute(geometry_set.get_component_for_write<PointCloudComponent>(), params); } + if (geometry_set.has<CurveComponent>()) { + separate_attribute(geometry_set.get_component_for_write<CurveComponent>(), params); + } params.set_output("Geometry", geometry_set); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_vector_math.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_vector_math.cc index 86fbc9dc6d0..8877af445f9 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_vector_math.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_vector_math.cc @@ -15,6 +15,7 @@ */ #include "BLI_math_base_safe.h" +#include "BLI_task.hh" #include "UI_interface.h" #include "UI_resources.h" @@ -181,12 +182,14 @@ static void do_math_operation_fl3_fl3_to_fl3(const VArray<float3> &input_a, bool success = try_dispatch_float_math_fl3_fl3_to_fl3( operation, [&](auto math_function, const FloatMathOperationInfo &UNUSED(info)) { - for (const int i : IndexRange(size)) { - const float3 a = span_a[i]; - const float3 b = span_b[i]; - const float3 out = math_function(a, b); - span_result[i] = out; - } + parallel_for(IndexRange(size), 512, [&](IndexRange range) { + for (const int i : range) { + const float3 a = span_a[i]; + const float3 b = span_b[i]; + const float3 out = math_function(a, b); + span_result[i] = out; + } + }); }); span_result.save(); @@ -211,13 +214,15 @@ static void do_math_operation_fl3_fl3_fl3_to_fl3(const VArray<float3> &input_a, bool success = try_dispatch_float_math_fl3_fl3_fl3_to_fl3( operation, [&](auto math_function, const FloatMathOperationInfo &UNUSED(info)) { - for (const int i : IndexRange(size)) { - const float3 a = span_a[i]; - const float3 b = span_b[i]; - const float3 c = span_c[i]; - const float3 out = math_function(a, b, c); - span_result[i] = out; - } + parallel_for(IndexRange(size), 512, [&](IndexRange range) { + for (const int i : range) { + const float3 a = span_a[i]; + const float3 b = span_b[i]; + const float3 c = span_c[i]; + const float3 out = math_function(a, b, c); + span_result[i] = out; + } + }); }); span_result.save(); @@ -242,13 +247,15 @@ static void do_math_operation_fl3_fl3_fl_to_fl3(const VArray<float3> &input_a, bool success = try_dispatch_float_math_fl3_fl3_fl_to_fl3( operation, [&](auto math_function, const FloatMathOperationInfo &UNUSED(info)) { - for (const int i : IndexRange(size)) { - const float3 a = span_a[i]; - const float3 b = span_b[i]; - const float c = span_c[i]; - const float3 out = math_function(a, b, c); - span_result[i] = out; - } + parallel_for(IndexRange(size), 512, [&](IndexRange range) { + for (const int i : range) { + const float3 a = span_a[i]; + const float3 b = span_b[i]; + const float c = span_c[i]; + const float3 out = math_function(a, b, c); + span_result[i] = out; + } + }); }); span_result.save(); @@ -271,12 +278,14 @@ static void do_math_operation_fl3_fl3_to_fl(const VArray<float3> &input_a, bool success = try_dispatch_float_math_fl3_fl3_to_fl( operation, [&](auto math_function, const FloatMathOperationInfo &UNUSED(info)) { - for (const int i : IndexRange(size)) { - const float3 a = span_a[i]; - const float3 b = span_b[i]; - const float out = math_function(a, b); - span_result[i] = out; - } + parallel_for(IndexRange(size), 512, [&](IndexRange range) { + for (const int i : range) { + const float3 a = span_a[i]; + const float3 b = span_b[i]; + const float out = math_function(a, b); + span_result[i] = out; + } + }); }); span_result.save(); @@ -299,12 +308,14 @@ static void do_math_operation_fl3_fl_to_fl3(const VArray<float3> &input_a, bool success = try_dispatch_float_math_fl3_fl_to_fl3( operation, [&](auto math_function, const FloatMathOperationInfo &UNUSED(info)) { - for (const int i : IndexRange(size)) { - const float3 a = span_a[i]; - const float b = span_b[i]; - const float3 out = math_function(a, b); - span_result[i] = out; - } + parallel_for(IndexRange(size), 512, [&](IndexRange range) { + for (const int i : range) { + const float3 a = span_a[i]; + const float b = span_b[i]; + const float3 out = math_function(a, b); + span_result[i] = out; + } + }); }); span_result.save(); @@ -325,11 +336,13 @@ static void do_math_operation_fl3_to_fl3(const VArray<float3> &input_a, bool success = try_dispatch_float_math_fl3_to_fl3( operation, [&](auto math_function, const FloatMathOperationInfo &UNUSED(info)) { - for (const int i : IndexRange(size)) { - const float3 in = span_a[i]; - const float3 out = math_function(in); - span_result[i] = out; - } + parallel_for(IndexRange(size), 512, [&](IndexRange range) { + for (const int i : range) { + const float3 in = span_a[i]; + const float3 out = math_function(in); + span_result[i] = out; + } + }); }); span_result.save(); @@ -350,11 +363,13 @@ static void do_math_operation_fl3_to_fl(const VArray<float3> &input_a, bool success = try_dispatch_float_math_fl3_to_fl( operation, [&](auto math_function, const FloatMathOperationInfo &UNUSED(info)) { - for (const int i : IndexRange(size)) { - const float3 in = span_a[i]; - const float out = math_function(in); - span_result[i] = out; - } + parallel_for(IndexRange(size), 512, [&](IndexRange range) { + for (const int i : range) { + const float3 in = span_a[i]; + const float out = math_function(in); + span_result[i] = out; + } + }); }); span_result.save(); @@ -510,6 +525,9 @@ static void geo_node_attribute_vector_math_exec(GeoNodeExecParams params) attribute_vector_math_calc(geometry_set.get_component_for_write<PointCloudComponent>(), params); } + if (geometry_set.has<CurveComponent>()) { + attribute_vector_math_calc(geometry_set.get_component_for_write<CurveComponent>(), params); + } params.set_output("Geometry", geometry_set); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_collection_info.cc b/source/blender/nodes/geometry/nodes/node_geo_collection_info.cc index 0ad495aa4db..5800d46b70d 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_collection_info.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_collection_info.cc @@ -58,10 +58,6 @@ static void geo_node_collection_info_exec(GeoNodeExecParams params) const bool transform_space_relative = (node_storage->transform_space == GEO_NODE_TRANSFORM_SPACE_RELATIVE); - InstancedData instance; - instance.type = INSTANCE_DATA_TYPE_COLLECTION; - instance.data.collection = collection; - InstancesComponent &instances = geometry_set_out.get_component_for_write<InstancesComponent>(); float transform_mat[4][4]; @@ -73,7 +69,9 @@ static void geo_node_collection_info_exec(GeoNodeExecParams params) mul_m4_m4_pre(transform_mat, self_object->imat); } - instances.add_instance(instance, transform_mat, -1); + + const int handle = instances.add_reference(*collection); + instances.add_instance(handle, transform_mat, -1); params.set_output("Geometry", geometry_set_out); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_to_mesh.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_to_mesh.cc new file mode 100644 index 00000000000..071504ad8df --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_to_mesh.cc @@ -0,0 +1,312 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "BLI_array.hh" +#include "BLI_float4x4.hh" +#include "BLI_timeit.hh" + +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" + +#include "BKE_mesh.h" +#include "BKE_spline.hh" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "node_geometry_util.hh" + +static bNodeSocketTemplate geo_node_curve_to_mesh_in[] = { + {SOCK_GEOMETRY, N_("Curve")}, + {SOCK_GEOMETRY, N_("Profile Curve")}, + {-1, ""}, +}; + +static bNodeSocketTemplate geo_node_curve_to_mesh_out[] = { + {SOCK_GEOMETRY, N_("Mesh")}, + {-1, ""}, +}; + +namespace blender::nodes { + +static void vert_extrude_to_mesh_data(const Spline &spline, + const float3 profile_vert, + MutableSpan<MVert> r_verts, + MutableSpan<MEdge> r_edges, + int &vert_offset, + int &edge_offset) +{ + Span<float3> positions = spline.evaluated_positions(); + + for (const int i : IndexRange(positions.size() - 1)) { + MEdge &edge = r_edges[edge_offset++]; + edge.v1 = vert_offset + i; + edge.v2 = vert_offset + i + 1; + edge.flag = ME_LOOSEEDGE; + } + + if (spline.is_cyclic()) { + MEdge &edge = r_edges[edge_offset++]; + edge.v1 = vert_offset; + edge.v2 = vert_offset + positions.size() - 1; + edge.flag = ME_LOOSEEDGE; + } + + for (const int i : positions.index_range()) { + MVert &vert = r_verts[vert_offset++]; + copy_v3_v3(vert.co, positions[i] + profile_vert); + } +} + +static void mark_edges_sharp(MutableSpan<MEdge> edges) +{ + for (MEdge &edge : edges) { + edge.flag |= ME_SHARP; + } +} + +static void spline_extrude_to_mesh_data(const Spline &spline, + const Spline &profile_spline, + MutableSpan<MVert> r_verts, + MutableSpan<MEdge> r_edges, + MutableSpan<MLoop> r_loops, + MutableSpan<MPoly> r_polys, + int &vert_offset, + int &edge_offset, + int &loop_offset, + int &poly_offset) +{ + const int spline_vert_len = spline.evaluated_points_size(); + const int spline_edge_len = spline.evaluated_edges_size(); + const int profile_vert_len = profile_spline.evaluated_points_size(); + const int profile_edge_len = profile_spline.evaluated_edges_size(); + if (spline_vert_len == 0) { + return; + } + + if (profile_vert_len == 1) { + vert_extrude_to_mesh_data(spline, + profile_spline.evaluated_positions()[0], + r_verts, + r_edges, + vert_offset, + edge_offset); + return; + } + + /* Add the edges running along the length of the curve, starting at each profile vertex. */ + const int spline_edges_start = edge_offset; + for (const int i_profile : IndexRange(profile_vert_len)) { + for (const int i_ring : IndexRange(spline_edge_len)) { + const int i_next_ring = (i_ring == spline_vert_len - 1) ? 0 : i_ring + 1; + + const int ring_vert_offset = vert_offset + profile_vert_len * i_ring; + const int next_ring_vert_offset = vert_offset + profile_vert_len * i_next_ring; + + MEdge &edge = r_edges[edge_offset++]; + edge.v1 = ring_vert_offset + i_profile; + edge.v2 = next_ring_vert_offset + i_profile; + edge.flag = ME_EDGEDRAW | ME_EDGERENDER; + } + } + + /* Add the edges running along each profile ring. */ + const int profile_edges_start = edge_offset; + for (const int i_ring : IndexRange(spline_vert_len)) { + const int ring_vert_offset = vert_offset + profile_vert_len * i_ring; + + for (const int i_profile : IndexRange(profile_edge_len)) { + const int i_next_profile = (i_profile == profile_vert_len - 1) ? 0 : i_profile + 1; + + MEdge &edge = r_edges[edge_offset++]; + edge.v1 = ring_vert_offset + i_profile; + edge.v2 = ring_vert_offset + i_next_profile; + edge.flag = ME_EDGEDRAW | ME_EDGERENDER; + } + } + + /* Calculate poly and corner indices. */ + for (const int i_ring : IndexRange(spline_edge_len)) { + const int i_next_ring = (i_ring == spline_vert_len - 1) ? 0 : i_ring + 1; + + const int ring_vert_offset = vert_offset + profile_vert_len * i_ring; + const int next_ring_vert_offset = vert_offset + profile_vert_len * i_next_ring; + + const int ring_edge_start = profile_edges_start + profile_edge_len * i_ring; + const int next_ring_edge_offset = profile_edges_start + profile_edge_len * i_next_ring; + + for (const int i_profile : IndexRange(profile_edge_len)) { + const int i_next_profile = (i_profile == profile_vert_len - 1) ? 0 : i_profile + 1; + + const int spline_edge_start = spline_edges_start + spline_edge_len * i_profile; + const int next_spline_edge_start = spline_edges_start + spline_edge_len * i_next_profile; + + MPoly &poly = r_polys[poly_offset++]; + poly.loopstart = loop_offset; + poly.totloop = 4; + poly.flag = ME_SMOOTH; + + MLoop &loop_a = r_loops[loop_offset++]; + loop_a.v = ring_vert_offset + i_profile; + loop_a.e = ring_edge_start + i_profile; + MLoop &loop_b = r_loops[loop_offset++]; + loop_b.v = ring_vert_offset + i_next_profile; + loop_b.e = next_spline_edge_start + i_ring; + MLoop &loop_c = r_loops[loop_offset++]; + loop_c.v = next_ring_vert_offset + i_next_profile; + loop_c.e = next_ring_edge_offset + i_profile; + MLoop &loop_d = r_loops[loop_offset++]; + loop_d.v = next_ring_vert_offset + i_profile; + loop_d.e = spline_edge_start + i_ring; + } + } + + /* Calculate the positions of each profile ring profile along the spline. */ + Span<float3> positions = spline.evaluated_positions(); + Span<float3> tangents = spline.evaluated_tangents(); + Span<float3> normals = spline.evaluated_normals(); + Span<float3> profile_positions = profile_spline.evaluated_positions(); + + GVArray_Typed<float> radii{ + spline.interpolate_to_evaluated_points(blender::fn::GVArray_For_Span(spline.radii()))}; + for (const int i_ring : IndexRange(spline_vert_len)) { + float4x4 point_matrix = float4x4::from_normalized_axis_data( + positions[i_ring], normals[i_ring], tangents[i_ring]); + + point_matrix.apply_scale(radii[i_ring]); + + for (const int i_profile : IndexRange(profile_vert_len)) { + MVert &vert = r_verts[vert_offset++]; + copy_v3_v3(vert.co, point_matrix * profile_positions[i_profile]); + } + } + + /* Mark edge loops from sharp vector control points sharp. */ + if (profile_spline.type() == Spline::Type::Bezier) { + const BezierSpline &bezier_spline = static_cast<const BezierSpline &>(profile_spline); + Span<int> control_point_offsets = bezier_spline.control_point_offsets(); + for (const int i : control_point_offsets.index_range()) { + if (bezier_spline.point_is_sharp(i)) { + mark_edges_sharp(r_edges.slice( + spline_edges_start + spline_edge_len * control_point_offsets[i], spline_edge_len)); + } + } + } +} + +static Mesh *curve_to_mesh_calculate(const CurveEval &curve, const CurveEval &profile_curve) +{ + int profile_vert_total = 0; + int profile_edge_total = 0; + for (const SplinePtr &profile_spline : profile_curve.splines) { + profile_vert_total += profile_spline->evaluated_points_size(); + profile_edge_total += profile_spline->evaluated_edges_size(); + } + + int vert_total = 0; + int edge_total = 0; + int poly_total = 0; + for (const SplinePtr &spline : curve.splines) { + const int spline_vert_len = spline->evaluated_points_size(); + const int spline_edge_len = spline->evaluated_edges_size(); + vert_total += spline_vert_len * profile_vert_total; + poly_total += spline_edge_len * profile_edge_total; + + /* Add the ring edges, with one ring for every curve vertex, and the edge loops + * that run along the length of the curve, starting on the first profile. */ + edge_total += profile_edge_total * spline_vert_len + profile_vert_total * spline_edge_len; + } + const int corner_total = poly_total * 4; + + if (vert_total == 0) { + return nullptr; + } + + Mesh *mesh = BKE_mesh_new_nomain(vert_total, edge_total, 0, corner_total, poly_total); + MutableSpan<MVert> verts{mesh->mvert, mesh->totvert}; + MutableSpan<MEdge> edges{mesh->medge, mesh->totedge}; + MutableSpan<MLoop> loops{mesh->mloop, mesh->totloop}; + MutableSpan<MPoly> polys{mesh->mpoly, mesh->totpoly}; + mesh->flag |= ME_AUTOSMOOTH; + mesh->smoothresh = DEG2RADF(180.0f); + + int vert_offset = 0; + int edge_offset = 0; + int loop_offset = 0; + int poly_offset = 0; + for (const SplinePtr &spline : curve.splines) { + for (const SplinePtr &profile_spline : profile_curve.splines) { + spline_extrude_to_mesh_data(*spline, + *profile_spline, + verts, + edges, + loops, + polys, + vert_offset, + edge_offset, + loop_offset, + poly_offset); + } + } + + BKE_mesh_calc_normals(mesh); + + return mesh; +} + +static CurveEval get_curve_single_vert() +{ + CurveEval curve; + std::unique_ptr<PolySpline> spline = std::make_unique<PolySpline>(); + spline->add_point(float3(0), 0, 0.0f); + curve.splines.append(std::move(spline)); + + return curve; +} + +static void geo_node_curve_to_mesh_exec(GeoNodeExecParams params) +{ + GeometrySet curve_set = params.extract_input<GeometrySet>("Curve"); + GeometrySet profile_set = params.extract_input<GeometrySet>("Profile Curve"); + + curve_set = bke::geometry_set_realize_instances(curve_set); + profile_set = bke::geometry_set_realize_instances(profile_set); + + if (!curve_set.has_curve()) { + params.set_output("Mesh", GeometrySet()); + return; + } + + const CurveEval *profile_curve = profile_set.get_curve_for_read(); + + static const CurveEval vert_curve = get_curve_single_vert(); + + Mesh *mesh = curve_to_mesh_calculate(*curve_set.get_curve_for_read(), + (profile_curve == nullptr) ? vert_curve : *profile_curve); + params.set_output("Mesh", GeometrySet::create_with_mesh(mesh)); +} + +} // namespace blender::nodes + +void register_node_type_geo_curve_to_mesh() +{ + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_CURVE_TO_MESH, "Curve to Mesh", NODE_CLASS_GEOMETRY, 0); + node_type_socket_templates(&ntype, geo_node_curve_to_mesh_in, geo_node_curve_to_mesh_out); + ntype.geometry_node_execute = blender::nodes::geo_node_curve_to_mesh_exec; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc b/source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc index 6696e2ccee8..68bb3614751 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc @@ -17,6 +17,7 @@ #include "BKE_mesh.h" #include "BKE_mesh_runtime.h" #include "BKE_pointcloud.h" +#include "BKE_spline.hh" #include "DNA_mesh_types.h" #include "DNA_meshdata_types.h" @@ -242,13 +243,30 @@ static void join_components(Span<const PointCloudComponent *> src_components, Ge static void join_components(Span<const InstancesComponent *> src_components, GeometrySet &result) { InstancesComponent &dst_component = result.get_component_for_write<InstancesComponent>(); - for (const InstancesComponent *component : src_components) { - const int size = component->instances_amount(); - Span<InstancedData> instanced_data = component->instanced_data(); - Span<float4x4> transforms = component->transforms(); - Span<int> ids = component->ids(); - for (const int i : IndexRange(size)) { - dst_component.add_instance(instanced_data[i], transforms[i], ids[i]); + + int tot_instances = 0; + for (const InstancesComponent *src_component : src_components) { + tot_instances += src_component->instances_amount(); + } + dst_component.reserve(tot_instances); + + for (const InstancesComponent *src_component : src_components) { + Span<InstanceReference> src_references = src_component->references(); + Array<int> handle_map(src_references.size()); + for (const int src_handle : src_references.index_range()) { + handle_map[src_handle] = dst_component.add_reference(src_references[src_handle]); + } + + Span<float4x4> src_transforms = src_component->instance_transforms(); + Span<int> src_ids = src_component->instance_ids(); + Span<int> src_reference_handles = src_component->instance_reference_handles(); + + for (const int i : src_transforms.index_range()) { + const int src_handle = src_reference_handles[i]; + const int dst_handle = handle_map[src_handle]; + const float4x4 &transform = src_transforms[i]; + const int id = src_ids[i]; + dst_component.add_instance(dst_handle, transform, id); } } } @@ -261,6 +279,40 @@ static void join_components(Span<const VolumeComponent *> src_components, Geomet UNUSED_VARS(src_components, dst_component); } +static void join_curve_components(MutableSpan<GeometrySet> src_geometry_sets, GeometrySet &result) +{ + Vector<CurveComponent *> src_components; + for (GeometrySet &geometry_set : src_geometry_sets) { + if (geometry_set.has_curve()) { + /* Retrieving with write access seems counterintuitive, but it can allow avoiding a copy + * in the case where the input spline has no other users, because the splines can be + * moved from the source curve rather than copied from a read-only source. Retrieving + * the curve for write will make a copy only when it has a user elsewhere. */ + CurveComponent &component = geometry_set.get_component_for_write<CurveComponent>(); + src_components.append(&component); + } + } + + if (src_components.size() == 0) { + return; + } + if (src_components.size() == 1) { + result.add(*src_components[0]); + return; + } + + CurveComponent &dst_component = result.get_component_for_write<CurveComponent>(); + CurveEval *dst_curve = new CurveEval(); + for (CurveComponent *component : src_components) { + CurveEval *src_curve = component->get_for_write(); + for (SplinePtr &spline : src_curve->splines) { + dst_curve->splines.append(std::move(spline)); + } + } + + dst_component.replace(dst_curve); +} + template<typename Component> static void join_component_type(Span<GeometrySet> src_geometry_sets, GeometrySet &result) { @@ -291,6 +343,7 @@ static void geo_node_join_geometry_exec(GeoNodeExecParams params) join_component_type<PointCloudComponent>(geometry_sets, geometry_set_result); join_component_type<InstancesComponent>(geometry_sets, geometry_set_result); join_component_type<VolumeComponent>(geometry_sets, geometry_set_result); + join_curve_components(geometry_sets, geometry_set_result); params.set_output("Geometry", std::move(geometry_set_result)); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_object_info.cc b/source/blender/nodes/geometry/nodes/node_geo_object_info.cc index bd42b4c11d6..de099b8062f 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_object_info.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_object_info.cc @@ -73,14 +73,15 @@ static void geo_node_object_info_exec(GeoNodeExecParams params) if (object != self_object) { InstancesComponent &instances = geometry_set.get_component_for_write<InstancesComponent>(); + const int handle = instances.add_reference(*object); if (transform_space_relative) { - instances.add_instance(object, transform); + instances.add_instance(handle, transform); } else { float unit_transform[4][4]; unit_m4(unit_transform); - instances.add_instance(object, unit_transform); + instances.add_instance(handle, unit_transform); } } } diff --git a/source/blender/nodes/geometry/nodes/node_geo_point_instance.cc b/source/blender/nodes/geometry/nodes/node_geo_point_instance.cc index 65b7d068003..7929a5a1546 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_point_instance.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_point_instance.cc @@ -65,8 +65,8 @@ static void geo_node_point_instance_update(bNodeTree *UNUSED(tree), bNode *node) seed_socket, type == GEO_NODE_POINT_INSTANCE_TYPE_COLLECTION && !use_whole_collection); } -static void get_instanced_data__object(const GeoNodeExecParams ¶ms, - MutableSpan<std::optional<InstancedData>> r_instances_data) +static void get_instance_references__object(const GeoNodeExecParams ¶ms, + MutableSpan<InstanceReference> r_references) { bke::PersistentObjectHandle object_handle = params.get_input<bke::PersistentObjectHandle>( "Object"); @@ -75,17 +75,13 @@ static void get_instanced_data__object(const GeoNodeExecParams ¶ms, object = nullptr; } if (object != nullptr) { - InstancedData instance; - instance.type = INSTANCE_DATA_TYPE_OBJECT; - instance.data.object = object; - r_instances_data.fill(instance); + r_references.fill(*object); } } -static void get_instanced_data__collection( - const GeoNodeExecParams ¶ms, - const GeometryComponent &component, - MutableSpan<std::optional<InstancedData>> r_instances_data) +static void get_instance_references__collection(const GeoNodeExecParams ¶ms, + const GeometryComponent &component, + MutableSpan<InstanceReference> r_references) { const bNode &node = params.node(); NodeGeometryPointInstance *node_storage = (NodeGeometryPointInstance *)node.storage; @@ -106,62 +102,51 @@ static void get_instanced_data__collection( const bool use_whole_collection = (node_storage->flag & GEO_NODE_POINT_INSTANCE_WHOLE_COLLECTION) != 0; if (use_whole_collection) { - InstancedData instance; - instance.type = INSTANCE_DATA_TYPE_COLLECTION; - instance.data.collection = collection; - r_instances_data.fill(instance); + r_references.fill(*collection); } else { - Vector<InstancedData> possible_instances; + Vector<InstanceReference> possible_references; /* Direct child objects are instanced as objects. */ LISTBASE_FOREACH (CollectionObject *, cob, &collection->gobject) { - Object *object = cob->ob; - InstancedData instance; - instance.type = INSTANCE_DATA_TYPE_OBJECT; - instance.data.object = object; - possible_instances.append(instance); + possible_references.append(*cob->ob); } /* Direct child collections are instanced as collections. */ LISTBASE_FOREACH (CollectionChild *, child, &collection->children) { - Collection *child_collection = child->collection; - InstancedData instance; - instance.type = INSTANCE_DATA_TYPE_COLLECTION; - instance.data.collection = child_collection; - possible_instances.append(instance); + possible_references.append(*child->collection); } - if (!possible_instances.is_empty()) { + if (!possible_references.is_empty()) { const int seed = params.get_input<int>("Seed"); Array<uint32_t> ids = get_geometry_element_ids_as_uints(component, ATTR_DOMAIN_POINT); - for (const int i : r_instances_data.index_range()) { - const int index = BLI_hash_int_2d(ids[i], seed) % possible_instances.size(); - r_instances_data[i] = possible_instances[index]; + for (const int i : r_references.index_range()) { + const int index = BLI_hash_int_2d(ids[i], seed) % possible_references.size(); + r_references[i] = possible_references[index]; } } } } -static Array<std::optional<InstancedData>> get_instanced_data(const GeoNodeExecParams ¶ms, - const GeometryComponent &component, - const int amount) +static Array<InstanceReference> get_instance_references(const GeoNodeExecParams ¶ms, + const GeometryComponent &component, + const int amount) { const bNode &node = params.node(); NodeGeometryPointInstance *node_storage = (NodeGeometryPointInstance *)node.storage; const GeometryNodePointInstanceType type = (GeometryNodePointInstanceType) node_storage->instance_type; - Array<std::optional<InstancedData>> instances_data(amount); + Array<InstanceReference> references(amount); switch (type) { case GEO_NODE_POINT_INSTANCE_TYPE_OBJECT: { - get_instanced_data__object(params, instances_data); + get_instance_references__object(params, references); break; } case GEO_NODE_POINT_INSTANCE_TYPE_COLLECTION: { - get_instanced_data__collection(params, component, instances_data); + get_instance_references__collection(params, component, references); break; } } - return instances_data; + return references; } static void add_instances_from_geometry_component(InstancesComponent &instances, @@ -171,8 +156,7 @@ static void add_instances_from_geometry_component(InstancesComponent &instances, const AttributeDomain domain = ATTR_DOMAIN_POINT; const int domain_size = src_geometry.attribute_domain_size(domain); - Array<std::optional<InstancedData>> instances_data = get_instanced_data( - params, src_geometry, domain_size); + Array<InstanceReference> references = get_instance_references(params, src_geometry, domain_size); GVArray_Typed<float3> positions = src_geometry.attribute_get_for_read<float3>( "position", domain, {0, 0, 0}); @@ -183,9 +167,11 @@ static void add_instances_from_geometry_component(InstancesComponent &instances, GVArray_Typed<int> ids = src_geometry.attribute_get_for_read<int>("id", domain, -1); for (const int i : IndexRange(domain_size)) { - if (instances_data[i].has_value()) { + const InstanceReference &reference = references[i]; + if (reference.type() != InstanceReference::Type::None) { const float4x4 matrix = float4x4::from_loc_eul_scale(positions[i], rotations[i], scales[i]); - instances.add_instance(*instances_data[i], matrix, ids[i]); + const int handle = instances.add_reference(reference); + instances.add_instance(handle, matrix, ids[i]); } } } @@ -209,6 +195,11 @@ static void geo_node_point_instance_exec(GeoNodeExecParams params) instances, *geometry_set.get_component_for_read<PointCloudComponent>(), params); } + if (geometry_set.has<CurveComponent>()) { + add_instances_from_geometry_component( + instances, *geometry_set.get_component_for_read<CurveComponent>(), params); + } + params.set_output("Geometry", std::move(geometry_set_out)); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_points_to_volume.cc b/source/blender/nodes/geometry/nodes/node_geo_points_to_volume.cc index d73c83b4caf..e85f6aaee54 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_points_to_volume.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_points_to_volume.cc @@ -186,6 +186,10 @@ static void initialize_volume_component_from_points(const GeometrySet &geometry_ gather_point_data_from_component( params, *geometry_set_in.get_component_for_read<PointCloudComponent>(), positions, radii); } + if (geometry_set_in.has<CurveComponent>()) { + gather_point_data_from_component( + params, *geometry_set_in.get_component_for_read<CurveComponent>(), positions, radii); + } const float max_radius = *std::max_element(radii.begin(), radii.end()); const float voxel_size = compute_voxel_size(params, positions, max_radius); diff --git a/source/blender/nodes/geometry/nodes/node_geo_transform.cc b/source/blender/nodes/geometry/nodes/node_geo_transform.cc index d54982d16c2..ce52dc90668 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_transform.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_transform.cc @@ -24,6 +24,7 @@ #include "DNA_volume_types.h" #include "BKE_mesh.h" +#include "BKE_spline.hh" #include "BKE_volume.h" #include "DEG_depsgraph_query.h" @@ -100,7 +101,7 @@ static void transform_instances(InstancesComponent &instances, const float3 rotation, const float3 scale) { - MutableSpan<float4x4> transforms = instances.transforms(); + MutableSpan<float4x4> transforms = instances.instance_transforms(); /* Use only translation if rotation and scale don't apply. */ if (use_translate(rotation, scale)) { @@ -152,6 +153,21 @@ static void transform_volume(Volume *volume, #endif } +static void transform_curve(CurveEval &curve, + const float3 translation, + const float3 rotation, + const float3 scale) +{ + + if (use_translate(rotation, scale)) { + curve.translate(translation); + } + else { + const float4x4 matrix = float4x4::from_loc_eul_scale(translation, rotation, scale); + curve.transform(matrix); + } +} + static void geo_node_transform_exec(GeoNodeExecParams params) { GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); @@ -179,6 +195,11 @@ static void geo_node_transform_exec(GeoNodeExecParams params) transform_volume(volume, translation, rotation, scale, params); } + if (geometry_set.has_curve()) { + CurveEval *curve = geometry_set.get_curve_for_write(); + transform_curve(*curve, translation, rotation, scale); + } + params.set_output("Geometry", std::move(geometry_set)); } } // namespace blender::nodes diff --git a/source/blender/render/intern/zbuf.c b/source/blender/render/intern/zbuf.c index 33af3bbaf29..242c8a199fb 100644 --- a/source/blender/render/intern/zbuf.c +++ b/source/blender/render/intern/zbuf.c @@ -175,7 +175,7 @@ static void zbuf_add_to_span(ZSpan *zspan, const float v1[2], const float v2[2]) /* Functions */ /*-----------------------------------------------------------*/ -/* Scanconvert for strand triangles, calls func for each x, y coordinate +/* Scan-convert for strand triangles, calls function for each x, y coordinate * and gives UV barycentrics and z. */ void zspan_scanconvert(ZSpan *zspan, diff --git a/source/blender/sequencer/SEQ_add.h b/source/blender/sequencer/SEQ_add.h index 9cb52145c04..2941eb6f4c0 100644 --- a/source/blender/sequencer/SEQ_add.h +++ b/source/blender/sequencer/SEQ_add.h @@ -36,6 +36,7 @@ typedef enum eSeqLoadFlags { SEQ_LOAD_SOUND_CACHE = (1 << 1), SEQ_LOAD_SOUND_MONO = (1 << 2), SEQ_LOAD_MOVIE_SYNC_FPS = (1 << 3), + SEQ_LOAD_SET_VIEW_TRANSFORM = (1 << 4), } eSeqLoadFlags; /* Api for adding new sequence strips. */ diff --git a/source/blender/sequencer/SEQ_iterator.h b/source/blender/sequencer/SEQ_iterator.h index 669c55e1f4c..b34f0558c56 100644 --- a/source/blender/sequencer/SEQ_iterator.h +++ b/source/blender/sequencer/SEQ_iterator.h @@ -54,12 +54,6 @@ typedef struct SeqIterator { void SEQ_iterator_begin(struct Editing *ed, SeqIterator *iter, const bool use_current_sequences); void SEQ_iterator_next(SeqIterator *iter); void SEQ_iterator_end(SeqIterator *iter); -int SEQ_iterator_seqbase_recursive_apply(struct ListBase *seqbase, - int (*apply_fn)(struct Sequence *seq, void *), - void *arg); -int SEQ_iterator_recursive_apply(struct Sequence *seq, - int (*apply_fn)(struct Sequence *, void *), - void *arg); #ifdef __cplusplus } diff --git a/source/blender/sequencer/SEQ_utils.h b/source/blender/sequencer/SEQ_utils.h index 45f53a64688..52fac5d7d0e 100644 --- a/source/blender/sequencer/SEQ_utils.h +++ b/source/blender/sequencer/SEQ_utils.h @@ -54,7 +54,12 @@ void SEQ_set_scale_to_fit(const struct Sequence *seq, const int preview_width, const int preview_height, const eSeqImageFitMethod fit_method); - +int SEQ_seqbase_recursive_apply(struct ListBase *seqbase, + int (*apply_fn)(struct Sequence *seq, void *), + void *arg); +int SEQ_recursive_apply(struct Sequence *seq, + int (*apply_fn)(struct Sequence *, void *), + void *arg); #ifdef __cplusplus } #endif diff --git a/source/blender/sequencer/intern/iterator.c b/source/blender/sequencer/intern/iterator.c index f99667dea04..356f5db45e8 100644 --- a/source/blender/sequencer/intern/iterator.c +++ b/source/blender/sequencer/intern/iterator.c @@ -138,31 +138,3 @@ void SEQ_iterator_end(SeqIterator *iter) iter->valid = 0; } - -int SEQ_iterator_seqbase_recursive_apply(ListBase *seqbase, - int (*apply_fn)(Sequence *seq, void *), - void *arg) -{ - Sequence *iseq; - for (iseq = seqbase->first; iseq; iseq = iseq->next) { - if (SEQ_iterator_recursive_apply(iseq, apply_fn, arg) == -1) { - return -1; /* bail out */ - } - } - return 1; -} - -int SEQ_iterator_recursive_apply(Sequence *seq, int (*apply_fn)(Sequence *, void *), void *arg) -{ - int ret = apply_fn(seq, arg); - - if (ret == -1) { - return -1; /* bail out */ - } - - if (ret && seq->seqbase.first) { - ret = SEQ_iterator_seqbase_recursive_apply(&seq->seqbase, apply_fn, arg); - } - - return ret; -} diff --git a/source/blender/sequencer/intern/strip_add.c b/source/blender/sequencer/intern/strip_add.c index 55d92c1eb10..1106f47c477 100644 --- a/source/blender/sequencer/intern/strip_add.c +++ b/source/blender/sequencer/intern/strip_add.c @@ -50,6 +50,7 @@ #include "DEG_depsgraph_query.h" +#include "IMB_colormanagement.h" #include "IMB_imbuf.h" #include "IMB_imbuf_types.h" #include "IMB_metadata.h" @@ -128,6 +129,24 @@ static void seq_add_set_name(Sequence *seq, SeqLoadData *load_data) } } +static void seq_add_set_view_transform(Scene *scene, Sequence *seq, SeqLoadData *load_data) +{ + const char *strip_colorspace = seq->strip->colorspace_settings.name; + + if (load_data->flags & SEQ_LOAD_SET_VIEW_TRANSFORM) { + const char *role_colorspace_byte; + role_colorspace_byte = IMB_colormanagement_role_colorspace_name_get(COLOR_ROLE_DEFAULT_BYTE); + + if (STREQ(strip_colorspace, role_colorspace_byte)) { + struct ColorManagedDisplay *display = IMB_colormanagement_display_get_named( + scene->display_settings.display_device); + const char *default_view_transform = + IMB_colormanagement_display_get_default_view_transform_name(display); + STRNCPY(scene->view_settings.view_transform, default_view_transform); + } + } +} + /** * Add scene strip. * @@ -344,6 +363,7 @@ Sequence *SEQ_add_image_strip(Main *bmain, Scene *scene, ListBase *seqbase, SeqL /* Set Last active directory. */ BLI_strncpy(scene->ed->act_imagedir, seq->strip->dir, sizeof(scene->ed->act_imagedir)); + seq_add_set_view_transform(scene, seq, load_data); seq_add_set_name(seq, load_data); seq_add_generic_update(scene, seqbase, seq); @@ -559,6 +579,7 @@ Sequence *SEQ_add_movie_strip(Main *bmain, Scene *scene, ListBase *seqbase, SeqL strip->stripdata->orig_height = orig_height; BLI_split_dirfile(load_data->path, strip->dir, se->name, sizeof(strip->dir), sizeof(se->name)); + seq_add_set_view_transform(scene, seq, load_data); seq_add_set_name(seq, load_data); seq_add_generic_update(scene, seqbase, seq); diff --git a/source/blender/sequencer/intern/utils.c b/source/blender/sequencer/intern/utils.c index a15465eb3c0..6d5332b2b15 100644 --- a/source/blender/sequencer/intern/utils.c +++ b/source/blender/sequencer/intern/utils.c @@ -160,7 +160,7 @@ void SEQ_sequence_base_unique_name_recursive(ListBase *seqbasep, Sequence *seq) while (sui.match) { sui.match = 0; seqbase_unique_name(seqbasep, &sui); - SEQ_iterator_seqbase_recursive_apply(seqbasep, seqbase_unique_name_recursive_fn, &sui); + SEQ_seqbase_recursive_apply(seqbasep, seqbase_unique_name_recursive_fn, &sui); } BLI_strncpy(seq->name + 2, sui.name_dest, sizeof(seq->name) - 2); @@ -584,3 +584,31 @@ void SEQ_set_scale_to_fit(const Sequence *seq, break; } } + +int SEQ_seqbase_recursive_apply(ListBase *seqbase, + int (*apply_fn)(Sequence *seq, void *), + void *arg) +{ + Sequence *iseq; + for (iseq = seqbase->first; iseq; iseq = iseq->next) { + if (SEQ_recursive_apply(iseq, apply_fn, arg) == -1) { + return -1; /* bail out */ + } + } + return 1; +} + +int SEQ_recursive_apply(Sequence *seq, int (*apply_fn)(Sequence *, void *), void *arg) +{ + int ret = apply_fn(seq, arg); + + if (ret == -1) { + return -1; /* bail out */ + } + + if (ret && seq->seqbase.first) { + ret = SEQ_seqbase_recursive_apply(&seq->seqbase, apply_fn, arg); + } + + return ret; +} diff --git a/source/blender/windowmanager/intern/wm_playanim.c b/source/blender/windowmanager/intern/wm_playanim.c index 58030920fc7..85a05b88af7 100644 --- a/source/blender/windowmanager/intern/wm_playanim.c +++ b/source/blender/windowmanager/intern/wm_playanim.c @@ -237,6 +237,12 @@ typedef struct PlayAnimPict { struct anim *anim; int frame; int IB_flags; + +#ifdef USE_FRAME_CACHE_LIMIT + /** Back pointer to the #LinkData node for this struct in the #g_frame_cache.pics list. */ + LinkData *frame_cache_node; + size_t size_in_memory; +#endif } PlayAnimPict; static struct ListBase picsbase = {NULL, NULL}; @@ -248,9 +254,22 @@ static double fps_movie; #endif #ifdef USE_FRAME_CACHE_LIMIT -static struct ListBase inmempicsbase = {NULL, NULL}; -static int added_images = 0; -#endif +static struct { + /** A list of #LinkData nodes referencing #PlayAnimPict to track cached frames. */ + struct ListBase pics; + /** Number if elements in `pics`. */ + int pics_len; + /** Keep track of memory used by #g_frame_cache.pics when `g_frame_cache.memory_limit != 0`. */ + size_t pics_size_in_memory; + /** Optionally limit the amount of memory used for cache (in bytes), ignored when zero. */ + size_t memory_limit; +} g_frame_cache = { + .pics = {NULL, NULL}, + .pics_len = 0, + .pics_size_in_memory = 0, + .memory_limit = 0, +}; +#endif /* USE_FRAME_CACHE_LIMIT */ static PlayAnimPict *playanim_step(PlayAnimPict *playanim, int step) { @@ -441,7 +460,7 @@ static void build_pict_list_ex( */ while (IMB_ispic(filepath) && totframes) { - bool hasevent; + bool has_event; size_t size; int file; @@ -522,7 +541,7 @@ static void build_pict_list_ex( BLI_path_sequence_encode( filepath, fp_decoded.head, fp_decoded.tail, fp_decoded.digits, fp_framenr); - while ((hasevent = GHOST_ProcessEvents(g_WS.ghost_system, 0))) { + while ((has_event = GHOST_ProcessEvents(g_WS.ghost_system, false))) { GHOST_DispatchEvents(g_WS.ghost_system); if (ps->loading == false) { return; @@ -1222,6 +1241,15 @@ static char *wm_main_playanim_intern(int argc, const char **argv) argc--; argv++; break; + case 'c': { +#ifdef USE_FRAME_CACHE_LIMIT + const int memory_in_mb = max_ii(0, atoi(argv[2])); + g_frame_cache.memory_limit = (size_t)memory_in_mb * (1024 * 1024); +#endif + argc--; + argv++; + break; + } default: printf("unknown option '%c': skipping\n", argv[1][1]); break; @@ -1389,7 +1417,7 @@ static char *wm_main_playanim_intern(int argc, const char **argv) #endif while (ps.picture) { - int hasevent; + bool has_event; #ifndef USE_IMB_CACHE if (ibuf != NULL && ibuf->ftype == IMB_FTYPE_NONE) { IMB_freeImBuf(ibuf); @@ -1412,28 +1440,50 @@ static char *wm_main_playanim_intern(int argc, const char **argv) } if (ibuf) { -#ifdef USE_FRAME_CACHE_LIMIT - LinkData *node; -#endif - #ifdef USE_IMB_CACHE ps.picture->ibuf = ibuf; #endif #ifdef USE_FRAME_CACHE_LIMIT - /* Really basic memory conservation scheme. Keep frames in a FIFO queue. */ - node = inmempicsbase.last; + if (ps.picture->frame_cache_node == NULL) { + ps.picture->frame_cache_node = BLI_genericNodeN(ps.picture); + BLI_addhead(&g_frame_cache.pics, ps.picture->frame_cache_node); + g_frame_cache.pics_len++; + + if (g_frame_cache.memory_limit != 0) { + BLI_assert(ps.picture->size_in_memory == 0); + ps.picture->size_in_memory = IMB_get_size_in_memory(ps.picture->ibuf); + g_frame_cache.pics_size_in_memory += ps.picture->size_in_memory; + } + } + else { + /* Don't free the current frame by moving it to the head of the list. */ + BLI_assert(ps.picture->frame_cache_node->data == ps.picture); + BLI_remlink(&g_frame_cache.pics, ps.picture->frame_cache_node); + BLI_addhead(&g_frame_cache.pics, ps.picture->frame_cache_node); + } - while (node && added_images > PLAY_FRAME_CACHE_MAX) { + /* Really basic memory conservation scheme. Keep frames in a FIFO queue. */ + LinkData *node = g_frame_cache.pics.last; + while (node && (g_frame_cache.memory_limit ? + (g_frame_cache.pics_size_in_memory > g_frame_cache.memory_limit) : + (g_frame_cache.pics_len > PLAY_FRAME_CACHE_MAX))) { PlayAnimPict *pic = node->data; + BLI_assert(pic->frame_cache_node == node); if (pic->ibuf && pic->ibuf != ibuf) { LinkData *node_tmp; IMB_freeImBuf(pic->ibuf); + if (g_frame_cache.memory_limit != 0) { + BLI_assert(pic->size_in_memory != 0); + g_frame_cache.pics_size_in_memory -= pic->size_in_memory; + pic->size_in_memory = 0; + } pic->ibuf = NULL; + pic->frame_cache_node = NULL; node_tmp = node->prev; - BLI_freelinkN(&inmempicsbase, node); - added_images--; + BLI_freelinkN(&g_frame_cache.pics, node); + g_frame_cache.pics_len--; node = node_tmp; } else { @@ -1441,8 +1491,6 @@ static char *wm_main_playanim_intern(int argc, const char **argv) } } - BLI_addhead(&inmempicsbase, BLI_genericNodeN(ps.picture)); - added_images++; #endif /* USE_FRAME_CACHE_LIMIT */ BLI_strncpy(ibuf->name, ps.picture->name, sizeof(ibuf->name)); @@ -1474,14 +1522,14 @@ static char *wm_main_playanim_intern(int argc, const char **argv) ps.next_frame = ps.direction; - while ((hasevent = GHOST_ProcessEvents(g_WS.ghost_system, 0))) { + while ((has_event = GHOST_ProcessEvents(g_WS.ghost_system, false))) { GHOST_DispatchEvents(g_WS.ghost_system); } if (ps.go == false) { break; } change_frame(&ps); - if (!hasevent) { + if (!has_event) { PIL_sleep_ms(1); } if (ps.wait2) { @@ -1550,8 +1598,11 @@ static char *wm_main_playanim_intern(int argc, const char **argv) #endif BLI_freelistN(&picsbase); - BLI_freelistN(&inmempicsbase); - added_images = 0; + +#ifdef USE_FRAME_CACHE_LIMIT + BLI_freelistN(&g_frame_cache.pics); + g_frame_cache.pics_len = 0; +#endif #ifdef WITH_AUDASPACE if (playback_handle) { diff --git a/source/blender/windowmanager/intern/wm_window.c b/source/blender/windowmanager/intern/wm_window.c index 2e9fd1b1b16..cdd5ea12df8 100644 --- a/source/blender/windowmanager/intern/wm_window.c +++ b/source/blender/windowmanager/intern/wm_window.c @@ -133,7 +133,7 @@ static struct WMInitStruct { * \{ */ static void wm_window_set_drawable(wmWindowManager *wm, wmWindow *win, bool activate); -static int wm_window_timer(const bContext *C); +static bool wm_window_timer(const bContext *C); /* XXX this one should correctly check for apple top header... * done for Cocoa : returns window contents (and not frame) max size*/ @@ -1498,12 +1498,12 @@ static int ghost_event_proc(GHOST_EventHandle evt, GHOST_TUserDataPtr C_void_ptr * Timer handlers should check for delta to decide if they just update, or follow real time. * Timer handlers can also set duration to match frames passed */ -static int wm_window_timer(const bContext *C) +static bool wm_window_timer(const bContext *C) { Main *bmain = CTX_data_main(C); wmWindowManager *wm = CTX_wm_manager(C); double time = PIL_check_seconds_timer(); - int retval = 0; + bool has_event = false; /* Mutable in case the timer gets removed. */ LISTBASE_FOREACH_MUTABLE (wmTimer *, wt, &wm->timers) { @@ -1540,31 +1540,34 @@ static int wm_window_timer(const bContext *C) event.customdata = wt; wm_event_add(win, &event); - retval = 1; + has_event = true; } } } - return retval; + return has_event; } void wm_window_process_events(const bContext *C) { BLI_assert(BLI_thread_is_main()); - int hasevent = GHOST_ProcessEvents(g_system, 0); /* 0 is no wait */ + bool has_event = GHOST_ProcessEvents(g_system, false); /* `false` is no wait. */ - if (hasevent) { + if (has_event) { GHOST_DispatchEvents(g_system); } - hasevent |= wm_window_timer(C); + has_event |= wm_window_timer(C); #ifdef WITH_XR_OPENXR /* XR events don't use the regular window queues. So here we don't only trigger * processing/dispatching but also handling. */ - hasevent |= wm_xr_events_handle(CTX_wm_manager(C)); + has_event |= wm_xr_events_handle(CTX_wm_manager(C)); #endif - /* no event, we sleep 5 milliseconds */ - if (hasevent == 0) { + /* When there is no event, sleep 5 milliseconds not to use too much CPU when idle. + * + * Skip sleeping when simulating events so tests don't idle unnecessarily as simulated + * events are typically generated from a timer that runs in the main loop. */ + if ((has_event == false) && !(G.f & G_FLAG_EVENT_SIMULATE)) { PIL_sleep_ms(5); } } diff --git a/source/creator/creator_args.c b/source/creator/creator_args.c index d7f649b657d..9c7b7dc3f34 100644 --- a/source/creator/creator_args.c +++ b/source/creator/creator_args.c @@ -1189,7 +1189,10 @@ static const char arg_handle_playback_mode_doc[] = "\t-s <frame>\n" "\t\tPlay from <frame>.\n" "\t-e <frame>\n" - "\t\tPlay until <frame>."; + "\t\tPlay until <frame>.\n" + "\t-c <cache_memory>\n" + "\t\tAmount of memory in megabytes to allow for caching images during playback.\n" + "\t\tZero disables (clamping to a fixed number of frames instead)."; static int arg_handle_playback_mode(int argc, const char **argv, void *UNUSED(data)) { /* not if -b was given first */ |