From 789fe66bb8fecd066288d6b7b4466da5d3026ded Mon Sep 17 00:00:00 2001 From: Samuel Freilich Date: Thu, 24 Mar 2022 09:47:28 -0700 Subject: Handle start==end correctly in Normalize In particular, Clamp01(Normalize(x, x, y)) should be 1 if y > x and 0 if y < x. (The real ambiguous case is where start == end == value, in which case we're still having Clamp01(Normalize(x, x, x)) be 0 arbitrarily.) Instead, rename Normalize to Normalize01, clamp the results to the range [0, 1], and return 0 if value < start and 1 if value > end. This allows us to pick the correct (none or all) amount of interpolation in WobbleSmoother when WobbleSmootherParams.speed_floor == WobbleSmootherParams.speed_ceiling, which is permitted. PiperOrigin-RevId: 437017353 --- .../internal/prediction/kalman_predictor.cc | 16 +++++++-------- ink_stroke_modeler/internal/utils.h | 11 +++++++---- ink_stroke_modeler/internal/utils_test.cc | 13 ++++++++---- ink_stroke_modeler/internal/wobble_smoother.cc | 2 +- .../internal/wobble_smoother_test.cc | 23 ++++++++++++++++++++++ 5 files changed, 48 insertions(+), 17 deletions(-) diff --git a/ink_stroke_modeler/internal/prediction/kalman_predictor.cc b/ink_stroke_modeler/internal/prediction/kalman_predictor.cc index ab24540..cc8949c 100644 --- a/ink_stroke_modeler/internal/prediction/kalman_predictor.cc +++ b/ink_stroke_modeler/internal/prediction/kalman_predictor.cc @@ -226,8 +226,8 @@ int KalmanPredictor::NumberOfPointsToPredict( float estimated_error = Distance(*last_position_received_, estimated_state.position); float normalized_error = - Clamp01(1.f - Normalize(0.f, confidence_params.max_estimation_distance, - estimated_error)); + 1.f - Normalize01(0.f, confidence_params.max_estimation_distance, + estimated_error); // This is the state that the prediction would end at if we predicted the full // interval (i.e. if confidence == 1). @@ -240,8 +240,8 @@ int KalmanPredictor::NumberOfPointsToPredict( Distance(estimated_state.position, end_state.position) / static_cast(predictor_params_.prediction_interval.Value()); float normalized_distance = - Clamp01(Normalize(confidence_params.min_travel_speed, - confidence_params.max_travel_speed, travel_speed)); + Normalize01(confidence_params.min_travel_speed, + confidence_params.max_travel_speed, travel_speed); // If the actual prediction differs too much from the linear prediction, it // suggests that the acceleration and jerk components overtake the velocity, @@ -251,10 +251,10 @@ int KalmanPredictor::NumberOfPointsToPredict( estimated_state.position + static_cast(predictor_params_.prediction_interval.Value()) * estimated_state.velocity); - float linearity = Interp( - confidence_params.baseline_linearity_confidence, 1.f, - 1.f - Clamp01(Normalize(0.f, confidence_params.max_linear_deviation, - deviation_from_linear_prediction))); + float linearity = + Interp(confidence_params.baseline_linearity_confidence, 1.f, + 1.f - Normalize01(0.f, confidence_params.max_linear_deviation, + deviation_from_linear_prediction)); auto confidence = sample_ratio * normalized_error * normalized_distance * linearity; diff --git a/ink_stroke_modeler/internal/utils.h b/ink_stroke_modeler/internal/utils.h index bd648b7..6bf2a27 100644 --- a/ink_stroke_modeler/internal/utils.h +++ b/ink_stroke_modeler/internal/utils.h @@ -34,10 +34,13 @@ inline float Clamp01(float value) { } // Returns the ratio of the difference from `start` to `value` and the -// difference from `start` to `end`. -inline float Normalize(float start, float end, float value) { - if (start == end) return 0; - return (value - start) / (end - start); +// difference from `start` to `end`, clamped to the range [0, 1]. If +// `start` == `end`, returns 1 if `value` > `start`, 0 otherwise. +inline float Normalize01(float start, float end, float value) { + if (start == end) { + return value > start ? 1 : 0; + } + return Clamp01((value - start) / (end - start)); } // Linearly interpolates between `start` and `end`, clamping the interpolation diff --git a/ink_stroke_modeler/internal/utils_test.cc b/ink_stroke_modeler/internal/utils_test.cc index 426cc27..b50d21a 100644 --- a/ink_stroke_modeler/internal/utils_test.cc +++ b/ink_stroke_modeler/internal/utils_test.cc @@ -14,6 +14,8 @@ #include "ink_stroke_modeler/internal/utils.h" +#include + #include "gmock/gmock.h" #include "gtest/gtest.h" #include "ink_stroke_modeler/internal/type_matchers.h" @@ -31,10 +33,13 @@ TEST(UtilsTest, Clamp01) { EXPECT_FLOAT_EQ(Clamp01(1.1), 1); } -TEST(UtilsTest, Normalize) { - EXPECT_FLOAT_EQ(Normalize(1, 2, 1.5), .5); - EXPECT_FLOAT_EQ(Normalize(7, 3, 4), .75); - EXPECT_FLOAT_EQ(Normalize(-1, 1, 2), 1.5); +TEST(UtilsTest, Normalize01) { + EXPECT_FLOAT_EQ(Normalize01(1, 2, 1.5), .5); + EXPECT_FLOAT_EQ(Normalize01(7, 3, 4), .75); + EXPECT_FLOAT_EQ(Normalize01(-1, 1, 2), 1); + EXPECT_FLOAT_EQ(Normalize01(1, 1, 1), 0); + EXPECT_FLOAT_EQ(Normalize01(1, 1, 0), 0); + EXPECT_FLOAT_EQ(Normalize01(1, 1, 2), 1); } TEST(UtilsTest, InterpFloat) { diff --git a/ink_stroke_modeler/internal/wobble_smoother.cc b/ink_stroke_modeler/internal/wobble_smoother.cc index 96fe90d..a403ecb 100644 --- a/ink_stroke_modeler/internal/wobble_smoother.cc +++ b/ink_stroke_modeler/internal/wobble_smoother.cc @@ -63,7 +63,7 @@ Vec2 WobbleSmoother::Update(Vec2 position, Time time) { float avg_speed = speed_sum_ / samples_.size(); return Interp( avg_position, position, - Normalize(params_.speed_floor, params_.speed_ceiling, avg_speed)); + Normalize01(params_.speed_floor, params_.speed_ceiling, avg_speed)); } } // namespace stroke_model diff --git a/ink_stroke_modeler/internal/wobble_smoother_test.cc b/ink_stroke_modeler/internal/wobble_smoother_test.cc index f31ac9d..7bffd37 100644 --- a/ink_stroke_modeler/internal/wobble_smoother_test.cc +++ b/ink_stroke_modeler/internal/wobble_smoother_test.cc @@ -37,6 +37,18 @@ TEST(WobbleSmootherTest, SlowStraightLine) { EXPECT_THAT(filter.Update({3.064, 4}, Time{1.064}), Vec2Eq({3.048, 4})); } +TEST(WobbleSmootherTest, SlowStraightLineEqualFloorAndCeiling) { + // The line moves at 1 cm/s, which is below the floor of 1.31 cm/s. + WobbleSmootherParams equal_floor_and_ceiling_params{ + .timeout = Duration(.04), .speed_floor = 1.31, .speed_ceiling = 1.31}; + WobbleSmoother filter; + filter.Reset(equal_floor_and_ceiling_params, {3, 4}, Time{1}); + EXPECT_THAT(filter.Update({3.016, 4}, Time{1.016}), Vec2Eq({3.008, 4})); + EXPECT_THAT(filter.Update({3.032, 4}, Time{1.032}), Vec2Eq({3.016, 4})); + EXPECT_THAT(filter.Update({3.048, 4}, Time{1.048}), Vec2Eq({3.032, 4})); + EXPECT_THAT(filter.Update({3.064, 4}, Time{1.064}), Vec2Eq({3.048, 4})); +} + TEST(WobbleSmootherTest, FastStraightLine) { // The line moves at 1.5 cm/s, which is above the ceiling of 1.44 cm/s. WobbleSmoother filter; @@ -46,6 +58,17 @@ TEST(WobbleSmootherTest, FastStraightLine) { EXPECT_THAT(filter.Update({-1, .072}, Time{.048}), Vec2Eq({-1, .072})); } +TEST(WobbleSmootherTest, FastStraightLineEqualFloorAndCeiling) { + // The line moves at 1.5 cm/s, which is above the ceiling of 1.44 cm/s. + WobbleSmoother filter; + WobbleSmootherParams equal_floor_and_ceiling_params{ + .timeout = Duration(.04), .speed_floor = 1.41, .speed_ceiling = 1.41}; + filter.Reset(equal_floor_and_ceiling_params, {-1, 0}, Time{0}); + EXPECT_THAT(filter.Update({-1, .024}, Time{.016}), Vec2Eq({-1, .024})); + EXPECT_THAT(filter.Update({-1, .048}, Time{.032}), Vec2Eq({-1, .048})); + EXPECT_THAT(filter.Update({-1, .072}, Time{.048}), Vec2Eq({-1, .072})); +} + TEST(WobbleSmootherTest, SlowZigZag) { // The line moves at 1 cm/s, which is below the floor of 1.31 cm/s. WobbleSmoother filter; -- cgit v1.2.3