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>2021-05-03 20:29:17 +0300
committerHans Goudey <h.goudey@me.com>2021-05-03 20:29:17 +0300
commit8216b759e9557c786e517aa64eee424fed0ac742 (patch)
tree561a8ed066e89a700fdbf6455b76e4007b132e5f /source/blender/blenkernel/intern/spline_nurbs.cc
parentc9d81678d7dd7da0ba8d2111f9575d16f00e6a5b (diff)
Geometry Nodes: Initial basic curve data support
This patch adds initial curve support to geometry nodes. Currently there is only one node available, the "Curve to Mesh" node, T87428. However, the aim of the changes here is larger than just supporting curve data in nodes-- it also uses the opportunity to add better spline data structures, intended to replace the existing curve evaluation code. The curve code in Blender is quite old, and it's generally regarded as some of the messiest, hardest-to-understand code as well. The classes in `BKE_spline.hh` aim to be faster, more extensible, and much more easily understandable. Further explanation can be found in comments in that file. Initial builtin spline attributes are supported-- reading and writing from the `cyclic` and `resolution` attributes works with any of the attribute nodes. Also, only Z-up normal calculation is implemented at the moment, and tilts do not apply yet. **Limitations** - For now, you must bring curves into the node tree with an "Object Info" node. Changes to the curve modifier stack will come later. - Converting to a mesh is necessary to visualize the curve data. Further progress can be tracked in: T87245 Higher level design document: https://wiki.blender.org/wiki/Modules/Physics_Nodes/Projects/EverythingNodes/CurveNodes Differential Revision: https://developer.blender.org/D11091
Diffstat (limited to 'source/blender/blenkernel/intern/spline_nurbs.cc')
-rw-r--r--source/blender/blenkernel/intern/spline_nurbs.cc417
1 files changed, 417 insertions, 0 deletions
diff --git a/source/blender/blenkernel/intern/spline_nurbs.cc b/source/blender/blenkernel/intern/spline_nurbs.cc
new file mode 100644
index 00000000000..37d1232cfeb
--- /dev/null
+++ b/source/blender/blenkernel/intern/spline_nurbs.cc
@@ -0,0 +1,417 @@
+/*
+ * 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.
+ */
+
+#include "BLI_array.hh"
+#include "BLI_span.hh"
+#include "BLI_virtual_array.hh"
+
+#include "BKE_attribute_math.hh"
+#include "BKE_spline.hh"
+
+using blender::Array;
+using blender::float3;
+using blender::IndexRange;
+using blender::MutableSpan;
+using blender::Span;
+
+SplinePtr NURBSpline::copy() const
+{
+ return std::make_unique<NURBSpline>(*this);
+}
+
+int NURBSpline::size() const
+{
+ const int size = positions_.size();
+ BLI_assert(size == radii_.size());
+ BLI_assert(size == tilts_.size());
+ BLI_assert(size == weights_.size());
+ return size;
+}
+
+int NURBSpline::resolution() const
+{
+ return resolution_;
+}
+
+void NURBSpline::set_resolution(const int value)
+{
+ BLI_assert(value > 0);
+ resolution_ = value;
+ this->mark_cache_invalid();
+}
+
+uint8_t NURBSpline::order() const
+{
+ return order_;
+}
+
+void NURBSpline::set_order(const uint8_t value)
+{
+ BLI_assert(value >= 2 && value <= 6);
+ order_ = value;
+ this->mark_cache_invalid();
+}
+
+void NURBSpline::add_point(const float3 position,
+ const float radius,
+ const float tilt,
+ const float weight)
+{
+ positions_.append(position);
+ radii_.append(radius);
+ tilts_.append(tilt);
+ weights_.append(weight);
+ knots_dirty_ = true;
+ this->mark_cache_invalid();
+}
+
+MutableSpan<float3> NURBSpline::positions()
+{
+ return positions_;
+}
+Span<float3> NURBSpline::positions() const
+{
+ return positions_;
+}
+MutableSpan<float> NURBSpline::radii()
+{
+ return radii_;
+}
+Span<float> NURBSpline::radii() const
+{
+ return radii_;
+}
+MutableSpan<float> NURBSpline::tilts()
+{
+ return tilts_;
+}
+Span<float> NURBSpline::tilts() const
+{
+ return tilts_;
+}
+MutableSpan<float> NURBSpline::weights()
+{
+ return weights_;
+}
+Span<float> NURBSpline::weights() const
+{
+ return weights_;
+}
+
+void NURBSpline::mark_cache_invalid()
+{
+ basis_cache_dirty_ = true;
+ position_cache_dirty_ = true;
+ tangent_cache_dirty_ = true;
+ normal_cache_dirty_ = true;
+ length_cache_dirty_ = true;
+}
+
+int NURBSpline::evaluated_points_size() const
+{
+ if (!this->check_valid_size_and_order()) {
+ return 0;
+ }
+ return resolution_ * this->segments_size();
+}
+
+void NURBSpline::correct_end_tangents() const
+{
+}
+
+bool NURBSpline::check_valid_size_and_order() const
+{
+ if (this->size() < order_) {
+ return false;
+ }
+
+ if (!is_cyclic_ && this->knots_mode == KnotsMode::Bezier) {
+ if (order_ == 4) {
+ if (this->size() < 5) {
+ return false;
+ }
+ }
+ else if (order_ != 3) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+int NURBSpline::knots_size() const
+{
+ const int size = this->size() + order_;
+ return is_cyclic_ ? size + order_ - 1 : size;
+}
+
+void NURBSpline::calculate_knots() const
+{
+ const KnotsMode mode = this->knots_mode;
+ const int length = this->size();
+ const int order = order_;
+
+ knots_.resize(this->knots_size());
+
+ MutableSpan<float> knots = knots_;
+
+ if (mode == NURBSpline::KnotsMode::Normal || is_cyclic_) {
+ for (const int i : knots.index_range()) {
+ knots[i] = static_cast<float>(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;
+ }
+ }
+ }
+
+ 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;
+ }
+ }
+ }
+ }
+
+ int c = order;
+ for (int i = b; i < this->knots_size(); i++) {
+ knots[i] = knots[i - 1] + (knots[c] - knots[c - 1]);
+ c--;
+ }
+ }
+}
+
+Span<float> NURBSpline::knots() const
+{
+ if (!knots_dirty_) {
+ BLI_assert(knots_.size() == this->size() + order_);
+ return knots_;
+ }
+
+ std::lock_guard lock{knots_mutex_};
+ if (!knots_dirty_) {
+ BLI_assert(knots_.size() == this->size() + order_);
+ return knots_;
+ }
+
+ this->calculate_knots();
+
+ knots_dirty_ = false;
+
+ return knots_;
+}
+
+static void calculate_basis_for_point(const float parameter,
+ const int points_len,
+ const int order,
+ Span<float> knots,
+ MutableSpan<float> basis_buffer,
+ NURBSpline::BasisCache &basis_cache)
+{
+ /* Clamp parameter due to floating point inaccuracy. TODO: Look into using doubles. */
+ const float t = std::clamp(parameter, knots[0], knots[points_len + order - 1]);
+
+ int start = 0;
+ int end = 0;
+ for (const int i : IndexRange(points_len + order - 1)) {
+ const bool knots_equal = knots[i] == knots[i + 1];
+ if (knots_equal || t < knots[i] || t > knots[i + 1]) {
+ basis_buffer[i] = 0.0f;
+ continue;
+ }
+
+ basis_buffer[i] = 1.0f;
+ start = std::max(i - order - 1, 0);
+ end = i;
+ basis_buffer.slice(i + 1, points_len + order - 1 - i).fill(0.0f);
+ break;
+ }
+ basis_buffer[points_len + order - 1] = 0.0f;
+
+ for (const int i_order : IndexRange(2, order - 1)) {
+ if (end + i_order >= points_len + order) {
+ end = points_len + order - 1 - i_order;
+ }
+ for (const int i : IndexRange(start, end - start + 1)) {
+ float new_basis = 0.0f;
+ if (basis_buffer[i] != 0.0f) {
+ new_basis += ((t - knots[i]) * basis_buffer[i]) / (knots[i + i_order - 1] - knots[i]);
+ }
+
+ if (basis_buffer[i + 1] != 0.0f) {
+ new_basis += ((knots[i + i_order] - t) * basis_buffer[i + 1]) /
+ (knots[i + i_order] - knots[i + 1]);
+ }
+
+ basis_buffer[i] = new_basis;
+ }
+ }
+
+ /* Shrink the range of calculated values to avoid storing unnecessary zeros. */
+ while (basis_buffer[start] == 0.0f && start < end) {
+ start++;
+ }
+ while (basis_buffer[end] == 0.0f && end > start) {
+ end--;
+ }
+
+ basis_cache.weights.clear();
+ basis_cache.weights.extend(basis_buffer.slice(start, end - start + 1));
+ basis_cache.start_index = start;
+}
+
+void NURBSpline::calculate_basis_cache() const
+{
+ if (!basis_cache_dirty_) {
+ return;
+ }
+
+ std::lock_guard lock{basis_cache_mutex_};
+ if (!basis_cache_dirty_) {
+ return;
+ }
+
+ const int points_len = this->size();
+ const int eval_size = this->evaluated_points_size();
+ BLI_assert(this->evaluated_edges_size() > 0);
+ basis_cache_.resize(eval_size);
+
+ const int order = this->order();
+ Span<float> control_weights = this->weights();
+ Span<float> knots = this->knots();
+
+ MutableSpan<BasisCache> basis_cache(basis_cache_);
+
+ /* This buffer is reused by each basis calculation to store temporary values.
+ * Theoretically it could be optimized away in the future. */
+ Array<float> basis_buffer(this->knots_size());
+
+ const float start = knots[order - 1];
+ const float end = is_cyclic_ ? knots[points_len + order - 1] : knots[points_len];
+ const float step = (end - start) / this->evaluated_edges_size();
+ float parameter = start;
+ for (const int i : IndexRange(eval_size)) {
+ BasisCache &basis = basis_cache[i];
+ calculate_basis_for_point(
+ parameter, points_len + (is_cyclic_ ? order - 1 : 0), order, knots, basis_buffer, basis);
+ BLI_assert(basis.weights.size() <= order);
+
+ for (const int j : basis.weights.index_range()) {
+ const int point_index = (basis.start_index + j) % points_len;
+ basis.weights[j] *= control_weights[point_index];
+ }
+
+ parameter += step;
+ }
+
+ basis_cache_dirty_ = false;
+}
+
+template<typename T>
+void interpolate_to_evaluated_points_impl(Span<NURBSpline::BasisCache> weights,
+ const blender::VArray<T> &source_data,
+ MutableSpan<T> result_data)
+{
+ const int points_len = source_data.size();
+ BLI_assert(result_data.size() == weights.size());
+ blender::attribute_math::DefaultMixer<T> mixer(result_data);
+
+ for (const int i : result_data.index_range()) {
+ Span<float> point_weights = weights[i].weights;
+ const int start_index = weights[i].start_index;
+
+ for (const int j : point_weights.index_range()) {
+ const int point_index = (start_index + j) % points_len;
+ mixer.mix_in(i, source_data[point_index], point_weights[j]);
+ }
+ }
+
+ mixer.finalize();
+}
+
+blender::fn::GVArrayPtr NURBSpline::interpolate_to_evaluated_points(
+ const blender::fn::GVArray &source_data) const
+{
+ BLI_assert(source_data.size() == this->size());
+
+ this->calculate_basis_cache();
+ Span<BasisCache> weights(basis_cache_);
+
+ blender::fn::GVArrayPtr new_varray;
+ blender::attribute_math::convert_to_static_type(source_data.type(), [&](auto dummy) {
+ using T = decltype(dummy);
+ if constexpr (!std::is_void_v<blender::attribute_math::DefaultMixer<T>>) {
+ Array<T> values(this->evaluated_points_size());
+ interpolate_to_evaluated_points_impl<T>(weights, source_data.typed<T>(), values);
+ new_varray = std::make_unique<blender::fn::GVArray_For_ArrayContainer<Array<T>>>(
+ std::move(values));
+ }
+ });
+
+ return new_varray;
+}
+
+Span<float3> NURBSpline::evaluated_positions() const
+{
+ if (!position_cache_dirty_) {
+ return evaluated_position_cache_;
+ }
+
+ std::lock_guard lock{position_cache_mutex_};
+ if (!position_cache_dirty_) {
+ return evaluated_position_cache_;
+ }
+
+ const int eval_size = this->evaluated_points_size();
+ evaluated_position_cache_.resize(eval_size);
+
+ blender::fn::GVArray_Typed<float3> evaluated_positions{
+ this->interpolate_to_evaluated_points(blender::fn::GVArray_For_Span<float3>(positions_))};
+
+ evaluated_positions->materialize(evaluated_position_cache_);
+
+ position_cache_dirty_ = false;
+ return evaluated_position_cache_;
+}