From 54d69a2fd1be60aaba62f92cd0db5b5ba71495aa Mon Sep 17 00:00:00 2001 From: Howard Trickey Date: Fri, 21 Jan 2022 14:37:33 -0500 Subject: Fix new OBJ exporter to handle instancing. The new OBJ exporter did not handle object instances. The fix is to use a dependency graph iterator, asking for instances. Unfortunately that iterator makes a temporary copy of instance objects that does not persist past the iteration, but we need to save all the objects and meshes to write later, so the Object has to be copied now. This changed some unit tests. Even though the tests don't have instancing, the iterator also picks up some Text objects as Mesh ones (which is a good thing), resulting in two more objects in the all_objects.obj file output. --- .../io/wavefront_obj/exporter/obj_export_mesh.cc | 35 ++++++++++++++-------- .../io/wavefront_obj/exporter/obj_export_mesh.hh | 9 ++++-- .../io/wavefront_obj/exporter/obj_exporter.cc | 26 ++++++++-------- .../io/wavefront_obj/tests/obj_exporter_tests.cc | 4 +-- 4 files changed, 45 insertions(+), 29 deletions(-) diff --git a/source/blender/io/wavefront_obj/exporter/obj_export_mesh.cc b/source/blender/io/wavefront_obj/exporter/obj_export_mesh.cc index d6e1d8a7ea5..b77b5735784 100644 --- a/source/blender/io/wavefront_obj/exporter/obj_export_mesh.cc +++ b/source/blender/io/wavefront_obj/exporter/obj_export_mesh.cc @@ -17,6 +17,8 @@ /** \file * \ingroup obj */ +/* Silence warnings from copying deprecated fields. Needed for an Object copy constructor use. */ +#define DNA_DEPRECATED_ALLOW #include "BKE_customdata.h" #include "BKE_deform.h" @@ -42,20 +44,21 @@ namespace blender::io::obj { OBJMesh::OBJMesh(Depsgraph *depsgraph, const OBJExportParams &export_params, Object *mesh_object) { - export_object_eval_ = DEG_get_evaluated_object(depsgraph, mesh_object); - export_mesh_eval_ = BKE_object_get_evaluated_mesh(export_object_eval_); + /* We need to copy the object because it may be in temporary space. */ + Object *obj_eval = DEG_get_evaluated_object(depsgraph, mesh_object); + export_object_eval_ = *obj_eval; + export_mesh_eval_ = BKE_object_get_evaluated_mesh(&export_object_eval_); mesh_eval_needs_free_ = false; if (!export_mesh_eval_) { /* Curves and NURBS surfaces need a new mesh when they're * exported in the form of vertices and edges. */ - export_mesh_eval_ = BKE_mesh_new_from_object(depsgraph, export_object_eval_, true, true); + export_mesh_eval_ = BKE_mesh_new_from_object(depsgraph, &export_object_eval_, true, true); /* Since a new mesh been allocated, it needs to be freed in the destructor. */ mesh_eval_needs_free_ = true; } - if (export_params.export_triangulated_mesh && - ELEM(export_object_eval_->type, OB_MESH, OB_SURF)) { + if (export_params.export_triangulated_mesh && ELEM(export_object_eval_.type, OB_MESH, OB_SURF)) { std::tie(export_mesh_eval_, mesh_eval_needs_free_) = triangulate_mesh_eval(); } set_world_axes_transform(export_params.forward_axis, export_params.up_axis); @@ -116,10 +119,10 @@ void OBJMesh::set_world_axes_transform(const eTransformAxisForward forward, mat3_from_axis_conversion(OBJ_AXIS_Y_FORWARD, OBJ_AXIS_Z_UP, forward, up, axes_transform); /* mat3_from_axis_conversion returns a transposed matrix! */ transpose_m3(axes_transform); - mul_m4_m3m4(world_and_axes_transform_, axes_transform, export_object_eval_->obmat); + mul_m4_m3m4(world_and_axes_transform_, axes_transform, export_object_eval_.obmat); /* mul_m4_m3m4 does not transform last row of obmat, i.e. location data. */ - mul_v3_m3v3(world_and_axes_transform_[3], axes_transform, export_object_eval_->obmat[3]); - world_and_axes_transform_[3][3] = export_object_eval_->obmat[3][3]; + mul_v3_m3v3(world_and_axes_transform_[3], axes_transform, export_object_eval_.obmat[3]); + world_and_axes_transform_[3][3] = export_object_eval_.obmat[3][3]; } int OBJMesh::tot_vertices() const @@ -185,8 +188,14 @@ void OBJMesh::calc_smooth_groups(const bool use_bitflags) const Material *OBJMesh::get_object_material(const int16_t mat_nr) const { - /* "+ 1" as material getter needs one-based indices. */ - const Material *r_mat = BKE_object_material_get(export_object_eval_, mat_nr + 1); + /** + * The const_cast is safe here because BKE_object_material_get won't change the object + * but it is a big can of worms to fix the declaration of that function right now. + * + * The call uses "+ 1" as material getter needs one-based indices. + */ + Object *obj = const_cast(&export_object_eval_); + const Material *r_mat = BKE_object_material_get(obj, mat_nr + 1); #ifdef DEBUG if (!r_mat) { std::cerr << "Material not found for mat_nr = " << mat_nr << std::endl; @@ -209,7 +218,7 @@ int16_t OBJMesh::ith_poly_matnr(const int poly_index) const const char *OBJMesh::get_object_name() const { - return export_object_eval_->id.name + 2; + return export_object_eval_.id.name + 2; } const char *OBJMesh::get_object_mesh_name() const @@ -403,7 +412,7 @@ int16_t OBJMesh::get_poly_deform_group_index(const int poly_index) const BLI_assert(poly_index < export_mesh_eval_->totpoly); const MPoly &mpoly = export_mesh_eval_->mpoly[poly_index]; const MLoop *mloop = &export_mesh_eval_->mloop[mpoly.loopstart]; - const Object *obj = export_object_eval_; + const Object *obj = &export_object_eval_; const int tot_deform_groups = BKE_object_defgroup_count(obj); /* Indices of the vector index into deform groups of an object; values are the] * number of vertex members in one deform group. */ @@ -444,7 +453,7 @@ int16_t OBJMesh::get_poly_deform_group_index(const int poly_index) const const char *OBJMesh::get_poly_deform_group_name(const int16_t def_group_index) const { const bDeformGroup &vertex_group = *(static_cast( - BLI_findlink(BKE_object_defgroup_list(export_object_eval_), def_group_index))); + BLI_findlink(BKE_object_defgroup_list(&export_object_eval_), def_group_index))); return vertex_group.name; } diff --git a/source/blender/io/wavefront_obj/exporter/obj_export_mesh.hh b/source/blender/io/wavefront_obj/exporter/obj_export_mesh.hh index 390d8034337..f3ace140006 100644 --- a/source/blender/io/wavefront_obj/exporter/obj_export_mesh.hh +++ b/source/blender/io/wavefront_obj/exporter/obj_export_mesh.hh @@ -32,6 +32,7 @@ #include "DNA_material_types.h" #include "DNA_mesh_types.h" #include "DNA_meshdata_types.h" +#include "DNA_object_types.h" #include "IO_wavefront_obj.h" @@ -57,7 +58,11 @@ using unique_bmesh_ptr = std::unique_ptr; class OBJMesh : NonCopyable { private: - Object *export_object_eval_; + /** + * We need to copy the entire Object structure here because the dependency graph iterator + * sometimes builds an Object in a temporary space that doesn't persist. + */ + Object export_object_eval_; Mesh *export_mesh_eval_; /** * For curves which are converted to mesh, and triangulated meshes, a new mesh is allocated. @@ -85,7 +90,7 @@ class OBJMesh : NonCopyable { * Total number of normal indices (maximum entry, plus 1, in * the loop_to_norm_index_ vector). */ - int tot_normal_indices_ = NEGATIVE_INIT; + int tot_normal_indices_ = 0; /** * Total smooth groups in an object. */ diff --git a/source/blender/io/wavefront_obj/exporter/obj_exporter.cc b/source/blender/io/wavefront_obj/exporter/obj_exporter.cc index 595e6aaf4f2..0c753ccdcac 100644 --- a/source/blender/io/wavefront_obj/exporter/obj_exporter.cc +++ b/source/blender/io/wavefront_obj/exporter/obj_exporter.cc @@ -91,28 +91,29 @@ filter_supported_objects(Depsgraph *depsgraph, const OBJExportParams &export_par { Vector> r_exportable_meshes; Vector> r_exportable_nurbs; - const ViewLayer *view_layer = DEG_get_input_view_layer(depsgraph); - LISTBASE_FOREACH (const Base *, base, &view_layer->object_bases) { - Object *object_in_layer = base->object; - if (export_params.export_selected_objects && !(object_in_layer->base_flag & BASE_SELECTED)) { + const int deg_objects_visibility_flags = DEG_ITER_OBJECT_FLAG_LINKED_DIRECTLY | + DEG_ITER_OBJECT_FLAG_LINKED_VIA_SET | + DEG_ITER_OBJECT_FLAG_VISIBLE | + DEG_ITER_OBJECT_FLAG_DUPLI; + DEG_OBJECT_ITER_BEGIN (depsgraph, object, deg_objects_visibility_flags) { + if (export_params.export_selected_objects && !(object->base_flag & BASE_SELECTED)) { continue; } - switch (object_in_layer->type) { + switch (object->type) { case OB_SURF: /* Export in mesh form: vertices and polygons. */ ATTR_FALLTHROUGH; case OB_MESH: - r_exportable_meshes.append( - std::make_unique(depsgraph, export_params, object_in_layer)); + r_exportable_meshes.append(std::make_unique(depsgraph, export_params, object)); break; case OB_CURVE: { - Curve *curve = static_cast(object_in_layer->data); + Curve *curve = static_cast(object->data); Nurb *nurb{static_cast(curve->nurb.first)}; if (!nurb) { /* An empty curve. Not yet supported to export these as meshes. */ if (export_params.export_curves_as_nurbs) { r_exportable_nurbs.append( - std::make_unique(depsgraph, export_params, object_in_layer)); + std::make_unique(depsgraph, export_params, object)); } break; } @@ -121,18 +122,18 @@ filter_supported_objects(Depsgraph *depsgraph, const OBJExportParams &export_par if (export_params.export_curves_as_nurbs) { /* Export in parameter form: control points. */ r_exportable_nurbs.append( - std::make_unique(depsgraph, export_params, object_in_layer)); + std::make_unique(depsgraph, export_params, object)); } else { /* Export in mesh form: edges and vertices. */ r_exportable_meshes.append( - std::make_unique(depsgraph, export_params, object_in_layer)); + std::make_unique(depsgraph, export_params, object)); } break; case CU_BEZIER: /* Always export in mesh form: edges and vertices. */ r_exportable_meshes.append( - std::make_unique(depsgraph, export_params, object_in_layer)); + std::make_unique(depsgraph, export_params, object)); break; default: /* Other curve types are not supported. */ @@ -145,6 +146,7 @@ filter_supported_objects(Depsgraph *depsgraph, const OBJExportParams &export_par break; } } + DEG_OBJECT_ITER_END; return {std::move(r_exportable_meshes), std::move(r_exportable_nurbs)}; } diff --git a/source/blender/io/wavefront_obj/tests/obj_exporter_tests.cc b/source/blender/io/wavefront_obj/tests/obj_exporter_tests.cc index 92d478c20a1..5dac913c902 100644 --- a/source/blender/io/wavefront_obj/tests/obj_exporter_tests.cc +++ b/source/blender/io/wavefront_obj/tests/obj_exporter_tests.cc @@ -60,7 +60,7 @@ TEST_F(obj_exporter_test, filter_objects_curves_as_mesh) return; } auto [objmeshes, objcurves]{filter_supported_objects(depsgraph, _export.params)}; - EXPECT_EQ(objmeshes.size(), 17); + EXPECT_EQ(objmeshes.size(), 19); EXPECT_EQ(objcurves.size(), 0); } @@ -73,7 +73,7 @@ TEST_F(obj_exporter_test, filter_objects_curves_as_nurbs) } _export.params.export_curves_as_nurbs = true; auto [objmeshes, objcurves]{filter_supported_objects(depsgraph, _export.params)}; - EXPECT_EQ(objmeshes.size(), 16); + EXPECT_EQ(objmeshes.size(), 18); EXPECT_EQ(objcurves.size(), 2); } -- cgit v1.2.3 From 4f9be46526e5b17f6da2b7548ae7ccc10f84bdb5 Mon Sep 17 00:00:00 2001 From: Aaron Carlisle Date: Fri, 21 Jan 2022 15:31:51 -0500 Subject: Distribute Points on Faces: Fix missing minimum value for density socket Negative number density is not a part of this reality. --- .../blender/nodes/geometry/nodes/node_geo_distribute_points_on_faces.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/blender/nodes/geometry/nodes/node_geo_distribute_points_on_faces.cc b/source/blender/nodes/geometry/nodes/node_geo_distribute_points_on_faces.cc index a257af4391c..d17657bfa3a 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_distribute_points_on_faces.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_distribute_points_on_faces.cc @@ -44,7 +44,7 @@ static void node_declare(NodeDeclarationBuilder &b) b.add_input(N_("Selection")).default_value(true).hide_value().supports_field(); b.add_input(N_("Distance Min")).min(0.0f).subtype(PROP_DISTANCE); b.add_input(N_("Density Max")).default_value(10.0f).min(0.0f); - b.add_input(N_("Density")).default_value(10.0f).supports_field(); + b.add_input(N_("Density")).default_value(10.0f).min(0.0f).supports_field(); b.add_input(N_("Density Factor")) .default_value(1.0f) .min(0.0f) -- cgit v1.2.3 From 32ceb0b80710473c9a9b818da23919f184fa3aae Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Fri, 21 Jan 2022 20:07:20 +0100 Subject: Fix std::optional value() build error on older macOS SDK No longer happens on the buildbot, but for users building with an older Xcode we still need to avoid using value(). --- .../blender/blenkernel/intern/idprop_serialize.cc | 26 +++++++++++----------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/source/blender/blenkernel/intern/idprop_serialize.cc b/source/blender/blenkernel/intern/idprop_serialize.cc index 92dce49500c..08a7f13b806 100644 --- a/source/blender/blenkernel/intern/idprop_serialize.cc +++ b/source/blender/blenkernel/intern/idprop_serialize.cc @@ -304,7 +304,7 @@ class IDPStringSerializer : public IDPropertySerializer { std::unique_ptr entry_to_idprop( DictionaryEntryParser &entry_reader) const override { - BLI_assert(entry_reader.get_type().value() == IDP_STRING); + BLI_assert(*(entry_reader.get_type()) == IDP_STRING); std::optional name = entry_reader.get_name(); if (!name.has_value()) { return nullptr; @@ -344,7 +344,7 @@ class IDPIntSerializer : public IDPropertySerializer { std::unique_ptr entry_to_idprop( DictionaryEntryParser &entry_reader) const override { - BLI_assert(entry_reader.get_type().value() == IDP_INT); + BLI_assert(*(entry_reader.get_type()) == IDP_INT); std::optional name = entry_reader.get_name(); if (!name.has_value()) { return nullptr; @@ -384,7 +384,7 @@ class IDPFloatSerializer : public IDPropertySerializer { std::unique_ptr entry_to_idprop( DictionaryEntryParser &entry_reader) const override { - BLI_assert(entry_reader.get_type().value() == IDP_FLOAT); + BLI_assert(*(entry_reader.get_type()) == IDP_FLOAT); std::optional name = entry_reader.get_name(); if (!name.has_value()) { return nullptr; @@ -424,7 +424,7 @@ class IDPDoubleSerializer : public IDPropertySerializer { std::unique_ptr entry_to_idprop( DictionaryEntryParser &entry_reader) const override { - BLI_assert(entry_reader.get_type().value() == IDP_DOUBLE); + BLI_assert(*(entry_reader.get_type()) == IDP_DOUBLE); std::optional name = entry_reader.get_name(); if (!name.has_value()) { return nullptr; @@ -502,7 +502,7 @@ class IDPArraySerializer : public IDPropertySerializer { std::unique_ptr entry_to_idprop( DictionaryEntryParser &entry_reader) const override { - BLI_assert(entry_reader.get_type().value() == IDP_ARRAY); + BLI_assert(*(entry_reader.get_type()) == IDP_ARRAY); std::optional property_subtype = entry_reader.get_subtype(); if (!property_subtype.has_value()) { return nullptr; @@ -556,8 +556,8 @@ class IDPArraySerializer : public IDPropertySerializer { std::unique_ptr idprop_array_int_from_value( DictionaryEntryParser &entry_reader) const { - BLI_assert(entry_reader.get_type().value() == IDP_ARRAY); - BLI_assert(entry_reader.get_subtype().value() == IDP_INT); + BLI_assert(*(entry_reader.get_type()) == IDP_ARRAY); + BLI_assert(*(entry_reader.get_subtype()) == IDP_INT); std::optional name = entry_reader.get_name(); if (!name.has_value()) { return nullptr; @@ -572,8 +572,8 @@ class IDPArraySerializer : public IDPropertySerializer { std::unique_ptr idprop_array_float_from_value( DictionaryEntryParser &entry_reader) const { - BLI_assert(entry_reader.get_type().value() == IDP_ARRAY); - BLI_assert(entry_reader.get_subtype().value() == IDP_FLOAT); + BLI_assert(*(entry_reader.get_type()) == IDP_ARRAY); + BLI_assert(*(entry_reader.get_subtype()) == IDP_FLOAT); std::optional name = entry_reader.get_name(); if (!name.has_value()) { return nullptr; @@ -588,8 +588,8 @@ class IDPArraySerializer : public IDPropertySerializer { std::unique_ptr idprop_array_double_from_value( DictionaryEntryParser &entry_reader) const { - BLI_assert(entry_reader.get_type().value() == IDP_ARRAY); - BLI_assert(entry_reader.get_subtype().value() == IDP_DOUBLE); + BLI_assert(*(entry_reader.get_type()) == IDP_ARRAY); + BLI_assert(*(entry_reader.get_subtype()) == IDP_DOUBLE); std::optional name = entry_reader.get_name(); if (!name.has_value()) { return nullptr; @@ -639,7 +639,7 @@ class IDPGroupSerializer : public IDPropertySerializer { std::unique_ptr entry_to_idprop( DictionaryEntryParser &entry_reader) const override { - BLI_assert(entry_reader.get_type().value() == IDP_GROUP); + BLI_assert(*(entry_reader.get_type()) == IDP_GROUP); std::optional name = entry_reader.get_name(); if (!name.has_value()) { return nullptr; @@ -796,7 +796,7 @@ static IDProperty *idprop_from_value(const DictionaryValue &value) return nullptr; } - const IDPropertySerializer &serializer = serializer_for(property_type.value()); + const IDPropertySerializer &serializer = serializer_for(*property_type); return serializer.entry_to_idprop(entry_reader).release(); } -- cgit v1.2.3 From e07b217669e227eda80d1f7b341c875a2d05dc5e Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Fri, 21 Jan 2022 20:58:00 +0100 Subject: Build: update macOS for FreeType library with woff2 support Ref D13448, T93161 --- build_files/cmake/platform/platform_apple.cmake | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/build_files/cmake/platform/platform_apple.cmake b/build_files/cmake/platform/platform_apple.cmake index 447645b6806..8acea1be841 100644 --- a/build_files/cmake/platform/platform_apple.cmake +++ b/build_files/cmake/platform/platform_apple.cmake @@ -166,7 +166,11 @@ if(WITH_FFTW3) find_package(Fftw3) endif() +# FreeType compiled with Brotli compression for woff2. find_package(Freetype REQUIRED) +list(APPEND FREETYPE_LIBRARIES + ${LIBDIR}/brotli/lib/libbrotlicommon-static.a + ${LIBDIR}/brotli/lib/libbrotlidec-static.a) if(WITH_IMAGE_OPENEXR) find_package(OpenEXR) -- cgit v1.2.3 From 1c23a067069463190b084af9560d4fe6263e6fe2 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Fri, 21 Jan 2022 16:04:16 -0600 Subject: Fix: Applying object transform can create normal layers Because this operator is used on original objects, it's best to tag the normals dirty instead of explicitly calculating them, to avoid unnecessarily storing normal layers on an original object (since they might have to be recalculated during evaluation anyway). There may be other places this change is helpful, but being conservative is likely better for now. Related to T95125 --- source/blender/editors/object/object_transform.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/blender/editors/object/object_transform.c b/source/blender/editors/object/object_transform.c index c320313643d..6d04bd3f9d5 100644 --- a/source/blender/editors/object/object_transform.c +++ b/source/blender/editors/object/object_transform.c @@ -811,8 +811,8 @@ static int apply_objects_internal(bContext *C, /* adjust data */ BKE_mesh_transform(me, mat, true); - /* update normals */ - BKE_mesh_calc_normals(me); + /* If normal layers exist, they are now dirty. */ + BKE_mesh_normals_tag_dirty(me); } else if (ob->type == OB_ARMATURE) { bArmature *arm = ob->data; -- cgit v1.2.3 From 294ab849099828d49ee4492083d9ba15e2678fe3 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Fri, 21 Jan 2022 16:08:21 -0600 Subject: Fix T95097: Attribute Capture node UI inconsistency All other nodes with data type and domain choices have the domain below the data type. Generally that order makes sense, because it's consistent with nodes that have no domain drop-down. --- source/blender/nodes/geometry/nodes/node_geo_attribute_capture.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_capture.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_capture.cc index 9001cb2d1f2..840dfd2fbd3 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_capture.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_capture.cc @@ -48,8 +48,8 @@ static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) { uiLayoutSetPropSep(layout, true); uiLayoutSetPropDecorate(layout, false); - uiItemR(layout, ptr, "domain", 0, "", ICON_NONE); uiItemR(layout, ptr, "data_type", 0, "", ICON_NONE); + uiItemR(layout, ptr, "domain", 0, "", ICON_NONE); } static void node_init(bNodeTree *UNUSED(tree), bNode *node) -- cgit v1.2.3 From 68aa35ae74226fbed936b1879302fc155d9acfac Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Fri, 21 Jan 2022 16:12:12 -0600 Subject: Fix T94967: Sculpt mode crashes with missing normals From an error in rBcfa53e0fbeed, the vertex normals in `SculptSession` seem to be used, but in the case when no "pbvh" is used, the value of the pointer is never assigned. Normals were not generally dirty before this "ensure" function with regular sculpting operations, so this addition shouldn't have any cost. Differential Revision: https://developer.blender.org/D13854 --- source/blender/blenkernel/intern/paint.c | 1 + 1 file changed, 1 insertion(+) diff --git a/source/blender/blenkernel/intern/paint.c b/source/blender/blenkernel/intern/paint.c index 72210eea71d..407375c4d22 100644 --- a/source/blender/blenkernel/intern/paint.c +++ b/source/blender/blenkernel/intern/paint.c @@ -1648,6 +1648,7 @@ static void sculpt_update_object(Depsgraph *depsgraph, ss->totvert = me->totvert; ss->totpoly = me->totpoly; ss->totfaces = me->totpoly; + ss->vert_normals = BKE_mesh_vertex_normals_ensure(me); ss->mvert = me->mvert; ss->mpoly = me->mpoly; ss->mloop = me->mloop; -- cgit v1.2.3 From d590e223daf6e20d462f2b197d32606d69873051 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Fri, 21 Jan 2022 16:14:00 -0600 Subject: Fix T94974: Invalid normals in edit mode Normal layers currently aren't stored in the undo step mesh storage, since they are not stored in files at all. However, the edit mesh expects normals to be fully calculated, and does not keep track of a dirty state. This patch updates the normals in the BMesh created by loading an undo step. Another option would be calculating the normals on the undo mesh first, which might be better if Mesh normal calculation is faster than BMesh calculation, but the preferred method to access vertex normals fails in this case, because the mesh runtime mutexes are not initialized for undo-state meshes. Differential Revision: https://developer.blender.org/D13859 --- source/blender/editors/mesh/editmesh_undo.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/source/blender/editors/mesh/editmesh_undo.c b/source/blender/editors/mesh/editmesh_undo.c index 46a056f865b..5d573271ea3 100644 --- a/source/blender/editors/mesh/editmesh_undo.c +++ b/source/blender/editors/mesh/editmesh_undo.c @@ -687,11 +687,15 @@ static void undomesh_to_editmesh(UndoMesh *um, Object *ob, BMEditMesh *em, Key * .active_shapekey = um->shapenr, })); + /* Normals should not be stored in the undo mesh, so recalculate them. The edit + * mesh is expected to have valid normals and there is no tracked dirty state. */ + BLI_assert(BKE_mesh_vertex_normals_are_dirty(&um->me)); + BM_mesh_normals_update(bm); + em_tmp = BKE_editmesh_create(bm); *em = *em_tmp; - /* Calculate face normals and tessellation at once since it's multi-threaded. - * The vertex normals are stored in the undo-mesh, so this doesn't need to be updated. */ + /* Calculate face normals and tessellation at once since it's multi-threaded. */ BKE_editmesh_looptri_calc_ex(em, &(const struct BMeshCalcTessellation_Params){ .face_normals = true, -- cgit v1.2.3 From 45d038181ae25972011f9656ba4f7062aa1c534f Mon Sep 17 00:00:00 2001 From: Laurynas Duburas Date: Fri, 21 Jan 2022 16:40:49 -0600 Subject: Curves: Improve accuracy and clarity of NURBS knots calculation This commit improves NURBS knot generation by adding proper support for the combination of the Bezier and cyclic options. In other cases the resulting knot doesn't change. This cyclic Bezier knot is used to create accurate accurate "Nurbs Circle", "Nurbs Cylinder" primitives. "Nurbs Sphere" and "Nurbs Torus" primitives are also improved by tweaking the spin operator. The knot vector in 3rd order NURBS curve with Bezier option turned on (without cyclic) is changed in comparison to previous calculations, although it doesn't change the curve shape itself. The accuracy of the of NURBS circle is fixed, which can be checked by comparing with mesh circle. Tessellation spacing differences in circular NURBS is also fixed, which is observable with the NURBS cylinder and sphere primitives. These were causing seam-like effects. This commit contains comments from Piotr Makal (@pmakal). Differential Revision: https://developer.blender.org/D11664 --- source/blender/blenkernel/intern/curve.cc | 112 ++++++----------------- source/blender/blenkernel/intern/spline_nurbs.cc | 72 +++++---------- source/blender/editors/curve/editcurve.c | 11 ++- source/blender/editors/curve/editcurve_add.c | 6 +- 4 files changed, 59 insertions(+), 142 deletions(-) diff --git a/source/blender/blenkernel/intern/curve.cc b/source/blender/blenkernel/intern/curve.cc index 755b05e2697..70edaccb244 100644 --- a/source/blender/blenkernel/intern/curve.cc +++ b/source/blender/blenkernel/intern/curve.cc @@ -30,6 +30,7 @@ #include "BLI_blenlib.h" #include "BLI_endian_switch.h" #include "BLI_ghash.h" +#include "BLI_index_range.hh" #include "BLI_math.h" #include "BLI_utildefines.h" @@ -67,10 +68,12 @@ #include "BLO_read_write.h" +using blender::IndexRange; + /* globals */ /* local */ -static CLG_LogRef LOG = {"bke.curve"}; +// static CLG_LogRef LOG = {"bke.curve"}; static void curve_init_data(ID *id) { @@ -1160,81 +1163,34 @@ void BKE_nurb_bpoint_calc_plane(struct Nurb *nu, BPoint *bp, float r_plane[3]) static void calcknots(float *knots, const int pnts, const short order, const short flag) { - /* knots: number of pnts NOT corrected for cyclic */ - const int pnts_order = pnts + order; - float k; - int a; + const bool is_cyclic = flag & CU_NURB_CYCLIC; + const bool is_bezier = flag & CU_NURB_BEZIER && !(flag & CU_NURB_ENDPOINT); + const bool is_end_point = flag & CU_NURB_ENDPOINT && !(flag & CU_NURB_BEZIER); + /* Inner knots are always repeated once except on Bezier case. */ + const int repeat_inner = is_bezier ? order - 1 : 1; + /* How many times to repeat 0.0 at the beginning of knot. */ + const int head = is_end_point && !is_cyclic ? order : (is_bezier ? order / 2 : 1); + /* Number of knots replicating widths of the starting knots. + * Covers both Cyclic and EndPoint cases. */ + const int tail = is_cyclic ? 2 * order - 1 : (is_end_point ? order : 0); - switch (flag & (CU_NURB_ENDPOINT | CU_NURB_BEZIER)) { - case CU_NURB_ENDPOINT: - k = 0.0; - for (a = 1; a <= pnts_order; a++) { - knots[a - 1] = k; - if (a >= order && a <= pnts) { - k += 1.0f; - } - } - break; - case CU_NURB_BEZIER: - /* Warning, the order MUST be 2 or 4, - * if this is not enforced, the displist will be corrupt */ - if (order == 4) { - k = 0.34; - for (a = 0; a < pnts_order; a++) { - knots[a] = floorf(k); - k += (1.0f / 3.0f); - } - } - else if (order == 3) { - k = 0.6f; - for (a = 0; a < pnts_order; a++) { - if (a >= order && a <= pnts) { - k += 0.5f; - } - knots[a] = floorf(k); - } - } - else { - CLOG_ERROR(&LOG, "bez nurb curve order is not 3 or 4, should never happen"); - } - break; - default: - for (a = 0; a < pnts_order; a++) { - knots[a] = (float)a; - } - break; - } -} - -static void makecyclicknots(float *knots, int pnts, short order) -/* pnts, order: number of pnts NOT corrected for cyclic */ -{ - int a, b, order2, c; - - if (knots == nullptr) { - return; - } + const int knot_count = pnts + order + (is_cyclic ? order - 1 : 0); - order2 = order - 1; + int r = head; + float current = 0.0f; - /* do first long rows (order -1), remove identical knots at endpoints */ - if (order > 2) { - b = pnts + order2; - for (a = 1; a < order2; a++) { - if (knots[b] != knots[b - a]) { - break; - } - } - if (a == order2) { - knots[pnts + order - 2] += 1.0f; + for (const int i : IndexRange(knot_count - tail)) { + knots[i] = current; + r--; + if (r == 0) { + current += 1.0; + r = repeat_inner; } } - b = order; - c = pnts + order + order2; - for (a = pnts + order2; a < c; a++) { - knots[a] = knots[a - 1] + (knots[b] - knots[b - 1]); - b--; + const int tail_index = knot_count - tail; + for (const int i : IndexRange(tail)) { + knots[tail_index + i] = current + (knots[i] - knots[0]); } } @@ -1247,13 +1203,7 @@ static void makeknots(Nurb *nu, short uv) } if (BKE_nurb_check_valid_u(nu)) { nu->knotsu = (float *)MEM_calloc_arrayN(KNOTSU(nu) + 1, sizeof(float), "makeknots"); - if (nu->flagu & CU_NURB_CYCLIC) { - calcknots(nu->knotsu, nu->pntsu, nu->orderu, 0); /* cyclic should be uniform */ - makecyclicknots(nu->knotsu, nu->pntsu, nu->orderu); - } - else { - calcknots(nu->knotsu, nu->pntsu, nu->orderu, nu->flagu); - } + calcknots(nu->knotsu, nu->pntsu, nu->orderu, nu->flagu); } else { nu->knotsu = nullptr; @@ -1265,13 +1215,7 @@ static void makeknots(Nurb *nu, short uv) } if (BKE_nurb_check_valid_v(nu)) { nu->knotsv = (float *)MEM_calloc_arrayN(KNOTSV(nu) + 1, sizeof(float), "makeknots"); - if (nu->flagv & CU_NURB_CYCLIC) { - calcknots(nu->knotsv, nu->pntsv, nu->orderv, 0); /* cyclic should be uniform */ - makecyclicknots(nu->knotsv, nu->pntsv, nu->orderv); - } - else { - calcknots(nu->knotsv, nu->pntsv, nu->orderv, nu->flagv); - } + calcknots(nu->knotsv, nu->pntsv, nu->orderv, nu->flagv); } else { nu->knotsv = nullptr; diff --git a/source/blender/blenkernel/intern/spline_nurbs.cc b/source/blender/blenkernel/intern/spline_nurbs.cc index 719ba4b7ecd..5993b9a9a27 100644 --- a/source/blender/blenkernel/intern/spline_nurbs.cc +++ b/source/blender/blenkernel/intern/spline_nurbs.cc @@ -179,65 +179,35 @@ int NURBSpline::knots_size() const void NURBSpline::calculate_knots() const { const KnotsMode mode = this->knots_mode; - const int length = this->size(); const int order = order_; + const bool is_bezier = mode == NURBSpline::KnotsMode::Bezier; + const bool is_end_point = mode == NURBSpline::KnotsMode::EndPoint; + /* Inner knots are always repeated once except on Bezier case. */ + const int repeat_inner = is_bezier ? order - 1 : 1; + /* How many times to repeat 0.0 at the beginning of knot. */ + const int head = is_end_point && !is_cyclic_ ? order : (is_bezier ? order / 2 : 1); + /* Number of knots replicating widths of the starting knots. + * Covers both Cyclic and EndPoint cases. */ + const int tail = is_cyclic_ ? 2 * order - 1 : (is_end_point ? order : 0); knots_.resize(this->knots_size()); - MutableSpan knots = knots_; - if (mode == NURBSpline::KnotsMode::Normal || is_cyclic_) { - for (const int i : knots.index_range()) { - knots[i] = static_cast(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; - } - } - } + int r = head; + float current = 0.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; - } - } - } + for (const int i : IndexRange(knots.size() - tail)) { + knots[i] = current; + r--; + if (r == 0) { + current += 1.0; + r = repeat_inner; } + } - int c = order; - for (int i = b; i < this->knots_size(); i++) { - knots[i] = knots[i - 1] + (knots[c] - knots[c - 1]); - c--; - } + const int tail_index = knots.size() - tail; + for (const int i : IndexRange(tail)) { + knots[tail_index + i] = current + (knots[i] - knots[0]); } } diff --git a/source/blender/editors/curve/editcurve.c b/source/blender/editors/curve/editcurve.c index a034e4bb10e..a70bc1c0350 100644 --- a/source/blender/editors/curve/editcurve.c +++ b/source/blender/editors/curve/editcurve.c @@ -4953,19 +4953,22 @@ bool ed_editnurb_spin( if ((a & 1) == 0) { rotateflagNurb(editnurb, SELECT, cent, scalemat1); - weightflagNurb(editnurb, SELECT, 0.25 * M_SQRT2); + weightflagNurb(editnurb, SELECT, 0.5 * M_SQRT2); } else { rotateflagNurb(editnurb, SELECT, cent, scalemat2); - weightflagNurb(editnurb, SELECT, 4.0 / M_SQRT2); + weightflagNurb(editnurb, SELECT, 2.0 / M_SQRT2); } } if (ok) { LISTBASE_FOREACH (Nurb *, nu, editnurb) { if (ED_curve_nurb_select_check(v3d, nu)) { - nu->orderv = 4; - nu->flagv |= CU_NURB_CYCLIC; + nu->orderv = 3; + /* It is challenging to create a good approximation of a circle with uniform knots vector + * (which is forced in Blender for cyclic NURBS curves). Here a NURBS circle is constructed + * by connecting four Bezier arcs. */ + nu->flagv |= CU_NURB_CYCLIC | CU_NURB_BEZIER; BKE_nurb_knot_calc_v(nu); } } diff --git a/source/blender/editors/curve/editcurve_add.c b/source/blender/editors/curve/editcurve_add.c index 614805a70f5..daef4a21692 100644 --- a/source/blender/editors/curve/editcurve_add.c +++ b/source/blender/editors/curve/editcurve_add.c @@ -306,9 +306,9 @@ Nurb *ED_curve_add_nurbs_primitive( else if (cutype == CU_NURBS) { /* nurb */ nu->pntsu = 8; nu->pntsv = 1; - nu->orderu = 4; + nu->orderu = 3; nu->bp = (BPoint *)MEM_callocN(sizeof(BPoint) * nu->pntsu, "addNurbprim6"); - nu->flagu = CU_NURB_CYCLIC; + nu->flagu = CU_NURB_CYCLIC | CU_NURB_BEZIER; bp = nu->bp; for (a = 0; a < 8; a++) { @@ -322,7 +322,7 @@ Nurb *ED_curve_add_nurbs_primitive( bp->vec[2] += 0.25f * nurbcircle[a][1] * grid; } if (a & 1) { - bp->vec[3] = 0.25 * M_SQRT2; + bp->vec[3] = 0.5 * M_SQRT2; } else { bp->vec[3] = 1.0; -- cgit v1.2.3