diff --git a/ink_stroke_modeler/internal/BUILD.bazel b/ink_stroke_modeler/internal/BUILD.bazel index a68cc7e..b6f2f94 100644 --- a/ink_stroke_modeler/internal/BUILD.bazel +++ b/ink_stroke_modeler/internal/BUILD.bazel @@ -24,6 +24,7 @@ cc_library( hdrs = ["internal_types.h"], deps = [ "//ink_stroke_modeler:types", + "@com_google_absl//absl/strings", "@com_google_absl//absl/strings:str_format", ], ) @@ -68,6 +69,7 @@ cc_test( ":type_matchers", "//ink_stroke_modeler:numbers", "//ink_stroke_modeler:params", + "//ink_stroke_modeler:types", "@com_google_googletest//:gtest_main", ], ) diff --git a/ink_stroke_modeler/internal/internal_types.cc b/ink_stroke_modeler/internal/internal_types.cc index 948d817..a6a183b 100644 --- a/ink_stroke_modeler/internal/internal_types.cc +++ b/ink_stroke_modeler/internal/internal_types.cc @@ -2,6 +2,7 @@ #include +#include "absl/strings/str_cat.h" #include "absl/strings/str_format.h" namespace ink { @@ -14,9 +15,23 @@ std::string ToFormattedString(const TipState &tip_state) { } std::string ToFormattedString(const StylusState &stylus_state) { - return absl::StrFormat( - "", - stylus_state.pressure, stylus_state.tilt, stylus_state.orientation); + std::string formatted = absl::StrFormat( + "'); + return formatted; } } // namespace stroke_model diff --git a/ink_stroke_modeler/internal/internal_types.h b/ink_stroke_modeler/internal/internal_types.h index 053428c..407f0c6 100644 --- a/ink_stroke_modeler/internal/internal_types.h +++ b/ink_stroke_modeler/internal/internal_types.h @@ -17,6 +17,7 @@ #ifndef INK_STROKE_MODELER_INTERNAL_INTERNAL_TYPES_H_ #define INK_STROKE_MODELER_INTERNAL_INTERNAL_TYPES_H_ +#include #include #include "ink_stroke_modeler/types.h" @@ -46,6 +47,9 @@ struct StylusState { float pressure = -1; float tilt = -1; float orientation = -1; + Vec2 projected_position = {0, 0}; + std::optional projected_velocity = std::nullopt; + std::optional projected_acceleration = std::nullopt; }; bool operator==(const StylusState& lhs, const StylusState& rhs); @@ -62,7 +66,10 @@ void AbslStringify(Sink& sink, const StylusState& stylus_state) { inline bool operator==(const StylusState& lhs, const StylusState& rhs) { return lhs.pressure == rhs.pressure && lhs.tilt == rhs.tilt && - lhs.orientation == rhs.orientation; + lhs.orientation == rhs.orientation && + lhs.projected_position == rhs.projected_position && + lhs.projected_velocity == rhs.projected_velocity && + lhs.projected_acceleration == rhs.projected_acceleration; } } // namespace stroke_model diff --git a/ink_stroke_modeler/internal/internal_types_test.cc b/ink_stroke_modeler/internal/internal_types_test.cc index 62c267e..54b455e 100644 --- a/ink_stroke_modeler/internal/internal_types_test.cc +++ b/ink_stroke_modeler/internal/internal_types_test.cc @@ -1,5 +1,7 @@ #include "ink_stroke_modeler/internal/internal_types.h" +#include + #include "gtest/gtest.h" #include "absl/strings/str_format.h" #include "ink_stroke_modeler/types.h" @@ -17,13 +19,51 @@ TEST(InternalTypesTest, TipStateString) { } TEST(InternalTypesTest, StylusStateEquals) { - EXPECT_EQ((StylusState{0.1, 0.2, 0.3}), (StylusState{0.1, 0.2, 0.3})); - EXPECT_FALSE((StylusState{0.1, 0.2, 0.3}) == (StylusState{0.1, 0.02, 0.3})); + EXPECT_EQ((StylusState{0.1, + 0.2, + 0.3, + {1, 2}, + std::optional({3, 4}), + std::optional({5, 6})}), + (StylusState{0.1, + 0.2, + 0.3, + {1, 2}, + std::optional({3, 4}), + std::optional({5, 6})})); + EXPECT_FALSE((StylusState{0.1, + 0.2, + 0.3, + {1, 2}, + std::optional({3, 4}), + std::optional({5, 6})}) == + (StylusState{0.1, + 0.02, + 0.3, + {1, 2}, + std::optional({3, 4}), + std::optional({5, 6})})); + EXPECT_FALSE( + (StylusState{0.1, + 0.2, + 0.3, + {1, 2}, + std::optional({3, 4}), + std::optional({5, 6})}) == + (StylusState{ + 0.1, 0.2, 0.3, {1, 2}, std::nullopt, std::optional({5, 6})})); } TEST(InternalTypesTest, StylusStateString) { - EXPECT_EQ(absl::StrFormat("%v", StylusState{0.1, 0.2, 0.3}), - ""); + EXPECT_EQ(absl::StrFormat("%v", StylusState{0.1, + 0.2, + 0.3, + {1, 2}, + std::optional({3, 4}), + std::optional({5, 6})}), + ""); } } // namespace diff --git a/ink_stroke_modeler/internal/stylus_state_modeler.cc b/ink_stroke_modeler/internal/stylus_state_modeler.cc index b9cefd3..d28522d 100644 --- a/ink_stroke_modeler/internal/stylus_state_modeler.cc +++ b/ink_stroke_modeler/internal/stylus_state_modeler.cc @@ -16,6 +16,7 @@ #include #include +#include #include "ink_stroke_modeler/internal/internal_types.h" #include "ink_stroke_modeler/internal/utils.h" @@ -25,7 +26,7 @@ namespace ink { namespace stroke_model { -void StylusStateModeler::Update(Vec2 position, const StylusState &state) { +void StylusStateModeler::Update(double time, const StylusState &state) { // Possibly NaN should be prohibited in ValidateInput, but due to current // consumers, that can't be tightened for these values currently. if (state.pressure < 0 || std::isnan(state.pressure)) { @@ -38,42 +39,59 @@ void StylusStateModeler::Update(Vec2 position, const StylusState &state) { state_.received_unknown_orientation = true; } - if (state_.received_unknown_pressure && state_.received_unknown_tilt && - state_.received_unknown_orientation) { - // We've stopped tracking all fields, so there's no need to keep updating. - state_.positions_and_stylus_states.clear(); - return; + Vec2 velocity = {0, 0}; + Vec2 acceleration = {0, 0}; + if (!state_.stylus_states.empty()) { + velocity = (state.projected_position - + state_.stylus_states.back().projected_position) / + (time - last_time_); + acceleration = + (velocity - *state_.stylus_states.back().projected_velocity) / + (time - last_time_); } - state_.positions_and_stylus_states.push_back({position, state}); + state_.stylus_states.push_back( + {.pressure = state.pressure, + .tilt = state.tilt, + .orientation = state.orientation, + .projected_position = state.projected_position, + .projected_velocity = velocity, + .projected_acceleration = acceleration}); if (params_.max_input_samples < 0 || - state_.positions_and_stylus_states.size() > + state_.stylus_states.size() > static_cast(params_.max_input_samples)) { - state_.positions_and_stylus_states.pop_front(); + state_.stylus_states.pop_front(); } + last_time_ = time; } void StylusStateModeler::Reset(const StylusStateModelerParams ¶ms) { - state_.positions_and_stylus_states.clear(); + state_.stylus_states.clear(); state_.received_unknown_pressure = false; state_.received_unknown_tilt = false; state_.received_unknown_orientation = false; save_active_ = false; params_ = params; + last_time_ = 0; } StylusState StylusStateModeler::Query(Vec2 position) const { - if (state_.positions_and_stylus_states.empty()) - return {.pressure = -1, .tilt = -1, .orientation = -1}; + if (state_.stylus_states.empty()) + return {.pressure = -1, + .tilt = -1, + .orientation = -1, + .projected_position = {0, 0}, + .projected_velocity = std::nullopt, + .projected_acceleration = std::nullopt}; int closest_segment_index = -1; float min_distance = std::numeric_limits::infinity(); float interp_value = 0; - for (decltype(state_.positions_and_stylus_states.size()) i = 0; - i < state_.positions_and_stylus_states.size() - 1; ++i) { - const Vec2 segment_start = state_.positions_and_stylus_states[i].position; - const Vec2 segment_end = state_.positions_and_stylus_states[i + 1].position; + for (decltype(state_.stylus_states.size()) i = 0; + i < state_.stylus_states.size() - 1; ++i) { + const Vec2 segment_start = state_.stylus_states[i].projected_position; + const Vec2 segment_end = state_.stylus_states[i + 1].projected_position; float param = NearestPointOnSegment(segment_start, segment_end, position); float distance = Distance(position, Interp(segment_start, segment_end, param)); @@ -85,17 +103,22 @@ StylusState StylusStateModeler::Query(Vec2 position) const { } if (closest_segment_index < 0) { - const auto &state = state_.positions_and_stylus_states.front().state; + const auto &state = state_.stylus_states.front(); return {.pressure = state_.received_unknown_pressure ? -1 : state.pressure, .tilt = state_.received_unknown_tilt ? -1 : state.tilt, .orientation = - state_.received_unknown_orientation ? -1 : state.orientation}; + state_.received_unknown_orientation ? -1 : state.orientation, + .projected_position = state.projected_position, + .projected_velocity = state.projected_velocity.has_value() + ? state.projected_velocity + : std::nullopt, + .projected_acceleration = state.projected_acceleration.has_value() + ? state.projected_acceleration + : std::nullopt}; } - auto from_state = - state_.positions_and_stylus_states[closest_segment_index].state; - auto to_state = - state_.positions_and_stylus_states[closest_segment_index + 1].state; + auto from_state = state_.stylus_states[closest_segment_index]; + auto to_state = state_.stylus_states[closest_segment_index + 1]; return StylusState{ .pressure = state_.received_unknown_pressure @@ -107,16 +130,36 @@ StylusState StylusStateModeler::Query(Vec2 position) const { .orientation = state_.received_unknown_orientation ? -1 : InterpAngle(from_state.orientation, - to_state.orientation, interp_value)}; + to_state.orientation, interp_value), + .projected_position = Interp(from_state.projected_position, + to_state.projected_position, interp_value), + .projected_velocity = + (from_state.projected_velocity.has_value() && + to_state.projected_velocity.has_value()) + ? std::optional(Interp(*from_state.projected_velocity, + *to_state.projected_velocity, + interp_value)) + : std::nullopt, + .projected_acceleration = + (from_state.projected_acceleration.has_value() && + to_state.projected_acceleration.has_value()) + ? std::optional(Interp(*from_state.projected_acceleration, + *to_state.projected_acceleration, + interp_value)) + : std::nullopt}; } void StylusStateModeler::Save() { saved_state_ = state_; save_active_ = true; + saved_last_time_ = last_time_; } void StylusStateModeler::Restore() { - if (save_active_) state_ = saved_state_; + if (save_active_) { + state_ = saved_state_; + last_time_ = saved_last_time_; + } } } // namespace stroke_model diff --git a/ink_stroke_modeler/internal/stylus_state_modeler.h b/ink_stroke_modeler/internal/stylus_state_modeler.h index 4352794..9419d5e 100644 --- a/ink_stroke_modeler/internal/stylus_state_modeler.h +++ b/ink_stroke_modeler/internal/stylus_state_modeler.h @@ -18,7 +18,6 @@ #define INK_STROKE_MODELER_INTERNAL_STYLUS_STATE_MODELER_H_ #include -#include #include "ink_stroke_modeler/internal/internal_types.h" #include "ink_stroke_modeler/params.h" @@ -47,7 +46,7 @@ class StylusStateModeler { public: // Adds a position and state pair to the model. During stroke modeling, these // values will be taken from the raw input. - void Update(Vec2 position, const StylusState &state); + void Update(double time, const StylusState &state); // Clear the model and reset. void Reset(const StylusStateModelerParams ¶ms); @@ -68,23 +67,16 @@ class StylusStateModeler { void Restore(); private: - struct PositionAndStylusState { - Vec2 position{0}; - StylusState state; - - PositionAndStylusState(Vec2 position_in, const StylusState &state_in) - : position(position_in), state(state_in) {} - }; - struct ModelerState { bool received_unknown_pressure = false; bool received_unknown_tilt = false; bool received_unknown_orientation = false; - std::deque positions_and_stylus_states; + std::deque stylus_states; }; ModelerState state_; + double last_time_ = 0; // Use a ModelerState + bool instead of optional for // performance. ModelerState contains a std::deque, which has a non-trivial @@ -93,6 +85,7 @@ class StylusStateModeler { // std::optional::reset(). ModelerState saved_state_; bool save_active_ = false; + double saved_last_time_ = 0; StylusStateModelerParams params_; }; diff --git a/ink_stroke_modeler/internal/stylus_state_modeler_test.cc b/ink_stroke_modeler/internal/stylus_state_modeler_test.cc index a10ca5c..787c92e 100644 --- a/ink_stroke_modeler/internal/stylus_state_modeler_test.cc +++ b/ink_stroke_modeler/internal/stylus_state_modeler_test.cc @@ -14,19 +14,27 @@ #include "ink_stroke_modeler/internal/stylus_state_modeler.h" +#include + #include "gmock/gmock.h" #include "gtest/gtest.h" #include "ink_stroke_modeler/internal/internal_types.h" #include "ink_stroke_modeler/internal/type_matchers.h" #include "ink_stroke_modeler/numbers.h" #include "ink_stroke_modeler/params.h" +#include "ink_stroke_modeler/types.h" namespace ink { namespace stroke_model { namespace { constexpr float kTol = 1e-5; -constexpr StylusState kUnknown{.pressure = -1, .tilt = -1, .orientation = -1}; +constexpr StylusState kUnknown{.pressure = -1, + .tilt = -1, + .orientation = -1, + .projected_position = {0, 0}, + .projected_velocity = std::nullopt, + .projected_acceleration = std::nullopt}; TEST(StylusStateModelerTest, QueryEmpty) { StylusStateModeler modeler; @@ -36,101 +44,277 @@ TEST(StylusStateModelerTest, QueryEmpty) { TEST(StylusStateModelerTest, QuerySingleInput) { StylusStateModeler modeler; - modeler.Update({0, 0}, {.pressure = 0.75, .tilt = 0.75, .orientation = 0.75}); - EXPECT_THAT(modeler.Query({0, 0}), - StylusStateNear( - {.pressure = .75, .tilt = .75, .orientation = .75}, kTol)); - EXPECT_THAT(modeler.Query({1, 1}), - StylusStateNear( - {.pressure = .75, .tilt = .75, .orientation = .75}, kTol)); + modeler.Update(0, {.pressure = 0.75, + .tilt = 0.75, + .orientation = 0.75, + .projected_position = {0, 0}}); + EXPECT_THAT( + modeler.Query({0, 0}), + StylusStateNear({.pressure = .75, + .tilt = .75, + .orientation = .75, + .projected_position = {0, 0}, + .projected_velocity = std::optional({0, 0}), + .projected_acceleration = std::optional({0, 0})}, + kTol)); + EXPECT_THAT( + modeler.Query({1, 1}), + StylusStateNear({.pressure = .75, + .tilt = .75, + .orientation = .75, + .projected_position = {0, 0}, + .projected_velocity = std::optional({0, 0}), + .projected_acceleration = std::optional({0, 0})}, + kTol)); } TEST(StylusStateModelerTest, QueryMultipleInputs) { StylusStateModeler modeler; - modeler.Update({.5, 1.5}, {.pressure = .3, .tilt = .8, .orientation = .1}); - modeler.Update({2, 1.5}, {.pressure = .6, .tilt = .5, .orientation = .7}); - modeler.Update({3, 3.5}, {.pressure = .8, .tilt = .1, .orientation = .3}); - modeler.Update({3.5, 4}, {.pressure = .2, .tilt = .2, .orientation = .2}); + modeler.Update(0, {.pressure = .3, + .tilt = .8, + .orientation = .1, + .projected_position = {.5, 1.5}}); + modeler.Update(1, {.pressure = .6, + .tilt = .5, + .orientation = .7, + .projected_position = {2, 1.5}}); + modeler.Update(2, {.pressure = .8, + .tilt = .1, + .orientation = .3, + .projected_position = {3, 3.5}}); + modeler.Update(3, {.pressure = .2, + .tilt = .2, + .orientation = .2, + .projected_position = {3.5, 4}}); EXPECT_THAT( modeler.Query({0, 2}), - StylusStateNear({.pressure = .3, .tilt = .8, .orientation = .1}, kTol)); + StylusStateNear({.pressure = .3, + .tilt = .8, + .orientation = .1, + .projected_position = {.5, 1.5}, + .projected_velocity = std::optional({0, 0}), + .projected_acceleration = std::optional({0, 0})}, + kTol)); EXPECT_THAT( modeler.Query({1, 2}), - StylusStateNear({.pressure = .4, .tilt = .7, .orientation = .3}, kTol)); + StylusStateNear({.pressure = .4, + .tilt = .7, + .orientation = .3, + .projected_position = {1, 1.5}, + .projected_velocity = std::optional({0.5, 0}), + .projected_acceleration = std::optional({0.5, 0})}, + kTol)); EXPECT_THAT( modeler.Query({2, 1.5}), - StylusStateNear({.pressure = .6, .tilt = .5, .orientation = .7}, kTol)); + StylusStateNear({.pressure = .6, + .tilt = .5, + .orientation = .7, + .projected_position = {2, 1.5}, + .projected_velocity = std::optional({1.5, 0}), + .projected_acceleration = std::optional({1.5, 0})}, + kTol)); EXPECT_THAT( modeler.Query({2.5, 1.875}), - StylusStateNear({.pressure = .65, .tilt = .4, .orientation = .6}, kTol)); + StylusStateNear({.pressure = .65, + .tilt = .4, + .orientation = .6, + .projected_position = {2.25, 2}, + .projected_velocity = std::optional({1.375, 0.5}), + .projected_acceleration = std::optional({1, 0.5})}, + kTol)); EXPECT_THAT( modeler.Query({2.5, 3.125}), - StylusStateNear({.pressure = .75, .tilt = .2, .orientation = .4}, kTol)); - EXPECT_THAT( - modeler.Query({2.5, 4}), - StylusStateNear({.pressure = .8, .tilt = .1, .orientation = .3}, kTol)); - EXPECT_THAT( - modeler.Query({3, 4}), - StylusStateNear({.pressure = .5, .tilt = .15, .orientation = .25}, kTol)); - EXPECT_THAT( - modeler.Query({4, 4}), - StylusStateNear({.pressure = .2, .tilt = .2, .orientation = .2}, kTol)); + StylusStateNear({.pressure = .75, + .tilt = .2, + .orientation = .4, + .projected_position = {2.75, 3}, + .projected_velocity = std::optional({1.125, 1.5}), + .projected_acceleration = std::optional({0, 1.5})}, + kTol)); + EXPECT_THAT(modeler.Query({2.5, 4}), + StylusStateNear( + {.pressure = .8, + .tilt = .1, + .orientation = .3, + .projected_position = {3, 3.5}, + .projected_velocity = std::optional({1, 2}), + .projected_acceleration = std::optional({-0.5, 2})}, + kTol)); + EXPECT_THAT(modeler.Query({3, 4}), + StylusStateNear( + {.pressure = .5, + .tilt = .15, + .orientation = .25, + .projected_position = {3.25, 3.75}, + .projected_velocity = std::optional({0.75, 1.25}), + .projected_acceleration = std::optional({-0.5, 0.25})}, + kTol)); + EXPECT_THAT(modeler.Query({4, 4}), + StylusStateNear( + {.pressure = .2, + .tilt = .2, + .orientation = .2, + .projected_position = {3.5, 4}, + .projected_velocity = std::optional({0.5, 0.5}), + .projected_acceleration = std::optional({-0.5, -1.5})}, + kTol)); } TEST(StylusStateModelerTest, QueryStaleInputsAreDiscarded) { StylusStateModeler modeler; - modeler.Update({1, 1}, {.pressure = .6, .tilt = .5, .orientation = .4}); - modeler.Update({-1, 2}, {.pressure = .3, .tilt = .7, .orientation = .6}); - modeler.Update({-4, 0}, {.pressure = .9, .tilt = .7, .orientation = .3}); - modeler.Update({-6, -3}, {.pressure = .4, .tilt = .3, .orientation = .5}); - modeler.Update({-5, -5}, {.pressure = .3, .tilt = .3, .orientation = .1}); - modeler.Update({-3, -4}, {.pressure = .6, .tilt = .8, .orientation = .3}); - modeler.Update({-6, -7}, {.pressure = .9, .tilt = .8, .orientation = .1}); - modeler.Update({-9, -8}, {.pressure = .8, .tilt = .2, .orientation = .2}); - modeler.Update({-11, -5}, {.pressure = .2, .tilt = .4, .orientation = .7}); - modeler.Update({-10, -2}, {.pressure = .7, .tilt = .3, .orientation = .2}); + modeler.Update(0, {.pressure = .6, + .tilt = .5, + .orientation = .4, + .projected_position = {1, 1}}); + modeler.Update(1, {.pressure = .3, + .tilt = .7, + .orientation = .6, + .projected_position = {-1, 2}}); + modeler.Update(2, {.pressure = .9, + .tilt = .7, + .orientation = .3, + .projected_position = {-4, 0}}); + modeler.Update(3, {.pressure = .4, + .tilt = .3, + .orientation = .5, + .projected_position = {-6, -3}}); + modeler.Update(4, {.pressure = .3, + .tilt = .3, + .orientation = .1, + .projected_position = {-5, -5}}); + modeler.Update(5, {.pressure = .6, + .tilt = .8, + .orientation = .3, + .projected_position = {-3, -4}}); + modeler.Update(6, {.pressure = .9, + .tilt = .8, + .orientation = .1, + .projected_position = {-6, -7}}); + modeler.Update(7, {.pressure = .8, + .tilt = .2, + .orientation = .2, + .projected_position = {-9, -8}}); + modeler.Update(8, {.pressure = .2, + .tilt = .4, + .orientation = .7, + .projected_position = {-11, -5}}); + modeler.Update(9, {.pressure = .7, + .tilt = .3, + .orientation = .2, + .projected_position = {-10, -2}}); EXPECT_THAT( modeler.Query({2, 0}), - StylusStateNear({.pressure = .6, .tilt = .5, .orientation = .4}, kTol)); - EXPECT_THAT( - modeler.Query({1, 3.5}), - StylusStateNear({.pressure = .45, .tilt = .6, .orientation = .5}, kTol)); + StylusStateNear({.pressure = .6, + .tilt = .5, + .orientation = .4, + .projected_position = {1, 1}, + .projected_velocity = std::optional({0, 0}), + .projected_acceleration = std::optional({0, 0})}, + kTol)); + EXPECT_THAT(modeler.Query({1, 3.5}), + StylusStateNear( + {.pressure = .45, + .tilt = .6, + .orientation = .5, + .projected_position = {0, 1.5}, + .projected_velocity = std::optional({-1, 0.5}), + .projected_acceleration = std::optional({-1, 0.5})}, + kTol)); EXPECT_THAT( modeler.Query({-3, 17. / 6}), - StylusStateNear({.pressure = .5, .tilt = .7, .orientation = .5}, kTol)); + StylusStateNear( + {.pressure = .5, + .tilt = .7, + .orientation = .5, + .projected_position = {-2, 4. / 3.}, + .projected_velocity = std::optional({-7. / 3., 0}), + .projected_acceleration = std::optional({-5. / 3., -1. / 3.})}, + kTol)); // This causes the point at {1, 1} to be discarded. - modeler.Update({-8, 0}, {.pressure = .6, .tilt = .8, .orientation = .9}); + modeler.Update(10, {.pressure = .6, + .tilt = .8, + .orientation = .9, + .projected_position = {-8, 0}}); EXPECT_THAT( modeler.Query({2, 0}), - StylusStateNear({.pressure = .3, .tilt = .7, .orientation = .6}, kTol)); + StylusStateNear({.pressure = .3, + .tilt = .7, + .orientation = .6, + .projected_position = {-1, 2}, + .projected_velocity = std::optional({-2, 1}), + .projected_acceleration = std::optional({-2, 1})}, + kTol)); EXPECT_THAT( modeler.Query({1, 3.5}), - StylusStateNear({.pressure = .3, .tilt = .7, .orientation = .6}, kTol)); + StylusStateNear({.pressure = .3, + .tilt = .7, + .orientation = .6, + .projected_position = {-1, 2}, + .projected_velocity = std::optional({-2, 1}), + .projected_acceleration = std::optional({-2, 1})}, + kTol)); EXPECT_THAT( modeler.Query({-3, 17. / 6}), - StylusStateNear({.pressure = .5, .tilt = .7, .orientation = .5}, kTol)); + StylusStateNear( + {.pressure = .5, + .tilt = .7, + .orientation = .5, + .projected_position = {-2, 4. / 3.}, + .projected_velocity = std::optional({-7. / 3., 0}), + .projected_acceleration = std::optional({-5. / 3., -1. / 3.})}, + kTol)); // This causes the point at {-1, 2} to be discarded. - modeler.Update({-8, 0}, {.pressure = .6, .tilt = .8, .orientation = .9}); + modeler.Update(11, {.pressure = .6, + .tilt = .8, + .orientation = .9, + .projected_position = {-8, 0}}); EXPECT_THAT( modeler.Query({2, 0}), - StylusStateNear({.pressure = .9, .tilt = .7, .orientation = .3}, kTol)); + StylusStateNear({.pressure = .9, + .tilt = .7, + .orientation = .3, + .projected_position = {-4, 0}, + .projected_velocity = std::optional({-3, -2}), + .projected_acceleration = std::optional({-1, -3})}, + kTol)); EXPECT_THAT( modeler.Query({1, 3.5}), - StylusStateNear({.pressure = .9, .tilt = .7, .orientation = .3}, kTol)); + StylusStateNear({.pressure = .9, + .tilt = .7, + .orientation = .3, + .projected_position = {-4, 0}, + .projected_velocity = std::optional({-3, -2}), + .projected_acceleration = std::optional({-1, -3})}, + kTol)); EXPECT_THAT( modeler.Query({-3, 17. / 6}), - StylusStateNear({.pressure = .9, .tilt = .7, .orientation = .3}, kTol)); + StylusStateNear({.pressure = .9, + .tilt = .7, + .orientation = .3, + .projected_position = {-4, 0}, + .projected_velocity = std::optional({-3, -2}), + .projected_acceleration = std::optional({-1, -3})}, + kTol)); } TEST(StylusStateModelerTest, QueryCyclicOrientationInterpolation) { StylusStateModeler modeler; - modeler.Update({0, 0}, {.pressure = 0, .tilt = 0, .orientation = 1.8 * kPi}); - modeler.Update({0, 1}, {.pressure = 0, .tilt = 0, .orientation = .2 * kPi}); - modeler.Update({0, 2}, {.pressure = 0, .tilt = 0, .orientation = 1.6 * kPi}); + modeler.Update(0, {.pressure = 0, + .tilt = 0, + .orientation = 1.8 * kPi, + .projected_position = {0, 0}}); + modeler.Update(1, {.pressure = 0, + .tilt = 0, + .orientation = .2 * kPi, + .projected_position = {0, 1}}); + modeler.Update(2, {.pressure = 0, + .tilt = 0, + .orientation = 1.6 * kPi, + .projected_position = {0, 2}}); EXPECT_NEAR(modeler.Query({0, .25}).orientation, 1.9 * kPi, 1e-5); EXPECT_NEAR(modeler.Query({0, .75}).orientation, .1 * kPi, 1e-5); @@ -141,191 +325,486 @@ TEST(StylusStateModelerTest, QueryCyclicOrientationInterpolation) { TEST(StylusStateModelerTest, QueryAndReset) { StylusStateModeler modeler; - modeler.Update({4, 5}, {.pressure = .4, .tilt = .9, .orientation = .1}); - modeler.Update({7, 8}, {.pressure = .1, .tilt = .2, .orientation = .5}); + modeler.Update(0, {.pressure = .4, + .tilt = .9, + .orientation = .1, + .projected_position = {4, 5}}); + modeler.Update(1, {.pressure = .1, + .tilt = .2, + .orientation = .5, + .projected_position = {7, 8}}); EXPECT_THAT( modeler.Query({10, 12}), - StylusStateNear({.pressure = .1, .tilt = .2, .orientation = .5}, kTol)); + StylusStateNear({.pressure = .1, + .tilt = .2, + .orientation = .5, + .projected_position = {7, 8}, + .projected_velocity = std::optional({3, 3}), + .projected_acceleration = std::optional({3, 3})}, + kTol)); modeler.Reset(StylusStateModelerParams{}); EXPECT_EQ(modeler.Query({10, 12}), kUnknown); - modeler.Update({-1, 4}, {.pressure = .4, .tilt = .6, .orientation = .8}); + modeler.Update(2, {.pressure = .4, + .tilt = .6, + .orientation = .8, + .projected_position = {-1, 4}}); EXPECT_THAT( modeler.Query({6, 7}), - StylusStateNear({.pressure = .4, .tilt = .6, .orientation = .8}, kTol)); - - modeler.Update({-3, 0}, {.pressure = .7, .tilt = .2, .orientation = .5}); + StylusStateNear({.pressure = .4, + .tilt = .6, + .orientation = .8, + .projected_position = {-1, 4}, + .projected_velocity = std::optional({0, 0}), + .projected_acceleration = std::optional({0, 0})}, + kTol)); + + modeler.Update(3, {.pressure = .7, + .tilt = .2, + .orientation = .5, + .projected_position = {-3, 0}}); EXPECT_THAT( modeler.Query({-2, 2}), - StylusStateNear({.pressure = .55, .tilt = .4, .orientation = .65}, kTol)); + StylusStateNear({.pressure = .55, + .tilt = .4, + .orientation = .65, + .projected_position = {-2, 2}, + .projected_velocity = std::optional({-1, -2}), + .projected_acceleration = std::optional({-1, -2})}, + kTol)); EXPECT_THAT( modeler.Query({0, 5}), - StylusStateNear({.pressure = .4, .tilt = .6, .orientation = .8}, kTol)); + StylusStateNear({.pressure = .4, + .tilt = .6, + .orientation = .8, + .projected_position = {-1, 4}, + .projected_velocity = std::optional({0, 0}), + .projected_acceleration = std::optional({0, 0})}, + kTol)); } TEST(StylusStateModelerTest, UpdateWithUnknownState) { StylusStateModeler modeler; - modeler.Update({1, 2}, {.pressure = .1, .tilt = .2, .orientation = .3}); - modeler.Update({2, 3}, {.pressure = .3, .tilt = .4, .orientation = .5}); + modeler.Update(0, {.pressure = .1, + .tilt = .2, + .orientation = .3, + .projected_position = {1, 2}}); + modeler.Update(1, {.pressure = .3, + .tilt = .4, + .orientation = .5, + .projected_position = {2, 3}}); + EXPECT_THAT(modeler.Query({2, 2}), + StylusStateNear( + {.pressure = .2, + .tilt = .3, + .orientation = .4, + .projected_position = {1.5, 2.5}, + .projected_velocity = std::optional({0.5, 0.5}), + .projected_acceleration = std::optional({0.5, 0.5})}, + kTol)); + + StylusState unknown = kUnknown; + unknown.projected_position = {5, 5}; + modeler.Update(2, unknown); + EXPECT_THAT( + modeler.Query({5, 5}), + StylusStateNear({.pressure = -1, + .tilt = -1, + .orientation = -1, + .projected_position = {5, 5}, + .projected_velocity = std::optional({3, 2}), + .projected_acceleration = std::optional({2, 1})}, + kTol)); + + modeler.Update(3, {.pressure = .3, + .tilt = .4, + .orientation = .5, + .projected_position = {2, 3}}); + unknown.projected_position = {1, 2}; EXPECT_THAT( - modeler.Query({2, 2}), - StylusStateNear({.pressure = .2, .tilt = .3, .orientation = .4}, kTol)); - - modeler.Update({5, 5}, kUnknown); - EXPECT_EQ(modeler.Query({5, 5}), kUnknown); - - modeler.Update({2, 3}, {.pressure = .3, .tilt = .4, .orientation = .5}); - EXPECT_EQ(modeler.Query({1, 2}), kUnknown); - - modeler.Update({-1, 3}, kUnknown); - EXPECT_EQ(modeler.Query({7, 9}), kUnknown); + modeler.Query({1, 2}), + StylusStateNear({.pressure = -1, + .tilt = -1, + .orientation = -1, + .projected_position = {1, 2}, + .projected_velocity = std::optional({0, 0}), + .projected_acceleration = std::optional({0, 0})}, + kTol)); + + unknown.projected_position = {-1, 3}; + modeler.Update(4, unknown); + EXPECT_THAT( + modeler.Query({7, 9}), + StylusStateNear({.pressure = -1, + .tilt = -1, + .orientation = -1, + .projected_position = {5, 5}, + .projected_velocity = std::optional({3, 2}), + .projected_acceleration = std::optional({2, 1})}, + kTol)); modeler.Reset(StylusStateModelerParams{}); - modeler.Update({3, 3}, {.pressure = .7, .tilt = .6, .orientation = .5}); + modeler.Update(5, {.pressure = .7, + .tilt = .6, + .orientation = .5, + .projected_position = {3, 3}}); EXPECT_THAT( modeler.Query({3, 3}), - StylusStateNear({.pressure = .7, .tilt = .6, .orientation = .5}, kTol)); + StylusStateNear({.pressure = .7, + .tilt = .6, + .orientation = .5, + .projected_position = {3, 3}, + .projected_velocity = std::optional({0, 0}), + .projected_acceleration = std::optional({0, 0})}, + kTol)); } TEST(StylusStateModelerTest, ModelPressureOnly) { StylusStateModeler modeler; - modeler.Update({0, 0}, {.pressure = .5, .tilt = -2, .orientation = -.1}); + modeler.Update(0, {.pressure = .5, + .tilt = -2, + .orientation = -.1, + .projected_position = {0, 0}}); EXPECT_THAT( modeler.Query({1, 1}), - StylusStateNear({.pressure = .5, .tilt = -1, .orientation = -1}, kTol)); - - modeler.Update({2, 0}, {.pressure = .7, .tilt = -2, .orientation = -.1}); + StylusStateNear({.pressure = .5, + .tilt = -1, + .orientation = -1, + .projected_position = {0, 0}, + .projected_velocity = std::optional({0, 0}), + .projected_acceleration = std::optional({0, 0})}, + kTol)); + + modeler.Update(1, {.pressure = .7, + .tilt = -2, + .orientation = -.1, + .projected_position = {2, 0}}); EXPECT_THAT( modeler.Query({1, 1}), - StylusStateNear({.pressure = .6, .tilt = -1, .orientation = -1}, kTol)); + StylusStateNear({.pressure = .6, + .tilt = -1, + .orientation = -1, + .projected_position = {1, 0}, + .projected_velocity = std::optional({1, 0}), + .projected_acceleration = std::optional({1, 0})}, + kTol)); } TEST(StylusStateModelerTest, ModelTiltOnly) { StylusStateModeler modeler; - modeler.Update({0, 0}, {.pressure = -2, .tilt = .5, .orientation = -.1}); + modeler.Update(0, {.pressure = -2, + .tilt = .5, + .orientation = -.1, + .projected_position = {0, 0}}); EXPECT_THAT( modeler.Query({1, 1}), - StylusStateNear({.pressure = -1, .tilt = .5, .orientation = -1}, kTol)); - - modeler.Update({2, 0}, {.pressure = -2, .tilt = .3, .orientation = -.1}); + StylusStateNear({.pressure = -1, + .tilt = .5, + .orientation = -1, + .projected_position = {0, 0}, + .projected_velocity = std::optional({0, 0}), + .projected_acceleration = std::optional({0, 0})}, + kTol)); + + modeler.Update(1, {.pressure = -2, + .tilt = .3, + .orientation = -.1, + .projected_position = {2, 0}}); EXPECT_THAT( modeler.Query({1, 1}), - StylusStateNear({.pressure = -1, .tilt = .4, .orientation = -1}, kTol)); + StylusStateNear({.pressure = -1, + .tilt = .4, + .orientation = -1, + .projected_position = {1, 0}, + .projected_velocity = std::optional({1, 0}), + .projected_acceleration = std::optional({1, 0})}, + kTol)); } TEST(StylusStateModelerTest, ModelOrientationOnly) { StylusStateModeler modeler; - modeler.Update({0, 0}, {.pressure = -2, .tilt = -.1, .orientation = 1}); + modeler.Update(0, {.pressure = -2, + .tilt = -.1, + .orientation = 1, + .projected_position = {0, 0}}); EXPECT_THAT( modeler.Query({1, 1}), - StylusStateNear({.pressure = -1, .tilt = -1, .orientation = 1}, kTol)); - - modeler.Update({2, 0}, {.pressure = -2, .tilt = -.3, .orientation = 2}); + StylusStateNear({.pressure = -1, + .tilt = -1, + .orientation = 1, + .projected_position = {0, 0}, + .projected_velocity = std::optional({0, 0}), + .projected_acceleration = std::optional({0, 0})}, + kTol)); + + modeler.Update(1, {.pressure = -2, + .tilt = -.3, + .orientation = 2, + .projected_position = {2, 0}}); EXPECT_THAT( modeler.Query({1, 1}), - StylusStateNear({.pressure = -1, .tilt = -1, .orientation = 1.5}, kTol)); + StylusStateNear({.pressure = -1, + .tilt = -1, + .orientation = 1.5, + .projected_position = {1, 0}, + .projected_velocity = std::optional({1, 0}), + .projected_acceleration = std::optional({1, 0})}, + kTol)); } TEST(StylusStateModelerTest, DropFieldsOneByOne) { StylusStateModeler modeler; - modeler.Update({0, 0}, {.pressure = .5, .tilt = .5, .orientation = .5}); + modeler.Update(0, {.pressure = .5, + .tilt = .5, + .orientation = .5, + .projected_position = {0, 0}}); EXPECT_THAT( modeler.Query({1, 0}), - StylusStateNear({.pressure = .5, .tilt = .5, .orientation = .5}, kTol)); - - modeler.Update({2, 0}, {.pressure = .3, .tilt = .7, .orientation = -1}); + StylusStateNear({.pressure = .5, + .tilt = .5, + .orientation = .5, + .projected_position = {0, 0}, + .projected_velocity = std::optional({0, 0}), + .projected_acceleration = std::optional({0, 0})}, + kTol)); + + modeler.Update(1, {.pressure = .3, + .tilt = .7, + .orientation = -1, + .projected_position = {2, 0}}); EXPECT_THAT( modeler.Query({1, 0}), - StylusStateNear({.pressure = .4, .tilt = .6, .orientation = -1}, kTol)); - - modeler.Update({4, 0}, {.pressure = .1, .tilt = -1, .orientation = 1}); + StylusStateNear({.pressure = .4, + .tilt = .6, + .orientation = -1, + .projected_position = {1, 0}, + .projected_velocity = std::optional({1, 0}), + .projected_acceleration = std::optional({1, 0})}, + kTol)); + + modeler.Update(2, {.pressure = .1, + .tilt = -1, + .orientation = 1, + .projected_position = {4, 0}}); EXPECT_THAT( modeler.Query({3, 0}), - StylusStateNear({.pressure = .2, .tilt = -1, .orientation = -1}, kTol)); - - modeler.Update({6, 0}, {.pressure = -1, .tilt = .2, .orientation = 0}); - EXPECT_THAT(modeler.Query({5, 0}), StylusStateNear(kUnknown, kTol)); - - modeler.Update({8, 0}, {.pressure = .3, .tilt = .4, .orientation = .5}); - EXPECT_THAT(modeler.Query({7, 0}), StylusStateNear(kUnknown, kTol)); + StylusStateNear({.pressure = .2, + .tilt = -1, + .orientation = -1, + .projected_position = {3, 0}, + .projected_velocity = std::optional({2, 0}), + .projected_acceleration = std::optional({1, 0})}, + kTol)); + + modeler.Update(3, {.pressure = -1, + .tilt = .2, + .orientation = 0, + .projected_position = {6, 0}}); + StylusState unknown = kUnknown; + unknown.projected_position = {5, 0}; + EXPECT_THAT( + modeler.Query({5, 0}), + StylusStateNear({.pressure = -1, + .tilt = -1, + .orientation = -1, + .projected_position = {5, 0}, + .projected_velocity = std::optional({2, 0}), + .projected_acceleration = std::optional({0, 0})}, + kTol)); + + modeler.Update(4, {.pressure = .3, + .tilt = .4, + .orientation = .5, + .projected_position = {8, 0}}); + EXPECT_THAT( + modeler.Query({7, 0}), + StylusStateNear({.pressure = -1, + .tilt = -1, + .orientation = -1, + .projected_position = {7, 0}, + .projected_velocity = std::optional({2, 0}), + .projected_acceleration = std::optional({0, 0})}, + kTol)); modeler.Reset(StylusStateModelerParams{}); EXPECT_THAT(modeler.Query({1, 0}), StylusStateNear(kUnknown, kTol)); - modeler.Update({0, 0}, {.pressure = .1, .tilt = .8, .orientation = .3}); + modeler.Update(5, {.pressure = .1, + .tilt = .8, + .orientation = .3, + .projected_position = {0, 0}}); EXPECT_THAT( modeler.Query({1, 0}), - StylusStateNear({.pressure = .1, .tilt = .8, .orientation = .3}, kTol)); + StylusStateNear({.pressure = .1, + .tilt = .8, + .orientation = .3, + .projected_position = {0, 0}, + .projected_velocity = std::optional({0, 0}), + .projected_acceleration = std::optional({0, 0})}, + kTol)); } TEST(StylusStateModelerTest, SaveAndRestore) { StylusStateModeler modeler; - modeler.Update({1, 1}, {.pressure = .6, .tilt = .5, .orientation = .4}); - modeler.Update({-1, 2}, {.pressure = .3, .tilt = .7, .orientation = .6}); - modeler.Update({-4, 0}, {.pressure = .9, .tilt = .7, .orientation = .3}); - modeler.Update({-6, -3}, {.pressure = .4, .tilt = .3, .orientation = .5}); - modeler.Update({-5, -5}, {.pressure = .3, .tilt = .3, .orientation = .1}); - modeler.Update({-3, -4}, {.pressure = .6, .tilt = .8, .orientation = .3}); - modeler.Update({-6, -7}, {.pressure = .9, .tilt = .8, .orientation = .1}); - modeler.Update({-9, -8}, {.pressure = .8, .tilt = .2, .orientation = .2}); - modeler.Update({-11, -5}, {.pressure = .2, .tilt = .4, .orientation = .7}); - modeler.Update({-10, -2}, {.pressure = .7, .tilt = .3, .orientation = .2}); + modeler.Update(0, {.pressure = .6, + .tilt = .5, + .orientation = .4, + .projected_position = {1, 1}}); + modeler.Update(1, {.pressure = .3, + .tilt = .7, + .orientation = .6, + .projected_position = {-1, 2}}); + modeler.Update(2, {.pressure = .9, + .tilt = .7, + .orientation = .3, + .projected_position = {-4, 0}}); + modeler.Update(3, {.pressure = .4, + .tilt = .3, + .orientation = .5, + .projected_position = {-6, -3}}); + modeler.Update(4, {.pressure = .3, + .tilt = .3, + .orientation = .1, + .projected_position = {-5, -5}}); + modeler.Update(5, {.pressure = .6, + .tilt = .8, + .orientation = .3, + .projected_position = {-3, -4}}); + modeler.Update(6, {.pressure = .9, + .tilt = .8, + .orientation = .1, + .projected_position = {-6, -7}}); + modeler.Update(7, {.pressure = .8, + .tilt = .2, + .orientation = .2, + .projected_position = {-9, -8}}); + modeler.Update(8, {.pressure = .2, + .tilt = .4, + .orientation = .7, + .projected_position = {-11, -5}}); + modeler.Update(9, {.pressure = .7, + .tilt = .3, + .orientation = .2, + .projected_position = {-10, -2}}); ASSERT_THAT( modeler.Query({2, 0}), - StylusStateNear({.pressure = .6, .tilt = .5, .orientation = .4}, kTol)); + StylusStateNear({.pressure = .6, + .tilt = .5, + .orientation = .4, + .projected_position = {1, 1}, + .projected_velocity = std::optional({0, 0}), + .projected_acceleration = std::optional({0, 0})}, + kTol)); // Calling restore with no save should have no effect. modeler.Restore(); EXPECT_THAT( modeler.Query({2, 0}), - StylusStateNear({.pressure = .6, .tilt = .5, .orientation = .4}, kTol)); + StylusStateNear({.pressure = .6, + .tilt = .5, + .orientation = .4, + .projected_position = {1, 1}, + .projected_velocity = std::optional({0, 0}), + .projected_acceleration = std::optional({0, 0})}, + kTol)); modeler.Save(); // This causes the points at {1, 1} and {-1, 2} to be discarded. - modeler.Update({-8, 0}, {.pressure = .6, .tilt = .8, .orientation = .9}); - modeler.Update({-8, 0}, {.pressure = .6, .tilt = .8, .orientation = .9}); + modeler.Update(10, {.pressure = .6, + .tilt = .8, + .orientation = .9, + .projected_position = {-8, 0}}); + modeler.Update(11, {.pressure = .6, + .tilt = .8, + .orientation = .9, + .projected_position = {-8, 0}}); EXPECT_THAT( modeler.Query({2, 0}), - StylusStateNear({.pressure = .9, .tilt = .7, .orientation = .3}, kTol)); + StylusStateNear({.pressure = .9, + .tilt = .7, + .orientation = .3, + .projected_position = {-4, 0}, + .projected_velocity = std::optional({-3, -2}), + .projected_acceleration = std::optional({-1, -3})}, + kTol)); // Restoring should revert the updates. modeler.Restore(); EXPECT_THAT( modeler.Query({2, 0}), - StylusStateNear({.pressure = .6, .tilt = .5, .orientation = .4}, kTol)); + StylusStateNear({.pressure = .6, + .tilt = .5, + .orientation = .4, + .projected_position = {1, 1}, + .projected_velocity = std::optional({0, 0}), + .projected_acceleration = std::optional({0, 0})}, + kTol)); // Restoring should not have cleared the saved state, so we can repeat the // action. - modeler.Update({-8, 0}, {.pressure = .6, .tilt = .8, .orientation = .9}); - modeler.Update({-8, 0}, {.pressure = .6, .tilt = .8, .orientation = .9}); + modeler.Update(12, {.pressure = .6, + .tilt = .8, + .orientation = .9, + .projected_position = {-8, 0}}); + modeler.Update(13, {.pressure = .6, + .tilt = .8, + .orientation = .9, + .projected_position = {-8, 0}}); EXPECT_THAT( modeler.Query({2, 0}), - StylusStateNear({.pressure = .9, .tilt = .7, .orientation = .3}, kTol)); + StylusStateNear({.pressure = .9, + .tilt = .7, + .orientation = .3, + .projected_position = {-4, 0}, + .projected_velocity = std::optional({-3, -2}), + .projected_acceleration = std::optional({-1, -3})}, + kTol)); modeler.Restore(); EXPECT_THAT( modeler.Query({2, 0}), - StylusStateNear({.pressure = .6, .tilt = .5, .orientation = .4}, kTol)); + StylusStateNear({.pressure = .6, + .tilt = .5, + .orientation = .4, + .projected_position = {1, 1}, + .projected_velocity = std::optional({0, 0}), + .projected_acceleration = std::optional({0, 0})}, + kTol)); // Calling Reset should clear the save point so that calling Restore should // have no effect. modeler.Reset(StylusStateModelerParams{}); - modeler.Update({-1, 4}, {.pressure = .4, .tilt = .6, .orientation = .8}); + modeler.Update(14, {.pressure = .4, + .tilt = .6, + .orientation = .8, + .projected_position = {-1, 4}}); EXPECT_THAT( modeler.Query({6, 7}), - StylusStateNear({.pressure = .4, .tilt = .6, .orientation = .8}, kTol)); + StylusStateNear({.pressure = .4, + .tilt = .6, + .orientation = .8, + .projected_position = {-1, 4}, + .projected_velocity = std::optional({0, 0}), + .projected_acceleration = std::optional({0, 0})}, + kTol)); modeler.Restore(); EXPECT_THAT( modeler.Query({6, 7}), - StylusStateNear({.pressure = .4, .tilt = .6, .orientation = .8}, kTol)); + StylusStateNear({.pressure = .4, + .tilt = .6, + .orientation = .8, + .projected_position = {-1, 4}, + .projected_velocity = std::optional({0, 0}), + .projected_acceleration = std::optional({0, 0})}, + kTol)); } } // namespace diff --git a/ink_stroke_modeler/internal/type_matchers.cc b/ink_stroke_modeler/internal/type_matchers.cc index 6fd4431..4046976 100644 --- a/ink_stroke_modeler/internal/type_matchers.cc +++ b/ink_stroke_modeler/internal/type_matchers.cc @@ -14,6 +14,8 @@ #include "ink_stroke_modeler/internal/type_matchers.h" +#include + #include "gmock/gmock.h" #include "gtest/gtest.h" #include "absl/strings/str_cat.h" @@ -90,7 +92,23 @@ MATCHER_P2(StylusStateNearMatcher, expected, tolerance, Field("tilt", &StylusState::tilt, FloatNear(expected.tilt, tolerance)), Field("orientation", &StylusState::orientation, - FloatNear(expected.orientation, tolerance))), + FloatNear(expected.orientation, tolerance)), + Field("projected_position", &StylusState::projected_position, + Vec2Near(expected.projected_position, tolerance)), + expected.projected_velocity.has_value() + ? Field("projected_velocity", &StylusState::projected_velocity, + Optional( + Vec2Near(*expected.projected_velocity, tolerance))) + : Field("projected_velocity", &StylusState::projected_velocity, + testing::Eq(std::nullopt)), + expected.projected_acceleration.has_value() + ? Field("projected_acceleration", + &StylusState::projected_acceleration, + Optional(Vec2Near(*expected.projected_acceleration, + tolerance))) + : Field("projected_acceleration", + &StylusState::projected_acceleration, + testing::Eq(std::nullopt))), arg, result_listener); } diff --git a/ink_stroke_modeler/stroke_modeler.cc b/ink_stroke_modeler/stroke_modeler.cc index 54554f9..5117995 100644 --- a/ink_stroke_modeler/stroke_modeler.cc +++ b/ink_stroke_modeler/stroke_modeler.cc @@ -172,10 +172,11 @@ absl::Status StrokeModeler::ProcessDownEvent(const Input &input, stroke_model_params_->position_modeler_params); stylus_state_modeler_.Reset( stroke_model_params_->stylus_state_modeler_params); - stylus_state_modeler_.Update(input.position, + stylus_state_modeler_.Update(input.time.Value(), {.pressure = input.pressure, .tilt = input.tilt, - .orientation = input.orientation}); + .orientation = input.orientation, + .projected_position = input.position}); const TipState &tip_state = position_modeler_.CurrentState(); if (predictor_ != nullptr) { @@ -231,10 +232,11 @@ absl::Status StrokeModeler::ProcessUpEvent(const Input &input, tip_state_buffer_.push_back(position_modeler_.CurrentState()); } - stylus_state_modeler_.Update(input.position, + stylus_state_modeler_.Update(input.time.Value(), {.pressure = input.pressure, .tilt = input.tilt, - .orientation = input.orientation}); + .orientation = input.orientation, + .projected_position = input.position}); // This indicates that we've finished the stroke. last_input_ = std::nullopt; @@ -251,10 +253,11 @@ absl::Status StrokeModeler::ProcessMoveEvent(const Input &input, } Vec2 corrected_position = wobble_smoother_.Update(input.position, input.time); - stylus_state_modeler_.Update(corrected_position, + stylus_state_modeler_.Update(input.time.Value(), {.pressure = input.pressure, .tilt = input.tilt, - .orientation = input.orientation}); + .orientation = input.orientation, + .projected_position = corrected_position}); absl::StatusOr n_steps = NumberOfStepsBetweenInputs( position_modeler_.CurrentState(), last_input_->input, input,