Skip to content

Commit 6b97f71

Browse files
Ink Open Sourcecopybara-github
authored andcommitted
Add kTimeSinceStrokeEndInSeconds brush behavior source
This allows for one-shot whole-stroke animations that occur after input is finished. PiperOrigin-RevId: 872922797
1 parent d696aa6 commit 6b97f71

28 files changed

+461
-191
lines changed

ink/brush/brush_behavior.cc

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ bool IsValidBehaviorSource(BrushBehavior::Source source) {
6161
case BrushBehavior::Source::kPredictedTimeElapsedInSeconds:
6262
case BrushBehavior::Source::kDistanceRemainingInMultiplesOfBrushSize:
6363
case BrushBehavior::Source::kTimeSinceInputInSeconds:
64+
case BrushBehavior::Source::kTimeSinceStrokeEndInSeconds:
6465
case BrushBehavior::Source::
6566
kAccelerationInMultiplesOfBrushSizePerSecondSquared:
6667
case BrushBehavior::Source::
@@ -95,9 +96,10 @@ absl::Status ValidateSourceAndOutOfRangeCombination(
9596
BrushBehavior::Source source, BrushBehavior::OutOfRange out_of_range) {
9697
switch (source) {
9798
case BrushBehavior::Source::kTimeSinceInputInSeconds:
99+
case BrushBehavior::Source::kTimeSinceStrokeEndInSeconds:
98100
if (out_of_range != BrushBehavior::OutOfRange::kClamp) {
99101
return absl::InvalidArgumentError(
100-
"`Source::kTimeSinceInputInSeconds` must only be used with "
102+
"`kTimeSince*` sources can only be used with a "
101103
"`source_out_of_range_behavior` of `kClamp`.");
102104
}
103105
break;
@@ -517,6 +519,8 @@ std::string ToFormattedString(BrushBehavior::Source source) {
517519
return "kDistanceRemainingInMultiplesOfBrushSize";
518520
case BrushBehavior::Source::kTimeSinceInputInSeconds:
519521
return "kTimeSinceInputInSeconds";
522+
case BrushBehavior::Source::kTimeSinceStrokeEndInSeconds:
523+
return "kTimeSinceStrokeEndInSeconds";
520524
case BrushBehavior::Source::
521525
kAccelerationInMultiplesOfBrushSizePerSecondSquared:
522526
return "kAccelerationInMultiplesOfBrushSizePerSecondSquared";

ink/brush/brush_behavior.h

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -107,8 +107,7 @@ struct BrushBehavior {
107107
// List of input properties along with their units that can act as sources for
108108
// a `BrushBehavior`.
109109
//
110-
// This should match the enum in BrushBehavior.kt and
111-
// BrushFamilyExtensions.kt.
110+
// This should match the enum in BrushBehavior.kt.
112111
//
113112
// Behaviors that consider properties of the stroke input do not consider
114113
// alterations to the visible position of that point in the stroke by brush
@@ -180,10 +179,16 @@ struct BrushBehavior {
180179
kDistanceRemainingInMultiplesOfBrushSize,
181180
// Time elapsed since the modeled stroke input. This continues to increase
182181
// even after all stroke inputs have completed, and can be used to drive
183-
// stroke animations. These enumerators are only compatible with a
182+
// wet-layer stroke animations. This source is only compatible with a
184183
// `source_out_of_range_behavior` of `kClamp`, to ensure that the animation
185184
// will eventually end.
186185
kTimeSinceInputInSeconds,
186+
// Time elapsed since the final input of the stroke, or zero if the final
187+
// input hasn't arrived yet. This can be used to drive wet-layer stroke
188+
// animations that should occur after the final input. This source is only
189+
// compatible with a `source_out_of_range_behavior` of `kClamp`, to ensure
190+
// that the animation will eventually end.
191+
kTimeSinceStrokeEndInSeconds,
187192
// Absolute acceleration of the modeled stroke input in multiples of the
188193
// brush size per second squared. Note that this value doesn't take into
189194
// account brush behaviors that offset the position of that visible point in
@@ -247,8 +252,7 @@ struct BrushBehavior {
247252

248253
// List of tip properties that can be modified by a `BrushBehavior`.
249254
//
250-
// This should match the enums in BrushBehavior.kt and
251-
// BrushFamilyExtensions.kt.
255+
// This should match the enum in BrushBehavior.kt.
252256
enum class Target : int8_t {
253257
// `kWidthMultiplier` and `kHeightMultiplier` scale the brush-tip size along
254258
// one dimension, starting from the values calculated using
@@ -324,8 +328,7 @@ struct BrushBehavior {
324328

325329
// List of vector tip properties that can be modified by a `BrushBehavior`.
326330
//
327-
// This should match the enums in BrushBehavior.kt and
328-
// BrushFamilyExtensions.kt.
331+
// This should match the enum in BrushBehavior.kt.
329332
enum class PolarTarget : int8_t {
330333
// Adds the vector to the brush tip's absolute x/y position in stroke space,
331334
// where the angle input is measured in radians and the magnitude input is
@@ -349,8 +352,7 @@ struct BrushBehavior {
349352
// The desired behavior when an input value is outside the bounds of
350353
// `source_value_range`.
351354
//
352-
// This should match the enum in BrushBehavior.kt and
353-
// BrushFamilyExtensions.kt.
355+
// This should match the enum in BrushBehavior.kt.
354356
enum class OutOfRange : int8_t {
355357
// Values outside the range will be clamped to not exceed the bounds.
356358
kClamp,
@@ -387,8 +389,7 @@ struct BrushBehavior {
387389

388390
// List of input properties that might not be reported by `StrokeInput`.
389391
//
390-
// This should match the enums in BrushBehavior.kt and
391-
// BrushFamilyExtensions.kt.
392+
// This should match the enum in BrushBehavior.kt.
392393
enum OptionalInputProperty : int8_t {
393394
kPressure,
394395
kTilt,

ink/brush/brush_behavior_test.cc

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@ TEST(BrushBehaviorTest, StringifySource) {
8080
"kDistanceRemainingInMultiplesOfBrushSize");
8181
EXPECT_EQ(absl::StrCat(BrushBehavior::Source::kTimeSinceInputInSeconds),
8282
"kTimeSinceInputInSeconds");
83+
EXPECT_EQ(absl::StrCat(BrushBehavior::Source::kTimeSinceStrokeEndInSeconds),
84+
"kTimeSinceStrokeEndInSeconds");
8385
EXPECT_EQ(
8486
absl::StrCat(BrushBehavior::Source::
8587
kAccelerationInMultiplesOfBrushSizePerSecondSquared),
@@ -732,8 +734,18 @@ TEST(BrushBehaviorTest, ValidateSourceNode) {
732734
});
733735
EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument);
734736
EXPECT_THAT(status.message(),
735-
HasSubstr("kTimeSinceInputInSeconds` must only be used with "
736-
"`source_out_of_range_behavior` of `kClamp"));
737+
HasSubstr("`kTimeSince*` sources can only be used with a "
738+
"`source_out_of_range_behavior` of `kClamp`"));
739+
740+
status = brush_internal::ValidateBrushBehaviorNode(BrushBehavior::SourceNode{
741+
.source = BrushBehavior::Source::kTimeSinceStrokeEndInSeconds,
742+
.source_out_of_range_behavior = BrushBehavior::OutOfRange::kMirror,
743+
.source_value_range = {0, 2},
744+
});
745+
EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument);
746+
EXPECT_THAT(status.message(),
747+
HasSubstr("`kTimeSince*` sources can only be used with a "
748+
"`source_out_of_range_behavior` of `kClamp`"));
737749

738750
status = brush_internal::ValidateBrushBehaviorNode(BrushBehavior::SourceNode{
739751
.source = BrushBehavior::Source::kInputDistanceTraveledInCentimeters,

ink/brush/brush_family_test.cc

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -522,6 +522,9 @@ TEST(BrushFamilyTest, CreateWithInvalidBehaviorSourceAndOutOfRangeBehavior) {
522522
BrushTip brush_tip = {
523523
.behaviors = {BrushBehavior{{
524524
BrushBehavior::SourceNode{
525+
.source = BrushBehavior::Source::kTimeSinceInputInSeconds,
526+
.source_out_of_range_behavior =
527+
BrushBehavior::OutOfRange::kRepeat,
525528
.source_value_range = {0, 1},
526529
},
527530
BrushBehavior::TargetNode{
@@ -530,17 +533,11 @@ TEST(BrushFamilyTest, CreateWithInvalidBehaviorSourceAndOutOfRangeBehavior) {
530533
},
531534
}}},
532535
};
533-
BrushBehavior::SourceNode* source_node =
534-
&std::get<BrushBehavior::SourceNode>(brush_tip.behaviors[0].nodes[0]);
535-
536-
source_node->source = BrushBehavior::Source::kTimeSinceInputInSeconds;
537-
source_node->source_out_of_range_behavior =
538-
BrushBehavior::OutOfRange::kRepeat;
539-
absl::Status status = BrushFamily::Create(brush_tip, BrushPaint{}).status();
540-
EXPECT_EQ(status.code(), kInvalidArgument);
541-
EXPECT_THAT(status.message(),
542-
HasSubstr("kTimeSinceInputInSeconds` must only be used with "
543-
"`source_out_of_range_behavior` of `kClamp"));
536+
EXPECT_THAT(
537+
BrushFamily::Create(brush_tip, BrushPaint{}).status(),
538+
StatusIs(kInvalidArgument,
539+
HasSubstr("`kTimeSince*` sources can only be used with a "
540+
"`source_out_of_range_behavior` of `kClamp`")));
544541
}
545542

546543
TEST(BrushFamilyTest, CreateWithInvalidBehaviorTargetModifierRange) {

ink/brush/fuzz_domains.cc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@ Domain<BrushBehavior::OutOfRange> ValidBrushBehaviorOutOfRangeForSource(
180180
BrushBehavior::Source source) {
181181
switch (source) {
182182
case BrushBehavior::Source::kTimeSinceInputInSeconds:
183+
case BrushBehavior::Source::kTimeSinceStrokeEndInSeconds:
183184
return Just(BrushBehavior::OutOfRange::kClamp);
184185
default:
185186
return ArbitraryBrushBehaviorOutOfRange();
@@ -209,6 +210,7 @@ Domain<BrushBehavior::Source> ArbitraryBrushBehaviorSource() {
209210
BrushBehavior::Source::kPredictedTimeElapsedInSeconds,
210211
BrushBehavior::Source::kDistanceRemainingInMultiplesOfBrushSize,
211212
BrushBehavior::Source::kTimeSinceInputInSeconds,
213+
BrushBehavior::Source::kTimeSinceStrokeEndInSeconds,
212214
BrushBehavior::Source::
213215
kAccelerationInMultiplesOfBrushSizePerSecondSquared,
214216
BrushBehavior::Source::

ink/storage/brush.cc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,8 @@ proto::BrushBehavior::Source EncodeBrushBehaviorSource(
199199
SOURCE_DISTANCE_REMAINING_IN_MULTIPLES_OF_BRUSH_SIZE;
200200
case BrushBehavior::Source::kTimeSinceInputInSeconds:
201201
return proto::BrushBehavior::SOURCE_TIME_SINCE_INPUT_IN_SECONDS;
202+
case BrushBehavior::Source::kTimeSinceStrokeEndInSeconds:
203+
return proto::BrushBehavior::SOURCE_TIME_SINCE_STROKE_END_IN_SECONDS;
202204
case BrushBehavior::Source::
203205
kAccelerationInMultiplesOfBrushSizePerSecondSquared:
204206
return proto::BrushBehavior::
@@ -384,6 +386,8 @@ absl::StatusOr<BrushBehavior::Source> DecodeBrushBehaviorSource(
384386
case proto::BrushBehavior::
385387
SOURCE_DISTANCE_REMAINING_AS_FRACTION_OF_STROKE_LENGTH:
386388
return BrushBehavior::Source::kDistanceRemainingAsFractionOfStrokeLength;
389+
case proto::BrushBehavior::SOURCE_TIME_SINCE_STROKE_END_IN_SECONDS:
390+
return BrushBehavior::Source::kTimeSinceStrokeEndInSeconds;
387391
default:
388392
return absl::InvalidArgumentError(absl::StrCat(
389393
"invalid ink.proto.BrushBehavior.Source value: ", source_proto));

ink/storage/proto/brush_family.proto

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -651,8 +651,8 @@ message BrushBehavior {
651651

652652
// Time elapsed (in seconds) since the modeled stroke input. This continues
653653
// to increase even after all stroke inputs have completed, and can be used
654-
// to drive stroke animations. These enumerators are only compatible with a
655-
// `source_out_of_range_behavior` of `OUT_OF_RANGE_CLAMP`, to ensure that
654+
// to drive wet-layer stroke animations. This source is only compatible with
655+
// a `source_out_of_range_behavior` of `OUT_OF_RANGE_CLAMP`, to ensure that
656656
// the animation will eventually end.
657657
SOURCE_TIME_SINCE_INPUT_IN_SECONDS = 19;
658658

@@ -755,6 +755,13 @@ message BrushBehavior {
755755
// the input path, as a fraction of the current total length of the stroke.
756756
// This value changes for each input as inputs are added.
757757
SOURCE_DISTANCE_REMAINING_AS_FRACTION_OF_STROKE_LENGTH = 39;
758+
759+
// Time elapsed (in seconds) since the final input of the stroke, or zero if
760+
// the final input hasn't arrived yet. This can be used to drive wet-layer
761+
// stroke animations that should occur after the final input. This source is
762+
// only compatible with a `source_out_of_range_behavior` of
763+
// `OUT_OF_RANGE_CLAMP`, to ensure that the animation will eventually end.
764+
SOURCE_TIME_SINCE_STROKE_END_IN_SECONDS = 40;
758765
}
759766
// LINT.ThenChange(../../brush/brush_behavior.h:source)
760767

ink/strokes/in_progress_stroke.cc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,9 @@ absl::Status InProgressStroke::UpdateShape(Duration32 current_elapsed_time) {
165165

166166
input_modeler_.ExtendStroke(queued_real_inputs_, queued_predicted_inputs_,
167167
current_elapsed_time);
168+
if (inputs_are_finished_) {
169+
input_modeler_.FinishStrokeInputs();
170+
}
168171
uint32_t num_coats = BrushCoatCount();
169172
for (uint32_t i = 0; i < num_coats; ++i) {
170173
StrokeShapeUpdate update = shape_builders_[i].ExtendStroke(input_modeler_);

ink/strokes/internal/BUILD.bazel

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ cc_test(
151151
"//ink/types:type_matchers",
152152
"@com_google_absl//absl/log:absl_check",
153153
"@com_google_absl//absl/status:status_matchers",
154+
"@com_google_absl//absl/status:statusor",
154155
"@com_google_fuzztest//fuzztest",
155156
"@com_google_googletest//:gtest_main",
156157
],
@@ -239,6 +240,7 @@ cc_library(
239240
":brush_tip_state",
240241
":easing_implementation",
241242
":modeled_stroke_input",
243+
":noise_generator",
242244
"//ink/brush:brush_behavior",
243245
"//ink/brush:brush_tip",
244246
"//ink/geometry:angle",
@@ -591,17 +593,20 @@ cc_test(
591593
":stroke_shape_builder",
592594
":stroke_shape_update",
593595
":stroke_vertex",
596+
"//ink/brush:brush_behavior",
594597
"//ink/brush:brush_coat",
595598
"//ink/brush:brush_family",
596599
"//ink/brush:brush_paint",
597600
"//ink/brush:brush_tip",
598601
"//ink/geometry:envelope",
599602
"//ink/geometry:mutable_mesh",
603+
"//ink/geometry:rect",
600604
"//ink/geometry:type_matchers",
601605
"//ink/geometry/internal:algorithms",
602606
"//ink/strokes/input:stroke_input_batch",
603607
"//ink/types:duration",
604608
"@com_google_absl//absl/status",
609+
"@com_google_absl//absl/status:status_matchers",
605610
"@com_google_absl//absl/status:statusor",
606611
"@com_google_googletest//:gtest_main",
607612
],

ink/strokes/internal/brush_tip_extruder.cc

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -62,20 +62,8 @@ void BrushTipExtruder::StartStroke(float brush_epsilon, bool is_particle_brush,
6262
max_chord_height_ = GetMaxChordHeight(brush_epsilon);
6363
simplification_threshold_ = GetSimplificationThreshold(brush_epsilon);
6464
is_particle_brush_ = is_particle_brush;
65-
extrusions_.clear();
66-
saved_extrusion_data_count_ = 0;
67-
deleted_save_point_extrusions_.clear();
6865
geometry_.Reset(MutableMeshView(mesh));
69-
bounds_ = {};
70-
// Pre-allocate the first outline.
71-
num_outlines_ = 1;
72-
if (outlines_.empty()) {
73-
outlines_.resize(1);
74-
}
75-
// Clear all the outlines from the previous stroke.
76-
for (StrokeOutline& outline : outlines_) {
77-
outline.TruncateIndices({0, 0});
78-
}
66+
RestartStroke();
7967
}
8068

8169
namespace {
@@ -137,6 +125,24 @@ StrokeShapeUpdate BrushTipExtruder::ExtendStroke(
137125
vertex_count_before_update);
138126
}
139127

128+
void BrushTipExtruder::RestartStroke() {
129+
ABSL_CHECK_GT(brush_epsilon_, 0) << "`StartStroke()` has not been called";
130+
extrusions_.clear();
131+
saved_extrusion_data_count_ = 0;
132+
deleted_save_point_extrusions_.clear();
133+
geometry_.Reset(geometry_.GetMeshView());
134+
bounds_ = {};
135+
// Pre-allocate the first outline.
136+
num_outlines_ = 1;
137+
if (outlines_.empty()) {
138+
outlines_.resize(1);
139+
}
140+
// Clear all the outlines from the previous stroke.
141+
for (StrokeOutline& outline : outlines_) {
142+
outline.TruncateIndices({0, 0});
143+
}
144+
}
145+
140146
void BrushTipExtruder::ClearCachedPartialBounds() {
141147
bounds_.cached_partial_bounds.Reset();
142148
bounds_.cached_partial_bounds_left_index_count = 0;

0 commit comments

Comments
 (0)