Changeset View
Standalone View
source/blender/blenkernel/intern/curves_geometry.cc
| /* SPDX-License-Identifier: GPL-2.0-or-later */ | /* SPDX-License-Identifier: GPL-2.0-or-later */ | ||||
| /** \file | /** \file | ||||
| * \ingroup bke | * \ingroup bke | ||||
| */ | */ | ||||
| #include <mutex> | #include <mutex> | ||||
| #include <utility> | #include <utility> | ||||
| #include "MEM_guardedalloc.h" | #include "MEM_guardedalloc.h" | ||||
| #include "BLI_bounds.hh" | #include "BLI_bounds.hh" | ||||
| #include "BLI_index_mask_ops.hh" | #include "BLI_index_mask_ops.hh" | ||||
| #include "BLI_length_parameterize.hh" | |||||
| #include "DNA_curves_types.h" | #include "DNA_curves_types.h" | ||||
| #include "BKE_attribute_math.hh" | #include "BKE_attribute_math.hh" | ||||
| #include "BKE_curves.hh" | #include "BKE_curves.hh" | ||||
| namespace blender::bke { | namespace blender::bke { | ||||
| ▲ Show 20 Lines • Show All 694 Lines • ▼ Show 20 Lines | case CURVE_TYPE_NURBS: | ||||
| this->nurbs_weights().slice(points), | this->nurbs_weights().slice(points), | ||||
| src, | src, | ||||
| dst); | dst); | ||||
| return; | return; | ||||
| } | } | ||||
| BLI_assert_unreachable(); | BLI_assert_unreachable(); | ||||
| } | } | ||||
| IndexRange CurvesGeometry::lengths_range_for_curve(const int curve_index, const bool cyclic) const | |||||
| { | |||||
| BLI_assert(cyclic == this->cyclic()[curve_index]); | |||||
| const IndexRange points = this->evaluated_points_for_curve(curve_index); | |||||
JacquesLucke: While the interface is generally fine, i's probably not very efficient. Accessing `->cyclic()`… | |||||
Done Inline ActionsYeah. It's definitely not idea, especially with no caching of name lookups in CustomData. Adding cyclic as an argument doesn't feel great either though, since that allows the user to potentially access bad values for those last floats. I wonder if we can make ->cyclic() fast enough that calling it here isn't unreasonable. Here are two more options though: A function in the bke::curves namespace namespace blender::bke::curves {
IndexRange lengths_range_for_curve(IndexRange points, int curve_index, bool cyclic)
{
return ...
}I'm not sure about that though. The other stuff in the bke::curves namespace seems almost general enough that it could move to blenlib, but this is a bit more specific to CurvesGeometry. A static method on CurvesGeometry static CurvesGeometry::lengths_range_for_curve(IndexRange points, int curve_index, bool cyclic
{
return ...
}I think I prefer this method? HooglyBoogly: Yeah. It's definitely not idea, especially with no caching of name lookups in `CustomData`. | |||||
Done Inline ActionsI think passing in some redundant data for performance reasons is fine. The function should assert that the passed in data is correct. A comment can mention that this data is passed in for performance reasons. Trying to make cyclic() fast enough for this use case would be an optimization at the wrong end for me. There shouldn't be a need to construct a new VArray for every curve. JacquesLucke: I think passing in some redundant data for performance reasons is fine. The function should… | |||||
| const int start = points.start() + curve_index; | |||||
| const int size = curves::curve_segment_size(points.size(), cyclic); | |||||
| BLI_assert(size == length_parameterize::lengths_num(points.size(), cyclic)); | |||||
| return {start, size}; | |||||
| } | |||||
| void CurvesGeometry::ensure_evaluated_lengths() const | |||||
| { | |||||
| if (!this->runtime->length_cache_dirty) { | |||||
| return; | |||||
| } | |||||
| /* A double checked lock. */ | |||||
| std::scoped_lock lock{this->runtime->length_cache_mutex}; | |||||
| if (!this->runtime->length_cache_dirty) { | |||||
| return; | |||||
| } | |||||
| threading::isolate_task([&]() { | |||||
| /* Use an extra length value for the final cyclic segment for a consistent size | |||||
Done Inline ActionsA comment for why this is the total_size what be good. JacquesLucke: A comment for why this is the `total_size` what be good. | |||||
| * (see comment on #evaluated_length_cache). */ | |||||
| const int total_size = this->evaluated_points_num() + this->curves_num(); | |||||
| this->runtime->evaluated_length_cache.resize(total_size); | |||||
| MutableSpan<float> evaluated_lengths = this->runtime->evaluated_length_cache; | |||||
| Span<float3> evaluated_positions = this->evaluated_positions(); | |||||
| VArray<bool> curves_cyclic = this->cyclic(); | |||||
| threading::parallel_for(this->curves_range(), 128, [&](IndexRange curves_range) { | |||||
| for (const int curve_index : curves_range) { | |||||
| const bool cyclic = curves_cyclic[curve_index]; | |||||
| const IndexRange evaluated_points = this->evaluated_points_for_curve(curve_index); | |||||
| if (UNLIKELY(evaluated_points.is_empty())) { | |||||
| continue; | |||||
| } | |||||
| const IndexRange lengths_range = this->lengths_range_for_curve(curve_index, cyclic); | |||||
| length_parameterize::accumulate_lengths(evaluated_positions.slice(evaluated_points), | |||||
| cyclic, | |||||
| evaluated_lengths.slice(lengths_range)); | |||||
| } | |||||
| }); | |||||
| }); | |||||
| this->runtime->length_cache_dirty = false; | |||||
| } | |||||
| Span<float> CurvesGeometry::evaluated_lengths_for_curve(const int curve_index, | |||||
| const bool cyclic) const | |||||
| { | |||||
| BLI_assert(!this->runtime->length_cache_dirty); | |||||
| const IndexRange range = this->lengths_range_for_curve(curve_index, cyclic); | |||||
| return this->runtime->evaluated_length_cache.as_span().slice(range); | |||||
| } | |||||
| /** \} */ | /** \} */ | ||||
| /* -------------------------------------------------------------------- */ | /* -------------------------------------------------------------------- */ | ||||
| /** \name Operations | /** \name Operations | ||||
| * \{ */ | * \{ */ | ||||
| void CurvesGeometry::resize(const int points_num, const int curves_num) | void CurvesGeometry::resize(const int points_num, const int curves_num) | ||||
| { | { | ||||
| Show All 10 Lines | void CurvesGeometry::resize(const int points_num, const int curves_num) | ||||
| this->update_customdata_pointers(); | this->update_customdata_pointers(); | ||||
| } | } | ||||
| void CurvesGeometry::tag_positions_changed() | void CurvesGeometry::tag_positions_changed() | ||||
| { | { | ||||
| this->runtime->position_cache_dirty = true; | this->runtime->position_cache_dirty = true; | ||||
| this->runtime->tangent_cache_dirty = true; | this->runtime->tangent_cache_dirty = true; | ||||
| this->runtime->normal_cache_dirty = true; | this->runtime->normal_cache_dirty = true; | ||||
| this->runtime->length_cache_dirty = true; | |||||
| } | } | ||||
| void CurvesGeometry::tag_topology_changed() | void CurvesGeometry::tag_topology_changed() | ||||
| { | { | ||||
| this->runtime->position_cache_dirty = true; | this->runtime->position_cache_dirty = true; | ||||
| this->runtime->tangent_cache_dirty = true; | this->runtime->tangent_cache_dirty = true; | ||||
| this->runtime->normal_cache_dirty = true; | this->runtime->normal_cache_dirty = true; | ||||
| this->runtime->offsets_cache_dirty = true; | this->runtime->offsets_cache_dirty = true; | ||||
| this->runtime->nurbs_basis_cache_dirty = true; | this->runtime->nurbs_basis_cache_dirty = true; | ||||
| this->runtime->length_cache_dirty = true; | |||||
| } | } | ||||
| void CurvesGeometry::tag_normals_changed() | void CurvesGeometry::tag_normals_changed() | ||||
| { | { | ||||
| this->runtime->normal_cache_dirty = true; | this->runtime->normal_cache_dirty = true; | ||||
| } | } | ||||
| static void translate_positions(MutableSpan<float3> positions, const float3 &translation) | static void translate_positions(MutableSpan<float3> positions, const float3 &translation) | ||||
| { | { | ||||
| ▲ Show 20 Lines • Show All 424 Lines • Show Last 20 Lines | |||||
While the interface is generally fine, i's probably not very efficient. Accessing ->cyclic() for every curve adds a lot of overhead.