Fix #120698: GPv3: Don't move the first stroke point

There are two ways the first stroke point is moved after initial
placement:
- The `process_extension_sample` overwrites the initial values of the
  sample point after `process_start_sample`. It copies the _new_ sample
  position, until a threshold (3 pixels) is reached and the 2nd point
  is created, and the 1st point remains stationary.
- After initial placement the point may still get shifted due to the
  resampling and interpolation used. Long sections between stroke
  samples may get subdivided and the positions of the two samples are
  linearly interpolated. However, the interpolation starts at 1/n,
  meaning the first interpolated point never matches the first sample.
  This is correct for later samples where the last point should not be
  repeated, but it ends up moving the first curve point again when the
  2nd sample is processed.

This patch fixes both issues by keeping the first generated point
stationary and never touching its position again.

Pull Request: https://projects.blender.org/blender/blender/pulls/121011
This commit is contained in:
Lukas Tönne
2024-04-24 11:54:01 +02:00
parent 2056b4b563
commit 11bfac7f11

View File

@@ -13,6 +13,7 @@
#include "BKE_scene.hh"
#include "BLI_length_parameterize.hh"
#include "BLI_math_base.hh"
#include "BLI_math_color.h"
#include "BLI_math_geom.h"
@@ -35,11 +36,22 @@ static constexpr float POINT_OVERRIDE_THRESHOLD_PX = 3.0f;
static constexpr float POINT_RESAMPLE_MIN_DISTANCE_PX = 10.0f;
template<typename T>
static inline void linear_interpolation(const T &a, const T &b, MutableSpan<T> dst)
static inline void linear_interpolation(const T &a,
const T &b,
MutableSpan<T> dst,
const bool include_first_point)
{
const float step = 1.0f / float(dst.size());
for (const int i : dst.index_range()) {
dst[i] = bke::attribute_math::mix2(float(i + 1) * step, a, b);
if (include_first_point) {
const float step = math::safe_rcp(float(dst.size() - 1));
for (const int i : dst.index_range()) {
dst[i] = bke::attribute_math::mix2(float(i) * step, a, b);
}
}
else {
const float step = 1.0f / float(dst.size());
for (const int i : dst.index_range()) {
dst[i] = bke::attribute_math::mix2(float(i + 1) * step, a, b);
}
}
}
@@ -366,8 +378,13 @@ struct PaintOperationExecutor {
const ColorGeometry4f prev_vertex_color = drawing_->vertex_colors().last();
/* Overwrite last point if it's very close. */
const IndexRange points_range = curves.points_by_curve()[curves.curves_range().last()];
const bool is_first_sample = (points_range.size() == 1);
if (math::distance(coords, prev_coords) < POINT_OVERRIDE_THRESHOLD_PX) {
curves.positions_for_write().last() = self.placement_.project(coords);
/* Don't move the first point of the stroke. */
if (!is_first_sample) {
curves.positions_for_write().last() = self.placement_.project(coords);
}
drawing_->radii_for_write().last() = math::max(radius, prev_radius);
drawing_->opacities_for_write().last() = math::max(opacity, prev_opacity);
return;
@@ -395,10 +412,11 @@ struct PaintOperationExecutor {
MutableSpan<float> new_opacities = drawing_->opacities_for_write().slice(new_points);
MutableSpan<ColorGeometry4f> new_vertex_colors = drawing_->vertex_colors_for_write().slice(
new_points);
linear_interpolation<float2>(prev_coords, coords, new_screen_space_coords);
linear_interpolation<float>(prev_radius, radius, new_radii);
linear_interpolation<float>(prev_opacity, opacity, new_opacities);
linear_interpolation<ColorGeometry4f>(prev_vertex_color, vertex_color, new_vertex_colors);
linear_interpolation<float2>(prev_coords, coords, new_screen_space_coords, is_first_sample);
linear_interpolation<float>(prev_radius, radius, new_radii, is_first_sample);
linear_interpolation<float>(prev_opacity, opacity, new_opacities, is_first_sample);
linear_interpolation<ColorGeometry4f>(
prev_vertex_color, vertex_color, new_vertex_colors, is_first_sample);
/* Update screen space buffers with new points. */
self.screen_space_coords_orig_.extend(new_screen_space_coords);