Skip to content

Commit e74517e

Browse files
Ink Open Sourcecopybara-github
authored andcommitted
Consolidate Ink's linear interpolation implementations
We had multiple different naive floating-point lerp implementations sprinkled throughout our code. In some places, we used `a + (b - a) * t`, which gives incorrect results if `b - a` overflows, and can be inexact for `t = 1`. In other places, we used `a * (1 - t) + b * t`, which is better, but apparently has monotonicity issues [1] and also gives incorrect results when `t` is infinite and `a` or `b` is zero. Better to have one _carefully tested_ definition and use it everywhere, which is what this CL does. The good news is that C++20 (which we can finally now use in all our build environments) provides a `std::lerp` function that handles these edge cases. The bad news is that (at least in some of our build environments), it seems to have a bug (b/457491215) where its behavior does not match the standard! So for now our `float Lerp` definition in this CL has an extra branch to work around that before deferring to `std::lerp`. [1] https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0811r3.html PiperOrigin-RevId: 826613841
1 parent 994e034 commit e74517e

22 files changed

+533
-298
lines changed

ink/geometry/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,7 @@ cc_library(
264264
deps = [
265265
":point",
266266
":vec",
267+
"//ink/geometry/internal:lerp",
267268
"@com_google_absl//absl/strings",
268269
],
269270
)

ink/geometry/internal/BUILD.bazel

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,7 @@ cc_library(
2525
hdrs = ["algorithms.h"],
2626
deps = [
2727
":intersects_internal",
28-
"//ink/color",
2928
"//ink/geometry:affine_transform",
30-
"//ink/geometry:angle",
3129
"//ink/geometry:envelope",
3230
"//ink/geometry:mesh",
3331
"//ink/geometry:mutable_mesh",
@@ -46,8 +44,6 @@ cc_test(
4644
srcs = ["algorithms_test.cc"],
4745
deps = [
4846
":algorithms",
49-
"//ink/color",
50-
"//ink/color:type_matchers",
5147
"//ink/geometry:affine_transform",
5248
"//ink/geometry:angle",
5349
"//ink/geometry:envelope",
@@ -136,6 +132,37 @@ cc_test(
136132
],
137133
)
138134

135+
cc_library(
136+
name = "lerp",
137+
srcs = ["lerp.cc"],
138+
hdrs = ["lerp.h"],
139+
deps = [
140+
"//ink/color",
141+
"//ink/geometry:angle",
142+
"//ink/geometry:point",
143+
"//ink/geometry:vec",
144+
"//ink/types:duration",
145+
],
146+
)
147+
148+
cc_test(
149+
name = "lerp_test",
150+
srcs = ["lerp_test.cc"],
151+
deps = [
152+
":lerp",
153+
"//ink/color",
154+
"//ink/color:type_matchers",
155+
"//ink/geometry:angle",
156+
"//ink/geometry:point",
157+
"//ink/geometry:type_matchers",
158+
"//ink/geometry:vec",
159+
"//ink/types:duration",
160+
"//ink/types:type_matchers",
161+
"@com_google_fuzztest//fuzztest",
162+
"@com_google_googletest//:gtest_main",
163+
],
164+
)
165+
139166
cc_library(
140167
name = "modulo",
141168
srcs = ["modulo.cc"],

ink/geometry/internal/algorithms.cc

Lines changed: 0 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,7 @@
2323

2424
#include "absl/log/absl_check.h"
2525
#include "absl/types/span.h"
26-
#include "ink/color/color.h"
2726
#include "ink/geometry/affine_transform.h"
28-
#include "ink/geometry/angle.h"
2927
#include "ink/geometry/envelope.h"
3028
#include "ink/geometry/internal/intersects_internal.h"
3129
#include "ink/geometry/mesh.h"
@@ -90,44 +88,6 @@ std::optional<Vec> VectorFromPointToSegmentProjection(Point point,
9088
base_vector.Orthogonal();
9189
}
9290

93-
float Lerp(float a, float b, float t) { return a + (b - a) * t; }
94-
95-
Point Lerp(Point a, Point b, float t) {
96-
return Segment{.start = a, .end = b}.Lerp(t);
97-
}
98-
99-
Color::RgbaFloat Lerp(const Color::RgbaFloat& a, const Color::RgbaFloat& b,
100-
float t) {
101-
return {.r = a.r * (1 - t) + b.r * t,
102-
.g = a.g * (1 - t) + b.g * t,
103-
.b = a.b * (1 - t) + b.b * t,
104-
.a = a.a * (1 - t) + b.a * t};
105-
}
106-
107-
Angle Lerp(Angle a, Angle b, float t) { return (1 - t) * a + t * b; }
108-
Angle NormalizedAngleLerp(Angle a, Angle b, float t) {
109-
return (a + Lerp(Angle(), (b - a).NormalizedAboutZero(), t)).Normalized();
110-
}
111-
112-
Vec Lerp(Vec a, Vec b, float t) {
113-
return Vec{Lerp(a.x, b.x, t), Lerp(a.y, b.y, t)};
114-
}
115-
116-
float InverseLerp(float a, float b, float value) {
117-
// If the interval between `a` and `b` is 0, there is no way to get to `t`
118-
// because in the other direction the value of `t` won't impact the result.
119-
if (b - a == 0.f) {
120-
return 0.f;
121-
}
122-
return (value - a) / (b - a);
123-
}
124-
125-
float LinearMap(float input_value, std::pair<float, float> input_range,
126-
std::pair<float, float> output_range) {
127-
return Lerp(output_range.first, output_range.second,
128-
InverseLerp(input_range.first, input_range.second, input_value));
129-
}
130-
13191
std::optional<std::pair<float, float>> SegmentIntersectionRatio(
13292
const Segment& a, const Segment& b) {
13393
if (a == b) return std::pair{0.f, 0.f};

ink/geometry/internal/algorithms.h

Lines changed: 0 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,7 @@
2020
#include <utility>
2121

2222
#include "absl/types/span.h"
23-
#include "ink/color/color.h"
2423
#include "ink/geometry/affine_transform.h"
25-
#include "ink/geometry/angle.h"
2624
#include "ink/geometry/envelope.h"
2725
#include "ink/geometry/mesh.h"
2826
#include "ink/geometry/mutable_mesh.h"
@@ -62,43 +60,6 @@ Envelope CalculateEnvelope(const MutableMesh& mesh);
6260
std::optional<Vec> VectorFromPointToSegmentProjection(Point point,
6361
const Segment& segment);
6462

65-
// Linearly interpolates between `a` and `b`. Extrapolates when `t` is not in
66-
// [0, 1].
67-
//
68-
// In the case where `a` == `b` the function will return `a` for any value of
69-
// `t`.
70-
//
71-
// Note that the `Angle` overload simply interpolates the value of the `Angle`;
72-
// it does not have any special case logic for congruent angles. I.e., for
73-
// `Angle`s that differ by more than 2π, this will interpolate through one (or
74-
// more) full rotations, and for `Angle`s that differ by less than 2π, this
75-
// may interpolate the "long way" around the unit circle. If you require that
76-
// behavior, you can achieve it by normalizing the `Angle`s w.r.t. a reference
77-
// `Angle` (see also `Angle::Normalized` and `Angle::NormalizedAboutZero`).
78-
float Lerp(float a, float b, float t);
79-
Point Lerp(Point a, Point b, float t);
80-
Color::RgbaFloat Lerp(const Color::RgbaFloat& a, const Color::RgbaFloat& b,
81-
float t);
82-
Angle Lerp(Angle a, Angle b, float t);
83-
Vec Lerp(Vec a, Vec b, float t);
84-
85-
// Linearly interpolates between `a` and `b` in the shorter direction between
86-
// the two angles and returns a value in range [0, 2pi).
87-
Angle NormalizedAngleLerp(Angle a, Angle b, float t);
88-
89-
// Linearly rescales `t` relative to `a` and `b`, such that `a` maps to 0, and
90-
// `b` maps to 1. If `value` is between `a` and `b`, the result will lie in the
91-
// interval [0, 1].
92-
//
93-
// If `a` == `b` this function will return 0, for any `value`.
94-
float InverseLerp(float a, float b, float value);
95-
96-
// Linearly maps an `input_value` from an `input_range` to an`output_range` such
97-
// that `input_range.first` maps to `output_range.first` and
98-
// `input_range.second` maps to `output_range.second`.
99-
float LinearMap(float input_value, std::pair<float, float> input_range,
100-
std::pair<float, float> output_range);
101-
10263
// Returns the ratio along `a` (per `Segment::Lerp`) at which it intersects `b`,
10364
// and along `b' (per `Segment::Lerp`) at which it intersects `a` or
10465
// `std::nullopt` if they do not intersect. If `a` and `b` are overlapping

ink/geometry/internal/algorithms_test.cc

Lines changed: 0 additions & 179 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,6 @@
2424
#include "absl/status/status.h"
2525
#include "absl/status/statusor.h"
2626
#include "absl/types/span.h"
27-
#include "ink/color/color.h"
28-
#include "ink/color/type_matchers.h"
2927
#include "ink/geometry/affine_transform.h"
3028
#include "ink/geometry/angle.h"
3129
#include "ink/geometry/envelope.h"
@@ -165,183 +163,6 @@ TEST(VectorFromPointToSegmentProjectionTest,
165163
Optional(VecNear(segment.Lerp(*segment.Project(point)) - point, 0.0001)));
166164
}
167165

168-
TEST(LerpFloatTest, AmountBetweenZeroAndOne) {
169-
EXPECT_FLOAT_EQ(Lerp(100.0, 200.0, 0.1), 110.0);
170-
}
171-
172-
TEST(LerpFloatTest, AmountGreaterThanOne) {
173-
EXPECT_FLOAT_EQ(Lerp(100.0, 200.0, 1.1), 210.0);
174-
}
175-
176-
TEST(LerpFloatTest, AmountLessThanZero) {
177-
EXPECT_FLOAT_EQ(Lerp(100.0, 200.0, -0.1), 90.0);
178-
}
179-
180-
TEST(InverseLerpFloatTest, InverseLerp) {
181-
EXPECT_FLOAT_EQ(InverseLerp(100.0, 200.0, 140.0), 0.4);
182-
}
183-
184-
TEST(InverseLerpFloatTest, InverseLerpOnZeroWidthInterval) {
185-
EXPECT_FLOAT_EQ(InverseLerp(100.0, 100.0, 140.0), 0.f);
186-
}
187-
188-
TEST(InverseLerpFloatTest, InverseLerpIsInverseOfLerp) {
189-
EXPECT_FLOAT_EQ(InverseLerp(100.0, 200, Lerp(100, 200, 0.1)), 0.1);
190-
EXPECT_FLOAT_EQ(InverseLerp(100.0, 200, Lerp(100, 200, 1.1)), 1.1);
191-
EXPECT_FLOAT_EQ(InverseLerp(100.0, 200, Lerp(100, 200, -0.1)), -0.1);
192-
193-
// For a zero-width interval Lerp cannot be be inversed because the original
194-
// `t` doesn't impact the result of the Lerp function.
195-
EXPECT_FLOAT_EQ(InverseLerp(100.0, 100, Lerp(100, 100, 0.1)), 0.0f);
196-
EXPECT_FLOAT_EQ(InverseLerp(100.0, 100, Lerp(100, 100, 1.1)), 0.0f);
197-
EXPECT_FLOAT_EQ(InverseLerp(100.0, 100, Lerp(100, 100, -0.1)), 0.0f);
198-
}
199-
200-
TEST(LinearMapTest, LinearMap) {
201-
EXPECT_FLOAT_EQ(LinearMap(10, {0, 100}, {0, 200}), 20);
202-
EXPECT_FLOAT_EQ(LinearMap(10, {0, 100}, {100, 150}), 105);
203-
EXPECT_FLOAT_EQ(LinearMap(10, {0, 100}, {0, -100}), -10);
204-
}
205-
206-
TEST(LinearMapTest, LinearMapOnZeroWidthInputInterval) {
207-
EXPECT_FLOAT_EQ(LinearMap(0, {0, 0}, {0, 100}), 0);
208-
EXPECT_FLOAT_EQ(LinearMap(150, {150, 150}, {0, 100}), 0);
209-
}
210-
211-
TEST(LinearMapTest, LinearMapOnZeroWidthTargetInterval) {
212-
EXPECT_FLOAT_EQ(LinearMap(0, {0, 100}, {100, 100}), 100);
213-
EXPECT_FLOAT_EQ(LinearMap(50, {0, 100}, {100, 100}), 100);
214-
}
215-
216-
TEST(LinearMapTest, LinearMapWithValueOutsideRange) {
217-
EXPECT_FLOAT_EQ(LinearMap(-10, {0, 100}, {0, 200}), -20);
218-
}
219-
220-
TEST(LinearMapTest, LinearMapLinearMapofLinearMapIsOriginalValue) {
221-
EXPECT_FLOAT_EQ(
222-
LinearMap(LinearMap(10, {0, 100}, {0, 200}), {0, 200}, {0, 100}), 10);
223-
}
224-
225-
TEST(LerpPointTest, AmountBetweenZeroAndOne) {
226-
EXPECT_THAT(Lerp(Point({100.0, 100.0}), Point({200.0, 200.0}), 0.1),
227-
PointEq(Point({110.0, 110.0})));
228-
}
229-
230-
TEST(LerpPointTest, AmountGreaterThanOne) {
231-
EXPECT_THAT(Lerp(Point({100.0, 100.0}), Point({200.0, 200.0}), 1.1),
232-
PointEq(Point({210.0, 210.0})));
233-
}
234-
235-
TEST(LerpPointTest, AmountLessThanZero) {
236-
EXPECT_THAT(Lerp(Point({100.0, 100.0}), Point({200.0, 200.0}), -0.1),
237-
PointEq(Point({90.0, 90.0})));
238-
}
239-
240-
TEST(LerpColorRgbaFloatTest, AmountBetweenZeroAndOne) {
241-
EXPECT_THAT(Lerp(Color::RgbaFloat({100.0, 100.0, 100.0, 100.0}),
242-
Color::RgbaFloat({200.0, 200.0, 200.0, 200.0}), 0.1),
243-
ChannelStructEqFloats({110.0, 110.0, 110.0, 110.0}));
244-
}
245-
246-
TEST(LerpColorRgbaFloatTest, AmountGreaterThanOne) {
247-
EXPECT_THAT(Lerp(Color::RgbaFloat({100.0, 100.0, 100.0, 100.0}),
248-
Color::RgbaFloat({200.0, 200.0, 200.0, 200.0}), 1.1),
249-
ChannelStructEqFloats({210.0, 210.0, 210.0, 210.0}));
250-
}
251-
252-
TEST(LerpColorRgbaFloatTest, AmountLessThanZero) {
253-
EXPECT_THAT(Lerp(Color::RgbaFloat({100.0, 100.0, 100.0, 100.0}),
254-
Color::RgbaFloat({200.0, 200.0, 200.0, 200.0}), -0.1),
255-
ChannelStructEqFloats({90.0, 90.0, 90.0, 90.0}));
256-
}
257-
258-
TEST(LerpAngleTest, AmountBetweenZeroAndOne) {
259-
EXPECT_THAT(Lerp(Angle::Radians(1), Angle::Radians(2), 0.3),
260-
AngleEq(Angle::Radians(1.3)));
261-
}
262-
263-
TEST(LerpAngleTest, AmountLessThanZero) {
264-
EXPECT_THAT(Lerp(Angle::Radians(0.5), Angle::Radians(1.5), -2),
265-
AngleEq(Angle::Radians(-1.5)));
266-
}
267-
268-
TEST(LerpAngleTest, AmountGreaterThanOne) {
269-
EXPECT_THAT(Lerp(Angle::Radians(-0.3), Angle::Radians(0.7), 5),
270-
AngleEq(Angle::Radians(4.7)));
271-
}
272-
273-
TEST(LerpAngleTest, DifferenceGreaterThanTwoPi) {
274-
EXPECT_THAT(Lerp(Angle::Radians(-6), Angle::Radians(4), 0.7),
275-
AngleEq(Angle::Radians(1)));
276-
}
277-
278-
TEST(NormalizedAngleLerpTest, DifferenceSmallerThanPiWithBGreaterA) {
279-
EXPECT_THAT(NormalizedAngleLerp(Angle::Radians(1), Angle::Radians(2), 0.3),
280-
AngleEq(Angle::Radians(1.3)));
281-
}
282-
283-
TEST(NormalizedAngleLerpTest, DifferenceGreaterThanPiWithBGreaterA) {
284-
EXPECT_THAT(
285-
NormalizedAngleLerp(Angle::Radians(0.5), Angle::Radians(5.5), 0.3),
286-
AngleNear(Angle::Radians(0.116), 0.01));
287-
}
288-
289-
TEST(NormalizedAngleLerpTest, DifferenceSmallerThanPiWithAGreaterB) {
290-
EXPECT_THAT(
291-
NormalizedAngleLerp(Angle::Radians(5.5), Angle::Radians(0.5), 0.3),
292-
AngleNear(Angle::Radians(5.884), 0.01));
293-
}
294-
295-
TEST(NormalizedAngleLerpTest, DifferenceGreaterThanPiWithAGreaterB) {
296-
EXPECT_THAT(
297-
NormalizedAngleLerp(Angle::Radians(5.5), Angle::Radians(4.5), 0.3),
298-
AngleEq(Angle::Radians(5.2)));
299-
}
300-
301-
TEST(LerpTest, NonZeroInputVectorsWithDifferentDirections) {
302-
EXPECT_THAT(Lerp(Vec{4, 0}, Vec{0, 5}, -5), VecEq({24, -25}));
303-
EXPECT_THAT(Lerp(Vec{4, 0}, Vec{0, 5}, -4), VecEq({20, -20}));
304-
EXPECT_THAT(Lerp(Vec{4, 0}, Vec{0, 5}, -1), VecEq({8, -5}));
305-
EXPECT_THAT(Lerp(Vec{4, 0}, Vec{0, 5}, 0), VecEq({4, 0}));
306-
EXPECT_THAT(Lerp(Vec{4, 0}, Vec{0, 5}, 0.5), VecEq({2, 2.5}));
307-
EXPECT_THAT(Lerp(Vec{4, 0}, Vec{0, 5}, 1), VecEq({0, 5}));
308-
EXPECT_THAT(Lerp(Vec{4, 0}, Vec{0, 5}, 3), VecEq({-8, 15}));
309-
310-
EXPECT_THAT(Lerp(Vec{4, 0}, Vec{0, -3}, -1), VecEq({8, 3}));
311-
EXPECT_THAT(Lerp(Vec{4, 0}, Vec{0, -3}, 0), VecEq({4, 0}));
312-
EXPECT_THAT(Lerp(Vec{4, 0}, Vec{0, -3}, 0.5), VecEq({2, -1.5}));
313-
EXPECT_THAT(Lerp(Vec{4, 0}, Vec{0, -3}, 1), VecEq({0, -3}));
314-
EXPECT_THAT(Lerp(Vec{4, 0}, Vec{0, -3}, 4), VecEq({-12, -12}));
315-
EXPECT_THAT(Lerp(Vec{4, 0}, Vec{0, -3}, 5), VecEq({-16, -15}));
316-
}
317-
318-
TEST(LerpTest, NonZeroInputVectorsWithSameDirection) {
319-
EXPECT_THAT(Lerp(Vec{1, 1}, Vec{3, 3}, -1), VecNear({-1, -1}, 0.001));
320-
EXPECT_THAT(Lerp(Vec{1, 1}, Vec{3, 3}, 0), VecEq({1, 1}));
321-
EXPECT_THAT(Lerp(Vec{1, 1}, Vec{3, 3}, 0.5), VecEq({2, 2}));
322-
EXPECT_THAT(Lerp(Vec{1, 1}, Vec{3, 3}, 1), VecEq({3, 3}));
323-
EXPECT_THAT(Lerp(Vec{1, 1}, Vec{3, 3}, 2), VecEq({5, 5}));
324-
}
325-
326-
TEST(LerpTest, ZeroInputVectors) {
327-
EXPECT_THAT(Lerp(Vec{0, 0}, Vec{0, 2}, -1), VecEq({0, -2}));
328-
EXPECT_THAT(Lerp(Vec{0, 0}, Vec{0, 2}, 0), VecEq({0, 0}));
329-
EXPECT_THAT(Lerp(Vec{0, 0}, Vec{0, 2}, 0.75), VecEq({0, 1.5}));
330-
EXPECT_THAT(Lerp(Vec{0, 0}, Vec{0, 2}, 1), VecEq({0, 2}));
331-
EXPECT_THAT(Lerp(Vec{0, 0}, Vec{0, 2}, 2), VecEq({0, 4}));
332-
333-
EXPECT_THAT(Lerp(Vec{-3, 0}, Vec{0, 0}, -1), VecEq({-6, 0}));
334-
EXPECT_THAT(Lerp(Vec{-3, 0}, Vec{0, 0}, 0), VecEq({-3, 0}));
335-
EXPECT_THAT(Lerp(Vec{-3, 0}, Vec{0, 0}, 0.5), VecEq({-1.5, 0}));
336-
EXPECT_THAT(Lerp(Vec{-3, 0}, Vec{0, 0}, 1), VecEq({0, 0}));
337-
EXPECT_THAT(Lerp(Vec{-3, 0}, Vec{0, 0}, 2), VecEq({3, 0}));
338-
EXPECT_THAT(Lerp(Vec{0, 0}, Vec{0, 0}, -1), VecEq({0, 0}));
339-
EXPECT_THAT(Lerp(Vec{0, 0}, Vec{0, 0}, 0), VecEq({0, 0}));
340-
EXPECT_THAT(Lerp(Vec{0, 0}, Vec{0, 0}, 0.2), VecEq({0, 0}));
341-
EXPECT_THAT(Lerp(Vec{0, 0}, Vec{0, 0}, 1), VecEq({0, 0}));
342-
EXPECT_THAT(Lerp(Vec{0, 0}, Vec{0, 0}, 2), VecEq({0, 0}));
343-
}
344-
345166
TEST(SegmentIntersectionRatioTest, NoIntersection) {
346167
// These segments cover all three non-intersection cases: skew but not
347168
// touching, parallel but offset, and parallel on the same line but

0 commit comments

Comments
 (0)