Skip to content

Commit 145a28a

Browse files
Ink Open Sourcecopybara-github
authored andcommitted
Add BrushBehavior::Target::kAnimationFrameOffset
This CL adds the necessary boilerplate and plumbing for a new brush behavior target for controlling the starting animation frame of animated particles. Such a target can be wired up to e.g. a noise node to make particles start on a random frame, or a distance-traveled source to make a series of particles that cycle through starting frames in order, or whatever else is desired. The actual extruder change to use the new field in the `BrushTipState` to alter the particle's texture UV coordinates is left as a TODO for the next CL. PiperOrigin-RevId: 720180903
1 parent fd5ece4 commit 145a28a

19 files changed

+231
-19
lines changed

ink/brush/brush_behavior.cc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,7 @@ bool IsValidBehaviorTarget(BrushBehavior::Target target) {
296296
case BrushBehavior::Target::kPositionOffsetYInMultiplesOfBrushSize:
297297
case BrushBehavior::Target::kPositionOffsetForwardInMultiplesOfBrushSize:
298298
case BrushBehavior::Target::kPositionOffsetLateralInMultiplesOfBrushSize:
299+
case BrushBehavior::Target::kTextureAnimationProgressOffset:
299300
case BrushBehavior::Target::kHueOffsetInRadians:
300301
case BrushBehavior::Target::kSaturationMultiplier:
301302
case BrushBehavior::Target::kLuminosity:
@@ -650,6 +651,8 @@ std::string ToFormattedString(BrushBehavior::Target target) {
650651
return "kPositionOffsetForwardInMultiplesOfBrushSize";
651652
case BrushBehavior::Target::kPositionOffsetLateralInMultiplesOfBrushSize:
652653
return "kPositionOffsetLateralInMultiplesOfBrushSize";
654+
case BrushBehavior::Target::kTextureAnimationProgressOffset:
655+
return "kTextureAnimationProgressOffset";
653656
case BrushBehavior::Target::kHueOffsetInRadians:
654657
return "kHueOffsetInRadians";
655658
case BrushBehavior::Target::kSaturationMultiplier:

ink/brush/brush_behavior.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,12 @@ struct BrushBehavior {
286286
// Y-axis (and a negative value moves the brush tip center towards the
287287
// negative Y-axis).
288288
kPositionOffsetLateralInMultiplesOfBrushSize,
289+
// Adds the target modifier to the initial texture animation progress value
290+
// of the current particle (which is relevant only for strokes with an
291+
// animated texture). The final progress offset is not clamped, but is
292+
// effectively normalized (mod 1). If multiple behaviors have this target,
293+
// they stack additively.
294+
kTextureAnimationProgressOffset,
289295

290296
// The following are targets for tip color adjustments, including opacity.
291297
// Renderers can apply them to the brush color when a stroke is drawn to

ink/brush/brush_behavior_test.cc

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,23 @@ TEST(BrushBehaviorTest, StringifyTarget) {
161161
"kRotationOffsetInRadians");
162162
EXPECT_EQ(absl::StrCat(BrushBehavior::Target::kCornerRoundingOffset),
163163
"kCornerRoundingOffset");
164+
EXPECT_EQ(absl::StrCat(
165+
BrushBehavior::Target::kPositionOffsetXInMultiplesOfBrushSize),
166+
"kPositionOffsetXInMultiplesOfBrushSize");
167+
EXPECT_EQ(absl::StrCat(
168+
BrushBehavior::Target::kPositionOffsetYInMultiplesOfBrushSize),
169+
"kPositionOffsetYInMultiplesOfBrushSize");
170+
EXPECT_EQ(
171+
absl::StrCat(
172+
BrushBehavior::Target::kPositionOffsetForwardInMultiplesOfBrushSize),
173+
"kPositionOffsetForwardInMultiplesOfBrushSize");
174+
EXPECT_EQ(
175+
absl::StrCat(
176+
BrushBehavior::Target::kPositionOffsetLateralInMultiplesOfBrushSize),
177+
"kPositionOffsetLateralInMultiplesOfBrushSize");
178+
EXPECT_EQ(
179+
absl::StrCat(BrushBehavior::Target::kTextureAnimationProgressOffset),
180+
"kTextureAnimationProgressOffset");
164181
EXPECT_EQ(absl::StrCat(BrushBehavior::Target::kHueOffsetInRadians),
165182
"kHueOffsetInRadians");
166183
EXPECT_EQ(absl::StrCat(BrushBehavior::Target::kSaturationMultiplier),

ink/brush/fuzz_domains.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,7 @@ Domain<BrushBehavior::Target> ArbitraryBrushBehaviorTarget() {
232232
BrushBehavior::Target::kPositionOffsetYInMultiplesOfBrushSize,
233233
BrushBehavior::Target::kPositionOffsetForwardInMultiplesOfBrushSize,
234234
BrushBehavior::Target::kPositionOffsetLateralInMultiplesOfBrushSize,
235+
BrushBehavior::Target::kTextureAnimationProgressOffset,
235236
BrushBehavior::Target::kHueOffsetInRadians,
236237
BrushBehavior::Target::kSaturationMultiplier,
237238
BrushBehavior::Target::kLuminosity,

ink/geometry/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ cc_library(
5858
hdrs = ["angle.h"],
5959
visibility = ["//visibility:public"],
6060
deps = [
61+
"//ink/geometry/internal:modulo",
6162
"//ink/types:numbers",
6263
"@com_google_absl//absl/strings:str_format",
6364
],

ink/geometry/angle.cc

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -22,25 +22,6 @@
2222

2323
namespace ink {
2424

25-
Angle Angle::Normalized() const {
26-
float radians =
27-
std::fmod(ValueInRadians(), static_cast<float>(2.0 * numbers::kPi));
28-
// fmod always matches the sign of the first argument, so fmod(angle, 2π)
29-
// returns a value in the range (-2π, 2π). We want [0, 2π), so wrap negative
30-
// values back to positive.
31-
if (radians < 0.f) {
32-
radians += static_cast<float>(2.0 * numbers::kPi);
33-
// If fmod returned a sufficiently small negative number, then adding 2π
34-
// will give us a result exactly equal to 2π, due to float rounding.
35-
// However, Angle::Normalized() promises to return a value strictly less
36-
// than 2π, so we should normalize to zero in this case.
37-
if (radians == static_cast<float>(2.0 * numbers::kPi)) {
38-
radians = 0.f;
39-
}
40-
}
41-
return Angle::Radians(radians);
42-
}
43-
4425
Angle Angle::NormalizedAboutZero() const {
4526
float radians =
4627
std::fmod(ValueInRadians(), static_cast<float>(2.0 * numbers::kPi));

ink/geometry/angle.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include <cstdlib>
2020
#include <string>
2121

22+
#include "ink/geometry/internal/modulo.h"
2223
#include "ink/types/numbers.h"
2324

2425
namespace ink {
@@ -197,6 +198,11 @@ inline Angle& operator/=(Angle& lhs, float rhs) {
197198
return lhs;
198199
}
199200

201+
inline Angle Angle::Normalized() const {
202+
return Angle::Radians(geometry_internal::FloatModulo(
203+
ValueInRadians(), static_cast<float>(2.0 * numbers::kPi)));
204+
}
205+
200206
} // namespace ink
201207

202208
#endif // INK_GEOMETRY_ANGLE_H_

ink/geometry/internal/BUILD.bazel

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,25 @@ cc_test(
133133
],
134134
)
135135

136+
cc_library(
137+
name = "modulo",
138+
srcs = ["modulo.cc"],
139+
hdrs = ["modulo.h"],
140+
deps = [
141+
"@com_google_absl//absl/log:absl_check",
142+
],
143+
)
144+
145+
cc_test(
146+
name = "modulo_test",
147+
srcs = ["modulo_test.cc"],
148+
deps = [
149+
":modulo",
150+
"@com_google_fuzztest//fuzztest",
151+
"@com_google_fuzztest//fuzztest:fuzztest_gtest_main",
152+
],
153+
)
154+
136155
cc_library(
137156
name = "circle",
138157
srcs = ["circle.cc"],

ink/geometry/internal/modulo.cc

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Copyright 2024 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#include "ink/geometry/internal/modulo.h"
16+
17+
#include <cmath>
18+
19+
#include "absl/log/absl_check.h"
20+
21+
namespace ink::geometry_internal {
22+
23+
float FloatModulo(float a, float b) {
24+
ABSL_DCHECK(std::isfinite(b) && b > 0.f);
25+
float result = std::fmodf(a, b);
26+
// `fmodf` always matches the sign of the first argument, so `fmodf(a, b)`
27+
// returns a value in the range (-b, b). We want [0, b), so wrap negative
28+
// values back to positive.
29+
if (result < 0.f) {
30+
result += b;
31+
// If `fmodf` returned a sufficiently small negative number, then adding `b`
32+
// will give us a result exactly equal to `b`, due to float rounding.
33+
// However, `FloatModulo` promises to return a value strictly less than `b`,
34+
// so we should return zero in this case.
35+
if (result == b) {
36+
result = 0.f;
37+
}
38+
}
39+
return result;
40+
}
41+
42+
} // namespace ink::geometry_internal

ink/geometry/internal/modulo.h

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Copyright 2024 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#ifndef INK_GEOMETRY_INTERNAL_MODULO_H_
16+
#define INK_GEOMETRY_INTERNAL_MODULO_H_
17+
18+
namespace ink::geometry_internal {
19+
20+
// Returns `a` mod `b`, with the result being in the range [0, `b`). This is
21+
// different from `std::fmodf`, which returns a negative result if `a` is
22+
// negative. `b` must be finite and strictly greater than zero. Returns NaN if
23+
// `a` is infinite or NaN.
24+
float FloatModulo(float a, float b);
25+
26+
} // namespace ink::geometry_internal
27+
28+
#endif // INK_GEOMETRY_INTERNAL_MODULO_H_

0 commit comments

Comments
 (0)