Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHans Goudey <h.goudey@me.com>2022-02-16 20:32:37 +0300
committerHans Goudey <h.goudey@me.com>2022-02-16 20:32:37 +0300
commitc324cf153924ae2ec5b7c59eabff71652847aeae (patch)
treef12f07ed61b018481e4bc45a7687567c48c952aa /source/blender/blenkernel
parent5b73017ddb679bb050fe13e4d9e3e5b04ea559b9 (diff)
Curves: Further implementation of new curves data structure
The general idea here is to wrap the `CurvesGeometry` DNA struct with a C++ class that can do most of the heavy lifting for the curve geometry. Using a C++ class allows easier ways to group methods, easier const correctness, and code that's more readable and faster to write. This way, it works much more like a version of `CurveEval` that uses more efficient attribute storage. This commit adds the structure of some yet-to-be-implemented code, the largest thing being mutexes and vectors meant to hold lazily calculated evaluated positions, tangents, and normals. That part might change slightly, but it's helpful to be able to see the direction this commit is aiming in. In particular, the inherently single-threaded accumulated lengths and Bezier evaluated point offsets might be cached. Ref T95355 Differential Revision: https://developer.blender.org/D14054
Diffstat (limited to 'source/blender/blenkernel')
-rw-r--r--source/blender/blenkernel/BKE_curves.h8
-rw-r--r--source/blender/blenkernel/BKE_curves.hh156
-rw-r--r--source/blender/blenkernel/CMakeLists.txt2
-rw-r--r--source/blender/blenkernel/intern/curves.cc171
-rw-r--r--source/blender/blenkernel/intern/curves_geometry.cc377
-rw-r--r--source/blender/blenkernel/intern/object.cc2
6 files changed, 605 insertions, 111 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"