2022-02-17 16:47:37 +11:00
|
|
|
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
2022-02-16 11:32:37 -06:00
|
|
|
|
|
|
|
|
/** \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";
|
2022-02-28 17:20:37 -05:00
|
|
|
static const std::string ATTR_CYCLIC = "cyclic";
|
2022-02-16 11:32:37 -06:00
|
|
|
|
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
|
/** \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__);
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-17 09:04:58 -06:00
|
|
|
/**
|
|
|
|
|
* \note Expects `dst` to be initialized, since the original attributes must be freed.
|
|
|
|
|
*/
|
2022-02-16 11:32:37 -06:00
|
|
|
static void copy_curves_geometry(CurvesGeometry &dst, const CurvesGeometry &src)
|
|
|
|
|
{
|
2022-02-17 09:04:58 -06:00
|
|
|
CustomData_free(&dst.point_data, dst.point_size);
|
|
|
|
|
CustomData_free(&dst.curve_data, dst.curve_size);
|
2022-02-16 11:32:37 -06:00
|
|
|
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;
|
|
|
|
|
}
|
2022-02-23 08:54:35 -05:00
|
|
|
IndexRange CurvesGeometry::points_range() const
|
|
|
|
|
{
|
|
|
|
|
return IndexRange(this->points_size());
|
|
|
|
|
}
|
|
|
|
|
IndexRange CurvesGeometry::curves_range() const
|
|
|
|
|
{
|
|
|
|
|
return IndexRange(this->curves_size());
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-16 11:32:37 -06:00
|
|
|
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());
|
|
|
|
|
return {data, this->curve_size};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
MutableSpan<float3> CurvesGeometry::positions()
|
|
|
|
|
{
|
2022-02-21 17:06:17 -05:00
|
|
|
this->position = (float(*)[3])CustomData_duplicate_referenced_layer_named(
|
|
|
|
|
&this->point_data, CD_PROP_FLOAT3, ATTR_POSITION.c_str(), this->point_size);
|
2022-02-16 11:32:37 -06:00
|
|
|
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};
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-28 17:20:37 -05:00
|
|
|
VArray<bool> CurvesGeometry::cyclic() const
|
|
|
|
|
{
|
|
|
|
|
const bool *data = (const bool *)CustomData_get_layer_named(
|
|
|
|
|
&this->curve_data, CD_PROP_INT8, ATTR_CURVE_TYPE.c_str());
|
|
|
|
|
if (data != nullptr) {
|
|
|
|
|
return VArray<bool>::ForSpan(Span(data, this->curve_size));
|
|
|
|
|
}
|
|
|
|
|
return VArray<bool>::ForSingle(false, this->curve_size);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
MutableSpan<bool> CurvesGeometry::cyclic()
|
|
|
|
|
{
|
2022-03-07 18:58:04 -06:00
|
|
|
bool *data = (bool *)CustomData_duplicate_referenced_layer_named(
|
|
|
|
|
&this->curve_data, CD_PROP_BOOL, ATTR_CYCLIC.c_str(), this->curve_size);
|
|
|
|
|
if (data != nullptr) {
|
|
|
|
|
return {data, this->curve_size};
|
|
|
|
|
}
|
|
|
|
|
data = (bool *)CustomData_add_layer_named(
|
2022-02-28 17:20:37 -05:00
|
|
|
&this->curve_data, CD_PROP_BOOL, CD_CALLOC, nullptr, this->curve_size, ATTR_CYCLIC.c_str());
|
|
|
|
|
return {data, this->curve_size};
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-16 11:32:37 -06:00
|
|
|
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
|