From 049e48b4316ffa34118e06097eb6dcc38dc4bc29 Mon Sep 17 00:00:00 2001 From: Falk David Date: Wed, 17 Jan 2024 14:09:34 +0100 Subject: [PATCH] GPv3: Add curve plane normal cache This adds a cache to read a normal vector for a plane that (roughly) fits a curve. If the curve lies on a plane, the vector always point along this plane normal, otherwise it's an approximation. The cache is lazily calculated and invalidated when the positions are tagged for a change. --- .../blender/blenkernel/BKE_grease_pencil.hh | 9 ++++ .../blenkernel/intern/grease_pencil.cc | 47 +++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/source/blender/blenkernel/BKE_grease_pencil.hh b/source/blender/blenkernel/BKE_grease_pencil.hh index 8bb5aac8b3f..e4565931032 100644 --- a/source/blender/blenkernel/BKE_grease_pencil.hh +++ b/source/blender/blenkernel/BKE_grease_pencil.hh @@ -49,6 +49,11 @@ class DrawingRuntime { */ mutable SharedCache> triangles_cache; + /** + * Normal vector cache for every stroke. Computed using Newell's method. + */ + mutable SharedCache> curve_plane_normals_cache; + /** * Number of users for this drawing. The users are the frames in the Grease Pencil layers. * Different frames can refer to the same drawing, so we need to make sure we count these users @@ -69,6 +74,10 @@ class Drawing : public ::GreasePencilDrawing { * The triangles for all the fills in the geometry. */ Span triangles() const; + /** + * Normal vectors for a plane that fits the stroke. + */ + Span curve_plane_normals() const; void tag_positions_changed(); void tag_topology_changed(); diff --git a/source/blender/blenkernel/intern/grease_pencil.cc b/source/blender/blenkernel/intern/grease_pencil.cc index 7e145f10b68..5dc92be0a5c 100644 --- a/source/blender/blenkernel/intern/grease_pencil.cc +++ b/source/blender/blenkernel/intern/grease_pencil.cc @@ -293,6 +293,7 @@ Drawing::Drawing(const Drawing &other) this->runtime = MEM_new(__func__); this->runtime->triangles_cache = other.runtime->triangles_cache; + this->runtime->curve_plane_normals_cache = other.runtime->curve_plane_normals_cache; } Drawing::~Drawing() @@ -357,6 +358,51 @@ Span Drawing::triangles() const return this->runtime->triangles_cache.data().as_span(); } +Span Drawing::curve_plane_normals() const +{ + this->runtime->curve_plane_normals_cache.ensure([&](Vector &r_data) { + const CurvesGeometry &curves = this->strokes(); + const Span positions = curves.positions(); + const OffsetIndices points_by_curve = curves.points_by_curve(); + + r_data.reinitialize(curves.curves_num()); + threading::parallel_for(curves.curves_range(), 512, [&](const IndexRange range) { + for (const int curve_i : range) { + const IndexRange points = points_by_curve[curve_i]; + if (points.size() < 2) { + r_data[curve_i] = float3(1.0f, 0.0f, 0.0f); + continue; + } + + /* Calculate normal using Newell's method. */ + float3 normal(0.0f); + float3 prev_point = positions[points.last()]; + for (const int point_i : points) { + const float3 curr_point = positions[point_i]; + add_newell_cross_v3_v3v3(normal, prev_point, curr_point); + prev_point = curr_point; + } + + float length; + normal = math::normalize_and_get_length(normal, length); + /* Check for degenerate case where the points are on a line. */ + if (math::is_zero(length)) { + for (const int point_i : points.drop_back(1)) { + float3 segment_vec = math::normalize(positions[point_i] - positions[point_i + 1]); + if (math::length_squared(segment_vec) != 0.0f) { + normal = float3(segment_vec.y, -segment_vec.x, 0.0f); + break; + } + } + } + + r_data[curve_i] = normal; + } + }); + }); + return this->runtime->curve_plane_normals_cache.data().as_span(); +} + const bke::CurvesGeometry &Drawing::strokes() const { return this->geometry.wrap(); @@ -409,6 +455,7 @@ void Drawing::tag_positions_changed() { this->strokes_for_write().tag_positions_changed(); this->runtime->triangles_cache.tag_dirty(); + this->runtime->curve_plane_normals_cache.tag_dirty(); } void Drawing::tag_topology_changed()