diff options
-rw-r--r-- | source/blender/blenkernel/BKE_curves.h | 8 | ||||
-rw-r--r-- | source/blender/blenkernel/BKE_curves.hh | 156 | ||||
-rw-r--r-- | source/blender/blenkernel/CMakeLists.txt | 2 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/curves.cc | 171 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/curves_geometry.cc | 377 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/object.cc | 2 | ||||
-rw-r--r-- | source/blender/draw/intern/draw_cache_impl_curves.cc | 6 | ||||
-rw-r--r-- | source/blender/makesdna/DNA_curves_types.h | 40 | ||||
-rw-r--r-- | source/blender/makesrna/intern/rna_curves.c | 8 |
9 files changed, 651 insertions, 119 deletions
diff --git a/source/blender/blenkernel/BKE_curves.h b/source/blender/blenkernel/BKE_curves.h index 2cce15fbfd6..88bb1c67fd1 100644 --- a/source/blender/blenkernel/BKE_curves.h +++ b/source/blender/blenkernel/BKE_curves.h @@ -2,9 +2,11 @@ #pragma once +#include "DNA_curves_types.h" + /** \file * \ingroup bke - * \brief Low-level operations for curves. + * \brief Low-level operations for curves that cannot be defined in the C++ header yet. */ #ifdef __cplusplus @@ -23,14 +25,10 @@ void *BKE_curves_add(struct Main *bmain, const char *name); struct BoundBox *BKE_curves_boundbox_get(struct Object *ob); -void BKE_curves_update_customdata_pointers(struct Curves *curves); bool BKE_curves_customdata_required(struct Curves *curves, struct CustomDataLayer *layer); /* Depsgraph */ -struct Curves *BKE_curves_new_for_eval(const struct Curves *curves_src, - int totpoint, - int totcurve); struct Curves *BKE_curves_copy_for_eval(struct Curves *curves_src, bool reference); void BKE_curves_data_update(struct Depsgraph *depsgraph, diff --git a/source/blender/blenkernel/BKE_curves.hh b/source/blender/blenkernel/BKE_curves.hh new file mode 100644 index 00000000000..6bcbb0f6e66 --- /dev/null +++ b/source/blender/blenkernel/BKE_curves.hh @@ -0,0 +1,156 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +#include "BKE_curves.h" + +/** \file + * \ingroup bke + * \brief Low-level operations for curves. + */ + +#include <mutex> + +#include "BLI_float4x4.hh" +#include "BLI_index_mask.hh" +#include "BLI_math_vec_types.hh" +#include "BLI_span.hh" +#include "BLI_task.hh" +#include "BLI_vector.hh" +#include "BLI_virtual_array.hh" + +#include "BKE_attribute_access.hh" + +#include "FN_generic_virtual_array.hh" + +namespace blender::bke { + +/** + * Contains derived data, caches, and other information not saved in files, besides a few pointers + * to arrays that are kept in the non-runtime struct to avoid dereferencing this whenever they are + * accessed. + */ +class CurvesGeometryRuntime { + public: + /** Cache of evaluated positions. */ + mutable Vector<float3> evaluated_position_cache; + mutable std::mutex position_cache_mutex; + mutable bool position_cache_dirty = true; + + /** Direction of the spline at each evaluated point. */ + mutable Vector<float3> evaluated_tangents_cache; + mutable std::mutex tangent_cache_mutex; + mutable bool tangent_cache_dirty = true; + + /** Normal direction vectors for each evaluated point. */ + mutable Vector<float3> evaluated_normals_cache; + mutable std::mutex normal_cache_mutex; + mutable bool normal_cache_dirty = true; +}; + +/** + * A C++ class that wraps the DNA struct for better encapsulation and ease of use. It inherits + * directly from the struct rather than storing a pointer to avoid more complcated ownership + * handling. + */ +class CurvesGeometry : public ::CurvesGeometry { + public: + CurvesGeometry(); + /** + * Create curves with the given size. Only the position attribute is created, along with the + * offsets. + */ + CurvesGeometry(int point_size, int curve_size); + CurvesGeometry(const CurvesGeometry &other); + CurvesGeometry &operator=(const CurvesGeometry &other); + ~CurvesGeometry(); + + static CurvesGeometry &wrap(::CurvesGeometry &dna_struct) + { + CurvesGeometry *geometry = reinterpret_cast<CurvesGeometry *>(&dna_struct); + return *geometry; + } + static const CurvesGeometry &wrap(const ::CurvesGeometry &dna_struct) + { + const CurvesGeometry *geometry = reinterpret_cast<const CurvesGeometry *>(&dna_struct); + return *geometry; + } + + /* -------------------------------------------------------------------- + * Accessors. + */ + + int points_size() const; + int curves_size() const; + + /** + * The total number of points in the evaluated poly curve. + * This can depend on the resolution attribute if it exists. + */ + int evaluated_points_size() const; + + /** + * Access a range of indices of point data for a specific curve. + */ + IndexRange range_for_curve(int index) const; + + /** The type (#CurveType) of each curve, or potentially a single if all are the same type. */ + VArray<int8_t> curve_types() const; + /** Mutable access to curve types. Call #tag_topology_changed after changing any type. */ + MutableSpan<int8_t> curve_types(); + + MutableSpan<float3> positions(); + Span<float3> positions() const; + + /** + * Calculate the largest and smallest position values, only including control points + * (rather than evaluated points). The existing values of `min` and `max` are taken into account. + * + * \return Whether there are any points. If the curve is empty, the inputs will be unaffected. + */ + bool bounds_min_max(float3 &min, float3 &max) const; + + /** + * The index of the first point in every curve. The size of this span is one larger than the + * number of curves. Consider using #range_for_curve rather than using the offsets directly. + */ + Span<int> offsets() const; + MutableSpan<int> offsets(); + + /* -------------------------------------------------------------------- + * Operations. + */ + + /** + * Change the number of elements. New values for existing attributes should be properly + * initialized afterwards. + */ + void resize(int point_size, int curve_size); + + /** Call after deforming the position attribute. */ + void tag_positions_changed(); + /** + * Call after any operation that changes the topology + * (number of points, evaluated points, or the total count). + */ + void tag_topology_changed(); + /** Call after changing the "tilt" or "up" attributes. */ + void tag_normals_changed(); + + void translate(const float3 &translation); + void transform(const float4x4 &matrix); + + void update_customdata_pointers(); + + /* -------------------------------------------------------------------- + * Attributes. + */ + + fn::GVArray adapt_domain(const fn::GVArray &varray, + AttributeDomain from, + AttributeDomain to) const; +}; + +Curves *curves_new_nomain(int point_size, int curves_size); + +} // namespace blender::bke diff --git a/source/blender/blenkernel/CMakeLists.txt b/source/blender/blenkernel/CMakeLists.txt index bf720fa1341..78a145335b4 100644 --- a/source/blender/blenkernel/CMakeLists.txt +++ b/source/blender/blenkernel/CMakeLists.txt @@ -103,6 +103,7 @@ set(SRC intern/cryptomatte.cc intern/curve.cc intern/curves.cc + intern/curves_geometry.cc intern/curve_bevel.c intern/curve_convert.c intern/curve_decimate.c @@ -341,6 +342,7 @@ set(SRC BKE_cryptomatte.hh BKE_curve.h BKE_curves.h + BKE_curves.hh BKE_curve_to_mesh.hh BKE_curveprofile.h BKE_customdata.h diff --git a/source/blender/blenkernel/intern/curves.cc b/source/blender/blenkernel/intern/curves.cc index ccc20d5118a..e4a57ee1897 100644 --- a/source/blender/blenkernel/intern/curves.cc +++ b/source/blender/blenkernel/intern/curves.cc @@ -14,16 +14,18 @@ #include "DNA_material_types.h" #include "DNA_object_types.h" +#include "BLI_bounds.hh" #include "BLI_index_range.hh" #include "BLI_listbase.h" #include "BLI_math_base.h" #include "BLI_math_vector.hh" #include "BLI_rand.hh" +#include "BLI_span.hh" #include "BLI_string.h" #include "BLI_utildefines.h" #include "BKE_anim_data.h" -#include "BKE_curves.h" +#include "BKE_curves.hh" #include "BKE_customdata.h" #include "BKE_global.h" #include "BKE_idtype.h" @@ -44,11 +46,12 @@ using blender::float3; using blender::IndexRange; using blender::MutableSpan; using blender::RandomNumberGenerator; +using blender::Span; static const char *ATTR_POSITION = "position"; -static const char *ATTR_RADIUS = "radius"; static void curves_random(Curves *curves); +static void update_custom_data_pointers(Curves &curves); static void curves_init_data(ID *id) { @@ -57,50 +60,38 @@ static void curves_init_data(ID *id) MEMCPY_STRUCT_AFTER(curves, DNA_struct_default_get(Curves), id); - CustomData_reset(&curves->geometry.point_data); - CustomData_reset(&curves->geometry.curve_data); - - CustomData_add_layer_named(&curves->geometry.point_data, - CD_PROP_FLOAT3, - CD_CALLOC, - nullptr, - curves->geometry.point_size, - ATTR_POSITION); - CustomData_add_layer_named(&curves->geometry.point_data, - CD_PROP_FLOAT, - CD_CALLOC, - nullptr, - curves->geometry.point_size, - ATTR_RADIUS); - - BKE_curves_update_customdata_pointers(curves); + new (&curves->geometry) blender::bke::CurvesGeometry(); curves_random(curves); } static void curves_copy_data(Main *UNUSED(bmain), ID *id_dst, const ID *id_src, const int flag) { + using namespace blender; + Curves *curves_dst = (Curves *)id_dst; const Curves *curves_src = (const Curves *)id_src; curves_dst->mat = static_cast<Material **>(MEM_dupallocN(curves_src->mat)); - curves_dst->geometry.point_size = curves_src->geometry.point_size; - curves_dst->geometry.curve_size = curves_src->geometry.curve_size; + const bke::CurvesGeometry &src = bke::CurvesGeometry::wrap(curves_src->geometry); + bke::CurvesGeometry &dst = bke::CurvesGeometry::wrap(curves_dst->geometry); + + /* We need special handling here because the generic ID management code has already done a + * shallow copy from the source to the destination, and because the copy-on-write functionality + * isn't supported more generically yet. */ + + dst.point_size = src.point_size; + dst.curve_size = src.curve_size; const eCDAllocType alloc_type = (flag & LIB_ID_COPY_CD_REFERENCE) ? CD_REFERENCE : CD_DUPLICATE; - CustomData_copy(&curves_src->geometry.point_data, - &curves_dst->geometry.point_data, - CD_MASK_ALL, - alloc_type, - curves_dst->geometry.point_size); - CustomData_copy(&curves_src->geometry.curve_data, - &curves_dst->geometry.curve_data, - CD_MASK_ALL, - alloc_type, - curves_dst->geometry.curve_size); - BKE_curves_update_customdata_pointers(curves_dst); - - curves_dst->geometry.offsets = static_cast<int *>(MEM_dupallocN(curves_src->geometry.offsets)); + CustomData_copy(&src.point_data, &dst.point_data, CD_MASK_ALL, alloc_type, dst.point_size); + CustomData_copy(&src.curve_data, &dst.curve_data, CD_MASK_ALL, alloc_type, dst.curve_size); + + dst.curve_offsets = static_cast<int *>(MEM_dupallocN(src.curve_offsets)); + + dst.runtime = MEM_new<bke::CurvesGeometryRuntime>(__func__); + + dst.update_customdata_pointers(); curves_dst->batch_cache = nullptr; } @@ -110,12 +101,9 @@ static void curves_free_data(ID *id) Curves *curves = (Curves *)id; BKE_animdata_free(&curves->id, false); - BKE_curves_batch_cache_free(curves); - - CustomData_free(&curves->geometry.point_data, curves->geometry.point_size); - CustomData_free(&curves->geometry.curve_data, curves->geometry.curve_size); + blender::bke::CurvesGeometry::wrap(curves->geometry).~CurvesGeometry(); - MEM_SAFE_FREE(curves->geometry.offsets); + BKE_curves_batch_cache_free(curves); MEM_SAFE_FREE(curves->mat); } @@ -157,7 +145,7 @@ static void curves_blend_write(BlendWriter *writer, ID *id, const void *id_addre CD_MASK_ALL, &curves->id); - BLO_write_int32_array(writer, curves->geometry.curve_size + 1, curves->geometry.offsets); + BLO_write_int32_array(writer, curves->geometry.curve_size + 1, curves->geometry.curve_offsets); BLO_write_pointer_array(writer, curves->totcol, curves->mat); if (curves->adt) { @@ -182,9 +170,11 @@ static void curves_blend_read_data(BlendDataReader *reader, ID *id) /* Geometry */ CustomData_blend_read(reader, &curves->geometry.point_data, curves->geometry.point_size); CustomData_blend_read(reader, &curves->geometry.curve_data, curves->geometry.curve_size); - BKE_curves_update_customdata_pointers(curves); + update_custom_data_pointers(*curves); + + BLO_read_int32_array(reader, curves->geometry.curve_size + 1, &curves->geometry.curve_offsets); - BLO_read_int32_array(reader, curves->geometry.curve_size + 1, &curves->geometry.offsets); + curves->geometry.runtime = MEM_new<blender::bke::CurvesGeometryRuntime>(__func__); /* Materials */ BLO_read_pointer_array(reader, (void **)&curves->mat); @@ -236,34 +226,33 @@ IDTypeInfo IDType_ID_CV = { /*lib_override_apply_post */ nullptr, }; +static void update_custom_data_pointers(Curves &curves) +{ + blender::bke::CurvesGeometry::wrap(curves.geometry).update_customdata_pointers(); +} + static void curves_random(Curves *curves) { - CurvesGeometry &geometry = curves->geometry; const int numpoints = 8; - geometry.curve_size = 500; - - geometry.curve_size = 500; - geometry.point_size = geometry.curve_size * numpoints; + blender::bke::CurvesGeometry &geometry = blender::bke::CurvesGeometry::wrap(curves->geometry); + geometry = blender::bke::CurvesGeometry(500 * numpoints, 500); - curves->geometry.offsets = (int *)MEM_calloc_arrayN( - curves->geometry.curve_size + 1, sizeof(int), __func__); - CustomData_realloc(&geometry.point_data, geometry.point_size); - CustomData_realloc(&geometry.curve_data, geometry.curve_size); - BKE_curves_update_customdata_pointers(curves); + MutableSpan<int> offsets = geometry.offsets(); + MutableSpan<float3> positions = geometry.positions(); - MutableSpan<int> offsets{geometry.offsets, geometry.curve_size + 1}; - MutableSpan<float3> positions{(float3 *)geometry.position, geometry.point_size}; - MutableSpan<float> radii{geometry.radius, geometry.point_size}; + float *radius_data = (float *)CustomData_add_layer_named( + &geometry.point_data, CD_PROP_FLOAT, CD_DEFAULT, nullptr, geometry.point_size, "radius"); + MutableSpan<float> radii{radius_data, geometry.points_size()}; for (const int i : offsets.index_range()) { - geometry.offsets[i] = numpoints * i; + offsets[i] = numpoints * i; } RandomNumberGenerator rng; for (int i = 0; i < geometry.curve_size; i++) { - const IndexRange curve_range(offsets[i], offsets[i + 1] - offsets[i]); + const IndexRange curve_range = geometry.range_for_curve(i); MutableSpan<float3> curve_positions = positions.slice(curve_range); MutableSpan<float> curve_radii = radii.slice(curve_range); @@ -304,18 +293,13 @@ BoundBox *BKE_curves_boundbox_get(Object *ob) if (ob->runtime.bb == nullptr) { ob->runtime.bb = MEM_cnew<BoundBox>(__func__); - float min[3], max[3]; - INIT_MINMAX(min, max); - - float(*curves_co)[3] = curves->geometry.position; - float *curves_radius = curves->geometry.radius; - for (int a = 0; a < curves->geometry.point_size; a++) { - float *co = curves_co[a]; - float radius = (curves_radius) ? curves_radius[a] : 0.0f; - const float co_min[3] = {co[0] - radius, co[1] - radius, co[2] - radius}; - const float co_max[3] = {co[0] + radius, co[1] + radius, co[2] + radius}; - DO_MIN(co_min, min); - DO_MAX(co_max, max); + blender::bke::CurvesGeometry &geometry = blender::bke::CurvesGeometry::wrap(curves->geometry); + + float3 min(FLT_MAX); + float3 max(-FLT_MAX); + if (!geometry.bounds_min_max(min, max)) { + min = float3(-1); + max = float3(1); } BKE_boundbox_init_from_minmax(ob->runtime.bb, min, max); @@ -324,46 +308,11 @@ BoundBox *BKE_curves_boundbox_get(Object *ob) return ob->runtime.bb; } -void BKE_curves_update_customdata_pointers(Curves *curves) -{ - curves->geometry.position = (float(*)[3])CustomData_get_layer_named( - &curves->geometry.point_data, CD_PROP_FLOAT3, ATTR_POSITION); - curves->geometry.radius = (float *)CustomData_get_layer_named( - &curves->geometry.point_data, CD_PROP_FLOAT, ATTR_RADIUS); -} - bool BKE_curves_customdata_required(Curves *UNUSED(curves), CustomDataLayer *layer) { return layer->type == CD_PROP_FLOAT3 && STREQ(layer->name, ATTR_POSITION); } -/* Dependency Graph */ - -Curves *BKE_curves_new_for_eval(const Curves *curves_src, int totpoint, int totcurve) -{ - Curves *curves_dst = static_cast<Curves *>(BKE_id_new_nomain(ID_CV, nullptr)); - - STRNCPY(curves_dst->id.name, curves_src->id.name); - curves_dst->mat = static_cast<Material **>(MEM_dupallocN(curves_src->mat)); - curves_dst->totcol = curves_src->totcol; - - curves_dst->geometry.point_size = totpoint; - curves_dst->geometry.curve_size = totcurve; - CustomData_copy(&curves_src->geometry.point_data, - &curves_dst->geometry.point_data, - CD_MASK_ALL, - CD_CALLOC, - totpoint); - CustomData_copy(&curves_src->geometry.curve_data, - &curves_dst->geometry.curve_data, - CD_MASK_ALL, - CD_CALLOC, - totcurve); - BKE_curves_update_customdata_pointers(curves_dst); - - return curves_dst; -} - Curves *BKE_curves_copy_for_eval(Curves *curves_src, bool reference) { int flags = LIB_ID_COPY_LOCALIZE; @@ -414,7 +363,7 @@ static Curves *curves_evaluate_modifiers(struct Depsgraph *depsgraph, CD_PROP_FLOAT3, ATTR_POSITION, curves->geometry.point_size); - BKE_curves_update_customdata_pointers(curves); + update_custom_data_pointers(*curves); /* Created deformed coordinates array on demand. */ mti->deformVerts( @@ -457,3 +406,15 @@ void BKE_curves_batch_cache_free(Curves *curves) BKE_curves_batch_cache_free_cb(curves); } } + +namespace blender::bke { + +Curves *curves_new_nomain(const int point_size, const int curves_size) +{ + Curves *curves = static_cast<Curves *>(BKE_id_new_nomain(ID_CV, nullptr)); + CurvesGeometry &geometry = CurvesGeometry::wrap(curves->geometry); + geometry.resize(point_size, curves_size); + return curves; +} + +} // namespace blender::bke diff --git a/source/blender/blenkernel/intern/curves_geometry.cc b/source/blender/blenkernel/intern/curves_geometry.cc new file mode 100644 index 00000000000..dcd04a408fa --- /dev/null +++ b/source/blender/blenkernel/intern/curves_geometry.cc @@ -0,0 +1,377 @@ +/* + * 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. + */ + +/** \file + * \ingroup bke + */ + +#include "MEM_guardedalloc.h" + +#include "BLI_bounds.hh" + +#include "DNA_curves_types.h" + +#include "BKE_attribute_math.hh" +#include "BKE_curves.hh" + +namespace blender::bke { + +static const std::string ATTR_POSITION = "position"; +static const std::string ATTR_RADIUS = "radius"; +static const std::string ATTR_CURVE_TYPE = "curve_type"; + +/* -------------------------------------------------------------------- */ +/** \name Constructors/Destructor + * \{ */ + +CurvesGeometry::CurvesGeometry() : CurvesGeometry(0, 0) +{ +} + +CurvesGeometry::CurvesGeometry(const int point_size, const int curve_size) +{ + this->point_size = point_size; + this->curve_size = curve_size; + CustomData_reset(&this->point_data); + CustomData_reset(&this->curve_data); + + CustomData_add_layer_named(&this->point_data, + CD_PROP_FLOAT3, + CD_DEFAULT, + nullptr, + this->point_size, + ATTR_POSITION.c_str()); + + this->curve_offsets = (int *)MEM_calloc_arrayN(this->curve_size + 1, sizeof(int), __func__); + + this->update_customdata_pointers(); + + this->runtime = MEM_new<CurvesGeometryRuntime>(__func__); +} + +static void copy_curves_geometry(CurvesGeometry &dst, const CurvesGeometry &src) +{ + dst.point_size = src.point_size; + dst.curve_size = src.curve_size; + CustomData_copy(&src.point_data, &dst.point_data, CD_MASK_ALL, CD_DUPLICATE, dst.point_size); + CustomData_copy(&src.curve_data, &dst.curve_data, CD_MASK_ALL, CD_DUPLICATE, dst.curve_size); + + MEM_SAFE_FREE(dst.curve_offsets); + dst.curve_offsets = (int *)MEM_calloc_arrayN(dst.point_size + 1, sizeof(int), __func__); + dst.offsets().copy_from(src.offsets()); + + dst.tag_topology_changed(); + + dst.update_customdata_pointers(); +} + +CurvesGeometry::CurvesGeometry(const CurvesGeometry &other) + : CurvesGeometry(other.point_size, other.curve_size) +{ + copy_curves_geometry(*this, other); +} + +CurvesGeometry &CurvesGeometry::operator=(const CurvesGeometry &other) +{ + if (this != &other) { + copy_curves_geometry(*this, other); + } + return *this; +} + +CurvesGeometry::~CurvesGeometry() +{ + CustomData_free(&this->point_data, this->point_size); + CustomData_free(&this->curve_data, this->curve_size); + MEM_SAFE_FREE(this->curve_offsets); + MEM_delete(this->runtime); + this->runtime = nullptr; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Accessors + * \{ */ + +int CurvesGeometry::points_size() const +{ + return this->point_size; +} +int CurvesGeometry::curves_size() const +{ + return this->curve_size; +} +int CurvesGeometry::evaluated_points_size() const +{ + /* TODO: Implement when there are evaluated points. */ + return 0; +} + +IndexRange CurvesGeometry::range_for_curve(const int index) const +{ + const int offset = this->curve_offsets[index]; + const int offset_next = this->curve_offsets[index + 1]; + return {offset, offset_next - offset}; +} + +VArray<int8_t> CurvesGeometry::curve_types() const +{ + if (const int8_t *data = (const int8_t *)CustomData_get_layer_named( + &this->curve_data, CD_PROP_INT8, ATTR_CURVE_TYPE.c_str())) { + return VArray<int8_t>::ForSpan({data, this->curve_size}); + } + return VArray<int8_t>::ForSingle(CURVE_TYPE_CATMULL_ROM, this->curve_size); +} + +MutableSpan<int8_t> CurvesGeometry::curve_types() +{ + int8_t *data = (int8_t *)CustomData_add_layer_named(&this->curve_data, + CD_PROP_INT8, + CD_CALLOC, + nullptr, + this->curve_size, + ATTR_CURVE_TYPE.c_str()); + BLI_assert(data != nullptr); + return {data, this->curve_size}; +} + +MutableSpan<float3> CurvesGeometry::positions() +{ + CustomData_duplicate_referenced_layer(&this->point_data, CD_PROP_FLOAT3, this->point_size); + this->update_customdata_pointers(); + return {(float3 *)this->position, this->point_size}; +} +Span<float3> CurvesGeometry::positions() const +{ + return {(const float3 *)this->position, this->point_size}; +} + +MutableSpan<int> CurvesGeometry::offsets() +{ + return {this->curve_offsets, this->curve_size + 1}; +} +Span<int> CurvesGeometry::offsets() const +{ + return {this->curve_offsets, this->curve_size + 1}; +} + +void CurvesGeometry::resize(const int point_size, const int curve_size) +{ + if (point_size != this->point_size) { + CustomData_realloc(&this->point_data, point_size); + this->point_size = point_size; + } + if (curve_size != this->curve_size) { + CustomData_realloc(&this->curve_data, curve_size); + this->curve_size = curve_size; + this->curve_offsets = (int *)MEM_reallocN(this->curve_offsets, sizeof(int) * (curve_size + 1)); + } + this->tag_topology_changed(); + this->update_customdata_pointers(); +} + +void CurvesGeometry::tag_positions_changed() +{ + this->runtime->position_cache_dirty = true; + this->runtime->tangent_cache_dirty = true; + this->runtime->normal_cache_dirty = true; +} +void CurvesGeometry::tag_topology_changed() +{ + this->runtime->position_cache_dirty = true; + this->runtime->tangent_cache_dirty = true; + this->runtime->normal_cache_dirty = true; +} +void CurvesGeometry::tag_normals_changed() +{ + this->runtime->normal_cache_dirty = true; +} + +void CurvesGeometry::translate(const float3 &translation) +{ + MutableSpan<float3> positions = this->positions(); + threading::parallel_for(positions.index_range(), 2048, [&](const IndexRange range) { + for (float3 &position : positions.slice(range)) { + position += translation; + } + }); +} + +void CurvesGeometry::transform(const float4x4 &matrix) +{ + MutableSpan<float3> positions = this->positions(); + threading::parallel_for(positions.index_range(), 1024, [&](const IndexRange range) { + for (float3 &position : positions.slice(range)) { + position = matrix * position; + } + }); +} + +static std::optional<bounds::MinMaxResult<float3>> curves_bounds(const CurvesGeometry &curves) +{ + Span<float3> positions = curves.positions(); + if (curves.radius) { + Span<float> radii{curves.radius, curves.points_size()}; + return bounds::min_max_with_radii(positions, radii); + } + return bounds::min_max(positions); +} + +bool CurvesGeometry::bounds_min_max(float3 &min, float3 &max) const +{ + const std::optional<bounds::MinMaxResult<float3>> bounds = curves_bounds(*this); + if (!bounds) { + return false; + } + min = math::min(bounds->min, min); + max = math::max(bounds->max, max); + return true; +} + +void CurvesGeometry::update_customdata_pointers() +{ + this->position = (float(*)[3])CustomData_get_layer_named( + &this->point_data, CD_PROP_FLOAT3, ATTR_POSITION.c_str()); + this->radius = (float *)CustomData_get_layer_named( + &this->point_data, CD_PROP_FLOAT, ATTR_RADIUS.c_str()); + this->curve_type = (int8_t *)CustomData_get_layer_named( + &this->point_data, CD_PROP_INT8, ATTR_CURVE_TYPE.c_str()); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Domain Interpolation + * \{ */ + +/** + * Mix together all of a curve's control point values. + * + * \note Theoretically this interpolation does not need to compute all values at once. + * However, doing that makes the implementation simpler, and this can be optimized in the future if + * only some values are required. + */ +template<typename T> +static void adapt_curve_domain_point_to_curve_impl(const CurvesGeometry &curves, + const VArray<T> &old_values, + MutableSpan<T> r_values) +{ + attribute_math::DefaultMixer<T> mixer(r_values); + for (const int i_curve : IndexRange(curves.curves_size())) { + for (const int i_point : curves.range_for_curve(i_curve)) { + mixer.mix_in(i_curve, old_values[i_point]); + } + } + mixer.finalize(); +} + +/** + * A curve is selected if all of its control points were selected. + * + * \note Theoretically this interpolation does not need to compute all values at once. + * However, doing that makes the implementation simpler, and this can be optimized in the future if + * only some values are required. + */ +template<> +void adapt_curve_domain_point_to_curve_impl(const CurvesGeometry &curves, + const VArray<bool> &old_values, + MutableSpan<bool> r_values) +{ + r_values.fill(true); + for (const int i_curve : IndexRange(curves.curves_size())) { + for (const int i_point : curves.range_for_curve(i_curve)) { + if (!old_values[i_point]) { + r_values[i_curve] = false; + break; + } + } + } +} + +static GVArray adapt_curve_domain_point_to_curve(const CurvesGeometry &curves, + const GVArray &varray) +{ + GVArray new_varray; + attribute_math::convert_to_static_type(varray.type(), [&](auto dummy) { + using T = decltype(dummy); + if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) { + Array<T> values(curves.curves_size()); + adapt_curve_domain_point_to_curve_impl<T>(curves, varray.typed<T>(), values); + new_varray = VArray<T>::ForContainer(std::move(values)); + } + }); + return new_varray; +} + +/** + * Copy the value from a curve to all of its points. + * + * \note Theoretically this interpolation does not need to compute all values at once. + * However, doing that makes the implementation simpler, and this can be optimized in the future if + * only some values are required. + */ +template<typename T> +static void adapt_curve_domain_curve_to_point_impl(const CurvesGeometry &curves, + const VArray<T> &old_values, + MutableSpan<T> r_values) +{ + for (const int i_curve : IndexRange(curves.curves_size())) { + r_values.slice(curves.range_for_curve(i_curve)).fill(old_values[i_curve]); + } +} + +static GVArray adapt_curve_domain_curve_to_point(const CurvesGeometry &curves, + const GVArray &varray) +{ + GVArray new_varray; + attribute_math::convert_to_static_type(varray.type(), [&](auto dummy) { + using T = decltype(dummy); + Array<T> values(curves.points_size()); + adapt_curve_domain_curve_to_point_impl<T>(curves, varray.typed<T>(), values); + new_varray = VArray<T>::ForContainer(std::move(values)); + }); + return new_varray; +} + +fn::GVArray CurvesGeometry::adapt_domain(const fn::GVArray &varray, + const AttributeDomain from, + const AttributeDomain to) const +{ + if (!varray) { + return {}; + } + if (varray.is_empty()) { + return {}; + } + if (from == to) { + return varray; + } + + if (from == ATTR_DOMAIN_POINT && to == ATTR_DOMAIN_CURVE) { + return adapt_curve_domain_point_to_curve(*this, varray); + } + if (from == ATTR_DOMAIN_CURVE && to == ATTR_DOMAIN_POINT) { + return adapt_curve_domain_curve_to_point(*this, varray); + } + + BLI_assert_unreachable(); + return {}; +} + +/** \} */ + +} // namespace blender::bke diff --git a/source/blender/blenkernel/intern/object.cc b/source/blender/blenkernel/intern/object.cc index 3a8d7d6bc8a..a381fe2c2ff 100644 --- a/source/blender/blenkernel/intern/object.cc +++ b/source/blender/blenkernel/intern/object.cc @@ -73,7 +73,7 @@ #include "BKE_constraint.h" #include "BKE_crazyspace.h" #include "BKE_curve.h" -#include "BKE_curves.h" +#include "BKE_curves.hh" #include "BKE_deform.h" #include "BKE_displist.h" #include "BKE_duplilist.h" diff --git a/source/blender/draw/intern/draw_cache_impl_curves.cc b/source/blender/draw/intern/draw_cache_impl_curves.cc index a779c694cd2..2153b674463 100644 --- a/source/blender/draw/intern/draw_cache_impl_curves.cc +++ b/source/blender/draw/intern/draw_cache_impl_curves.cc @@ -22,7 +22,7 @@ #include "DNA_curves_types.h" #include "DNA_object_types.h" -#include "BKE_curves.h" +#include "BKE_curves.hh" #include "GPU_batch.h" #include "GPU_material.h" @@ -133,7 +133,7 @@ static void curves_batch_cache_fill_segments_proc_pos(Curves *curves, { /* TODO: use hair radius layer if available. */ const int curve_size = curves->geometry.curve_size; - Span<int> offsets{curves->geometry.offsets, curves->geometry.curve_size + 1}; + Span<int> offsets{curves->geometry.curve_offsets, curves->geometry.curve_size + 1}; Span<float3> positions{(float3 *)curves->geometry.position, curves->geometry.point_size}; @@ -216,7 +216,7 @@ static void curves_batch_cache_fill_strands_data(Curves *curves, GPUVertBufRaw *seg_step) { const int curve_size = curves->geometry.curve_size; - Span<int> offsets{curves->geometry.offsets, curves->geometry.curve_size + 1}; + Span<int> offsets{curves->geometry.curve_offsets, curves->geometry.curve_size + 1}; for (const int i : IndexRange(curve_size)) { const IndexRange curve_range(offsets[i], offsets[i + 1] - offsets[i]); diff --git a/source/blender/makesdna/DNA_curves_types.h b/source/blender/makesdna/DNA_curves_types.h index 03a587c896b..c45de832e3c 100644 --- a/source/blender/makesdna/DNA_curves_types.h +++ b/source/blender/makesdna/DNA_curves_types.h @@ -13,6 +13,33 @@ extern "C" { #endif +#ifdef __cplusplus +namespace blender::bke { +class CurvesGeometryRuntime; +} // namespace blender::bke +using CurvesGeometryRuntimeHandle = blender::bke::CurvesGeometryRuntime; +#else +typedef struct CurvesGeometryRuntimeHandle CurvesGeometryRuntimeHandle; +#endif + +typedef enum CurveType { + CURVE_TYPE_CATMULL_ROM = 0, + CURVE_TYPE_POLY = 1, + CURVE_TYPE_BEZIER = 2, + CURVE_TYPE_NURBS = 3, +} CurveType; + +typedef enum HandleType { + /** The handle can be moved anywhere, and doesn't influence the point's other handle. */ + BEZIER_HANDLE_FREE = 0, + /** The location is automatically calculated to be smooth. */ + BEZIER_HANDLE_AUTO = 1, + /** The location is calculated to point to the next/previous control point. */ + BEZIER_HANDLE_VECTOR = 2, + /** The location is constrained to point in the opposite direction as the other handle. */ + BEZIER_HANDLE_ALIGN = 3, +} HandleType; + /** * A reusable data structure for geometry consisting of many curves. All control point data is * stored contiguously for better efficiency. Data for each curve is stored as a slice of the @@ -34,13 +61,19 @@ typedef struct CurvesGeometry { float *radius; /** + * The type of each curve. #CurveType. + * \note This data is owned by #curve_data. + */ + int8_t *curve_type; + + /** * The start index of each curve in the point data. The size of each curve can be calculated by * subtracting the offset from the next offset. That is valid even for the last curve because * this array is allocated with a length one larger than the number of splines. * * \note This is *not* stored in #CustomData because its size is one larger than #curve_data. */ - int *offsets; + int *curve_offsets; /** * All attributes stored on control points (#ATTR_DOMAIN_POINT). @@ -60,6 +93,11 @@ typedef struct CurvesGeometry { * The number of curves in the data-block. */ int curve_size; + + /** + * Runtime data for curves, stored as a pointer to allow defining this as a C++ class. + */ + CurvesGeometryRuntimeHandle *runtime; } CurvesGeometry; typedef struct Curves { diff --git a/source/blender/makesrna/intern/rna_curves.c b/source/blender/makesrna/intern/rna_curves.c index 8e7fb415c7d..1552a7ddbfb 100644 --- a/source/blender/makesrna/intern/rna_curves.c +++ b/source/blender/makesrna/intern/rna_curves.c @@ -43,7 +43,7 @@ static void rna_Curves_curve_offset_data_begin(CollectionPropertyIterator *iter, { const Curves *curves = rna_curves(ptr); rna_iterator_array_begin(iter, - (void *)curves->geometry.offsets, + (void *)curves->geometry.curve_offsets, sizeof(int), curves->geometry.curve_size + 1, false, @@ -95,7 +95,7 @@ static char *rna_CurvePoint_path(PointerRNA *ptr) static int rna_CurveSlice_index_get(PointerRNA *ptr) { Curves *curves = rna_curves(ptr); - return (int)((int *)ptr->data - curves->geometry.offsets); + return (int)((int *)ptr->data - curves->geometry.curve_offsets); } static char *rna_CurveSlice_path(PointerRNA *ptr) @@ -220,7 +220,7 @@ static void rna_def_curves(BlenderRNA *brna) /* Point and Curve RNA API helpers. */ prop = RNA_def_property(srna, "curves", PROP_COLLECTION, PROP_NONE); - RNA_def_property_collection_sdna(prop, NULL, "geometry.offsets", "geometry.curve_size"); + RNA_def_property_collection_sdna(prop, NULL, "geometry.curve_offsets", "geometry.curve_size"); RNA_def_property_struct_type(prop, "CurveSlice"); RNA_def_property_ui_text(prop, "Curves", "All curves in the data-block"); @@ -243,7 +243,7 @@ static void rna_def_curves(BlenderRNA *brna) RNA_define_verify_sdna(1); prop = RNA_def_property(srna, "curve_offset_data", PROP_COLLECTION, PROP_NONE); - RNA_def_property_collection_sdna(prop, NULL, "geometry.offsets", NULL); + RNA_def_property_collection_sdna(prop, NULL, "geometry.curve_offsets", NULL); RNA_def_property_struct_type(prop, "IntAttributeValue"); RNA_def_property_collection_funcs(prop, "rna_Curves_curve_offset_data_begin", |