blob: 00f1dbab2295f004652be713bf1ab3b5ebca6fd4 [file] [log] [blame]
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "ink_stroke_modeler/stroke_modeler.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "absl/status/status.h"
#include "ink_stroke_modeler/internal/type_matchers.h"
#include "ink_stroke_modeler/params.h"
namespace ink {
namespace stroke_model {
namespace {
using ::testing::DoubleNear;
using ::testing::ElementsAre;
using ::testing::FloatNear;
using ::testing::IsEmpty;
using ::testing::Matches;
using ::testing::Not;
constexpr float kTol = 1e-4;
// These parameters use cm for distance and seconds for time.
const StrokeModelParams kDefaultParams{
.wobble_smoother_params{
.timeout = Duration(.04), .speed_floor = 1.31, .speed_ceiling = 1.44},
.position_modeler_params{.spring_mass_constant = 11.f / 32400,
.drag_constant = 72.f},
.sampling_params{.min_output_rate = 180,
.end_of_stroke_stopping_distance = .001,
.end_of_stroke_max_iterations = 20},
.stylus_state_modeler_params{.max_input_samples = 20},
.prediction_params = StrokeEndPredictorParams()};
MATCHER_P2(ResultNearMatcher, expected, tolerance, "") {
if (Matches(Vec2Near(expected.position, tolerance))(arg.position) &&
Matches(Vec2Near(expected.velocity, tolerance))(arg.velocity) &&
Matches(DoubleNear(expected.time.Value(), tolerance))(arg.time.Value()) &&
Matches(FloatNear(expected.pressure, tolerance))(arg.pressure) &&
Matches(FloatNear(expected.tilt, tolerance))(arg.tilt) &&
Matches(FloatNear(expected.orientation, tolerance))(arg.orientation)) {
return true;
}
return false;
}
::testing::Matcher<Result> ResultNear(const Result &expected, float tolerance) {
return ResultNearMatcher(expected, tolerance);
}
TEST(StrokeModelerTest, NoPredictionUponInit) {
StrokeModeler modeler;
ASSERT_TRUE(modeler.Reset(kDefaultParams).ok());
EXPECT_EQ(modeler.Predict().status().code(),
absl::StatusCode::kFailedPrecondition);
}
TEST(StrokeModelerTest, InputRateSlowerThanMinOutputRate) {
const Duration kDeltaTime{1. / 30};
StrokeModeler modeler;
ASSERT_TRUE(modeler.Reset(kDefaultParams).ok());
Time time{0};
absl::StatusOr<std::vector<Result>> results =
modeler.Update({.event_type = Input::EventType::kDown,
.position = {3, 4},
.time = time});
ASSERT_TRUE(results.ok());
EXPECT_THAT(
*results,
ElementsAre(ResultNear(
{.position = {3, 4}, .velocity = {0, 0}, .time = Time(0)}, kTol)));
results = modeler.Predict();
ASSERT_TRUE(results.ok());
EXPECT_THAT(*results, IsEmpty());
time += kDeltaTime;
results = modeler.Update({.event_type = Input::EventType::kMove,
.position = {3.2, 4.2},
.time = time});
ASSERT_TRUE(results.ok());
EXPECT_THAT(*results, ElementsAre(ResultNear({.position = {3.0019, 4.0019},
.velocity = {0.4007, 0.4007},
.time = Time(0.0048)},
kTol),
ResultNear({.position = {3.0069, 4.0069},
.velocity = {1.0381, 1.0381},
.time = Time(0.0095)},
kTol),
ResultNear({.position = {3.0154, 4.0154},
.velocity = {1.7883, 1.7883},
.time = Time(0.0143)},
kTol),
ResultNear({.position = {3.0276, 4.0276},
.velocity = {2.5626, 2.5626},
.time = Time(0.0190)},
kTol),
ResultNear({.position = {3.0433, 4.0433},
.velocity = {3.3010, 3.3010},
.time = Time(0.0238)},
kTol),
ResultNear({.position = {3.0622, 4.0622},
.velocity = {3.9665, 3.9665},
.time = Time(0.0286)},
kTol),
ResultNear({.position = {3.0838, 4.0838},
.velocity = {4.5397, 4.5397},
.time = Time(0.0333)},
kTol)));
results = modeler.Predict();
ASSERT_TRUE(results.ok());
EXPECT_THAT(*results, ElementsAre(ResultNear({.position = {3.1095, 4.1095},
.velocity = {4.6253, 4.6253},
.time = Time(0.0389)},
kTol),
ResultNear({.position = {3.1331, 4.1331},
.velocity = {4.2563, 4.2563},
.time = Time(0.0444)},
kTol),
ResultNear({.position = {3.1534, 4.1534},
.velocity = {3.6479, 3.6479},
.time = Time(0.0500)},
kTol),
ResultNear({.position = {3.1698, 4.1698},
.velocity = {2.9512, 2.9512},
.time = Time(0.0556)},
kTol),
ResultNear({.position = {3.1824, 4.1824},
.velocity = {2.2649, 2.2649},
.time = Time(0.0611)},
kTol),
ResultNear({.position = {3.1915, 4.1915},
.velocity = {1.6473, 1.6473},
.time = Time(0.0667)},
kTol),
ResultNear({.position = {3.1978, 4.1978},
.velocity = {1.1269, 1.1269},
.time = Time(0.0722)},
kTol),
ResultNear({.position = {3.1992, 4.1992},
.velocity = {1.0232, 1.0232},
.time = Time(0.0736)},
kTol)));
time += kDeltaTime;
results = modeler.Update({.event_type = Input::EventType::kMove,
.position = {3.5, 4.2},
.time = time});
ASSERT_TRUE(results.ok());
EXPECT_THAT(*results, ElementsAre(ResultNear({.position = {3.1086, 4.1058},
.velocity = {5.2142, 4.6131},
.time = Time(0.0381)},
kTol),
ResultNear({.position = {3.1368, 4.1265},
.velocity = {5.9103, 4.3532},
.time = Time(0.0429)},
kTol),
ResultNear({.position = {3.1681, 4.1450},
.velocity = {6.5742, 3.8917},
.time = Time(0.0476)},
kTol),
ResultNear({.position = {3.2022, 4.1609},
.velocity = {7.1724, 3.3285},
.time = Time(0.0524)},
kTol),
ResultNear({.position = {3.2388, 4.1739},
.velocity = {7.6876, 2.7361},
.time = Time(0.0571)},
kTol),
ResultNear({.position = {3.2775, 4.1842},
.velocity = {8.1138, 2.1640},
.time = Time(0.0619)},
kTol),
ResultNear({.position = {3.3177, 4.1920},
.velocity = {8.4531, 1.6436},
.time = Time(0.0667)},
kTol)));
results = modeler.Predict();
ASSERT_TRUE(results.ok());
EXPECT_THAT(*results, ElementsAre(ResultNear({.position = {3.3625, 4.1982},
.velocity = {8.0545, 1.1165},
.time = Time(0.0722)},
kTol),
ResultNear({.position = {3.4018, 4.2021},
.velocity = {7.0831, 0.6987},
.time = Time(0.0778)},
kTol),
ResultNear({.position = {3.4344, 4.2043},
.velocity = {5.8564, 0.3846},
.time = Time(0.0833)},
kTol),
ResultNear({.position = {3.4598, 4.2052},
.velocity = {4.5880, 0.1611},
.time = Time(0.0889)},
kTol),
ResultNear({.position = {3.4788, 4.2052},
.velocity = {3.4098, 0.0124},
.time = Time(0.0944)},
kTol),
ResultNear({.position = {3.4921, 4.2048},
.velocity = {2.3929, -0.0780},
.time = Time(0.1000)},
kTol),
ResultNear({.position = {3.4976, 4.2045},
.velocity = {1.9791, -0.1015},
.time = Time(0.1028)},
kTol),
ResultNear({.position = {3.5001, 4.2044},
.velocity = {1.7911, -0.1098},
.time = Time(0.1042)},
kTol)));
time += kDeltaTime;
// We get more results at the end of the stroke as it tries to "catch up" to
// the raw input.
results = modeler.Update({.event_type = Input::EventType::kUp,
.position = {3.7, 4.4},
.time = time});
ASSERT_TRUE(results.ok());
EXPECT_THAT(*results, ElementsAre(ResultNear({.position = {3.3583, 4.1996},
.velocity = {8.5122, 1.5925},
.time = Time(0.0714)},
kTol),
ResultNear({.position = {3.3982, 4.2084},
.velocity = {8.3832, 1.8534},
.time = Time(0.0762)},
kTol),
ResultNear({.position = {3.4369, 4.2194},
.velocity = {8.1393, 2.3017},
.time = Time(0.0810)},
kTol),
ResultNear({.position = {3.4743, 4.2329},
.velocity = {7.8362, 2.8434},
.time = Time(0.0857)},
kTol),
ResultNear({.position = {3.5100, 4.2492},
.velocity = {7.5143, 3.4101},
.time = Time(0.0905)},
kTol),
ResultNear({.position = {3.5443, 4.2680},
.velocity = {7.2016, 3.9556},
.time = Time(0.0952)},
kTol),
ResultNear({.position = {3.5773, 4.2892},
.velocity = {6.9159, 4.4505},
.time = Time(0.1000)},
kTol),
ResultNear({.position = {3.6115, 4.3141},
.velocity = {6.1580, 4.4832},
.time = Time(0.1056)},
kTol),
ResultNear({.position = {3.6400, 4.3369},
.velocity = {5.1434, 4.0953},
.time = Time(0.1111)},
kTol),
ResultNear({.position = {3.6626, 4.3563},
.velocity = {4.0671, 3.4902},
.time = Time(0.1167)},
kTol),
ResultNear({.position = {3.6796, 4.3719},
.velocity = {3.0515, 2.8099},
.time = Time(0.1222)},
kTol),
ResultNear({.position = {3.6916, 4.3838},
.velocity = {2.1648, 2.1462},
.time = Time(0.1278)},
kTol),
ResultNear({.position = {3.6996, 4.3924},
.velocity = {1.4360, 1.5529},
.time = Time(0.1333)},
kTol),
ResultNear({.position = {3.7028, 4.3960},
.velocity = {1.1520, 1.3044},
.time = Time(0.1361)},
kTol)));
// The stroke is finished, so there's nothing to predict anymore.
EXPECT_EQ(modeler.Predict().status().code(),
absl::StatusCode::kFailedPrecondition);
}
TEST(StrokeModelerTest, InputRateFasterThanMinOutputRate) {
const Duration kDeltaTime{1. / 300};
StrokeModeler modeler;
ASSERT_TRUE(modeler.Reset(kDefaultParams).ok());
Time time{2};
absl::StatusOr<std::vector<Result>> results =
modeler.Update({.event_type = Input::EventType::kDown,
.position = {5, -3},
.time = time});
ASSERT_TRUE(results.ok());
EXPECT_THAT(
*results,
ElementsAre(ResultNear(
{.position = {5, -3}, .velocity = {0, 0}, .time = Time(2)}, kTol)));
results = modeler.Predict();
ASSERT_TRUE(results.ok());
EXPECT_THAT(*results, IsEmpty());
time += kDeltaTime;
results = modeler.Update({.event_type = Input::EventType::kMove,
.position = {5, -3.1},
.time = time});
ASSERT_TRUE(results.ok());
EXPECT_THAT(*results, ElementsAre(ResultNear({.position = {5, -3.0033},
.velocity = {0, -0.9818},
.time = Time(2.0033)},
kTol)));
results = modeler.Predict();
ASSERT_TRUE(results.ok());
EXPECT_THAT(*results, ElementsAre(ResultNear({.position = {5, -3.0153},
.velocity = {0, -2.1719},
.time = Time(2.0089)},
kTol),
ResultNear({.position = {5, -3.0303},
.velocity = {0, -2.6885},
.time = Time(2.0144)},
kTol),
ResultNear({.position = {5, -3.0456},
.velocity = {0, -2.7541},
.time = Time(2.0200)},
kTol),
ResultNear({.position = {5, -3.0597},
.velocity = {0, -2.5430},
.time = Time(2.0256)},
kTol),
ResultNear({.position = {5, -3.0718},
.velocity = {0, -2.1852},
.time = Time(2.0311)},
kTol),
ResultNear({.position = {5, -3.0817},
.velocity = {0, -1.7719},
.time = Time(2.0367)},
kTol),
ResultNear({.position = {5, -3.0893},
.velocity = {0, -1.3628},
.time = Time(2.0422)},
kTol),
ResultNear({.position = {5, -3.0948},
.velocity = {0, -0.9934},
.time = Time(2.0478)},
kTol),
ResultNear({.position = {5, -3.0986},
.velocity = {0, -0.6815},
.time = Time(2.0533)},
kTol)));
time += kDeltaTime;
results = modeler.Update({.event_type = Input::EventType::kMove,
.position = {4.975, -3.175},
.time = time});
ASSERT_TRUE(results.ok());
EXPECT_THAT(*results, ElementsAre(ResultNear({.position = {4.9992, -3.0114},
.velocity = {-0.2455, -2.4322},
.time = Time(2.0067)},
kTol)));
results = modeler.Predict();
ASSERT_TRUE(results.ok());
EXPECT_THAT(*results, ElementsAre(ResultNear({.position = {4.9962, -3.0344},
.velocity = {-0.5430, -4.1368},
.time = Time(2.0122)},
kTol),
ResultNear({.position = {4.9924, -3.0609},
.velocity = {-0.6721, -4.7834},
.time = Time(2.0178)},
kTol),
ResultNear({.position = {4.9886, -3.0873},
.velocity = {-0.6885, -4.7365},
.time = Time(2.0233)},
kTol),
ResultNear({.position = {4.9851, -3.1110},
.velocity = {-0.6358, -4.2778},
.time = Time(2.0289)},
kTol),
ResultNear({.position = {4.9820, -3.1311},
.velocity = {-0.5463, -3.6137},
.time = Time(2.0344)},
kTol),
ResultNear({.position = {4.9796, -3.1471},
.velocity = {-0.4430, -2.8867},
.time = Time(2.0400)},
kTol),
ResultNear({.position = {4.9777, -3.1593},
.velocity = {-0.3407, -2.1881},
.time = Time(2.0456)},
kTol),
ResultNear({.position = {4.9763, -3.1680},
.velocity = {-0.2484, -1.5700},
.time = Time(2.0511)},
kTol),
ResultNear({.position = {4.9754, -3.1739},
.velocity = {-0.1704, -1.0564},
.time = Time(2.0567)},
kTol)));
time += kDeltaTime;
results = modeler.Update({.event_type = Input::EventType::kMove,
.position = {4.9, -3.2},
.time = time});
ASSERT_TRUE(results.ok());
EXPECT_THAT(*results, ElementsAre(ResultNear({.position = {4.9953, -3.0237},
.velocity = {-1.1603, -3.7004},
.time = Time(2.0100)},
kTol)));
results = modeler.Predict();
ASSERT_TRUE(results.ok());
EXPECT_THAT(*results, ElementsAre(ResultNear({.position = {4.9828, -3.0521},
.velocity = {-2.2559, -5.1049},
.time = Time(2.0156)},
kTol),
ResultNear({.position = {4.9677, -3.0825},
.velocity = {-2.7081, -5.4835},
.time = Time(2.0211)},
kTol),
ResultNear({.position = {4.9526, -3.1115},
.velocity = {-2.7333, -5.2122},
.time = Time(2.0267)},
kTol),
ResultNear({.position = {4.9387, -3.1369},
.velocity = {-2.4999, -4.5756},
.time = Time(2.0322)},
kTol),
ResultNear({.position = {4.9268, -3.1579},
.velocity = {-2.1326, -3.7776},
.time = Time(2.0378)},
kTol),
ResultNear({.position = {4.9173, -3.1743},
.velocity = {-1.7184, -2.9554},
.time = Time(2.0433)},
kTol),
ResultNear({.position = {4.9100, -3.1865},
.velocity = {-1.3136, -2.1935},
.time = Time(2.0489)},
kTol),
ResultNear({.position = {4.9047, -3.1950},
.velocity = {-0.9513, -1.5369},
.time = Time(2.0544)},
kTol),
ResultNear({.position = {4.9011, -3.2006},
.velocity = {-0.6475, -1.0032},
.time = Time(2.0600)},
kTol)));
time += kDeltaTime;
results = modeler.Update({.event_type = Input::EventType::kMove,
.position = {4.825, -3.2},
.time = time});
ASSERT_TRUE(results.ok());
EXPECT_THAT(*results, ElementsAre(ResultNear({.position = {4.9868, -3.0389},
.velocity = {-2.5540, -4.5431},
.time = Time(2.0133)},
kTol)));
results = modeler.Predict();
ASSERT_TRUE(results.ok());
EXPECT_THAT(*results, ElementsAre(ResultNear({.position = {4.9636, -3.0687},
.velocity = {-4.1801, -5.3627},
.time = Time(2.0189)},
kTol),
ResultNear({.position = {4.9370, -3.0985},
.velocity = {-4.7757, -5.3670},
.time = Time(2.0244)},
kTol),
ResultNear({.position = {4.9109, -3.1256},
.velocity = {-4.6989, -4.8816},
.time = Time(2.0300)},
kTol),
ResultNear({.position = {4.8875, -3.1486},
.velocity = {-4.2257, -4.1466},
.time = Time(2.0356)},
kTol),
ResultNear({.position = {4.8677, -3.1671},
.velocity = {-3.5576, -3.3287},
.time = Time(2.0411)},
kTol),
ResultNear({.position = {4.8520, -3.1812},
.velocity = {-2.8333, -2.5353},
.time = Time(2.0467)},
kTol),
ResultNear({.position = {4.8401, -3.1914},
.velocity = {-2.1411, -1.8288},
.time = Time(2.0522)},
kTol),
ResultNear({.position = {4.8316, -3.1982},
.velocity = {-1.5312, -1.2386},
.time = Time(2.0578)},
kTol),
ResultNear({.position = {4.8280, -3.2010},
.velocity = {-1.2786, -1.0053},
.time = Time(2.0606)},
kTol),
ResultNear({.position = {4.8272, -3.2017},
.velocity = {-1.2209, -0.9529},
.time = Time(2.0613)},
kTol)));
time += kDeltaTime;
results = modeler.Update({.event_type = Input::EventType::kMove,
.position = {4.75, -3.225},
.time = time});
ASSERT_TRUE(results.ok());
EXPECT_THAT(*results, ElementsAre(ResultNear({.position = {4.9726, -3.0565},
.velocity = {-4.2660, -5.2803},
.time = Time(2.0167)},
kTol)));
results = modeler.Predict();
ASSERT_TRUE(results.ok());
EXPECT_THAT(*results, ElementsAre(ResultNear({.position = {4.9381, -3.0894},
.velocity = {-6.2018, -5.9261},
.time = Time(2.0222)},
kTol),
ResultNear({.position = {4.9004, -3.1215},
.velocity = {-6.7995, -5.7749},
.time = Time(2.0278)},
kTol),
ResultNear({.position = {4.8640, -3.1501},
.velocity = {-6.5400, -5.1591},
.time = Time(2.0333)},
kTol),
ResultNear({.position = {4.8319, -3.1741},
.velocity = {-5.7897, -4.3207},
.time = Time(2.0389)},
kTol),
ResultNear({.position = {4.8051, -3.1932},
.velocity = {-4.8133, -3.4248},
.time = Time(2.0444)},
kTol),
ResultNear({.position = {4.7841, -3.2075},
.velocity = {-3.7898, -2.5759},
.time = Time(2.0500)},
kTol),
ResultNear({.position = {4.7683, -3.2176},
.velocity = {-2.8312, -1.8324},
.time = Time(2.0556)},
kTol),
ResultNear({.position = {4.7572, -3.2244},
.velocity = {-1.9986, -1.2198},
.time = Time(2.0611)},
kTol),
ResultNear({.position = {4.7526, -3.2271},
.velocity = {-1.6580, -0.9805},
.time = Time(2.0639)},
kTol)));
time += kDeltaTime;
results = modeler.Update({.event_type = Input::EventType::kMove,
.position = {4.7, -3.3},
.time = time});
ASSERT_TRUE(results.ok());
EXPECT_THAT(*results, ElementsAre(ResultNear({.position = {4.9529, -3.0778},
.velocity = {-5.9184, -6.4042},
.time = Time(2.0200)},
kTol)));
results = modeler.Predict();
ASSERT_TRUE(results.ok());
EXPECT_THAT(*results, ElementsAre(ResultNear({.position = {4.9101, -3.1194},
.velocity = {-7.6886, -7.4784},
.time = Time(2.0256)},
kTol),
ResultNear({.position = {4.8654, -3.1607},
.velocity = {-8.0518, -7.4431},
.time = Time(2.0311)},
kTol),
ResultNear({.position = {4.8235, -3.1982},
.velocity = {-7.5377, -6.7452},
.time = Time(2.0367)},
kTol),
ResultNear({.position = {4.7872, -3.2299},
.velocity = {-6.5440, -5.7133},
.time = Time(2.0422)},
kTol),
ResultNear({.position = {4.7574, -3.2553},
.velocity = {-5.3529, -4.5748},
.time = Time(2.0478)},
kTol),
ResultNear({.position = {4.7344, -3.2746},
.velocity = {-4.1516, -3.4758},
.time = Time(2.0533)},
kTol),
ResultNear({.position = {4.7174, -3.2885},
.velocity = {-3.0534, -2.5004},
.time = Time(2.0589)},
kTol),
ResultNear({.position = {4.7056, -3.2979},
.velocity = {-2.1169, -1.6879},
.time = Time(2.0644)},
kTol),
ResultNear({.position = {4.7030, -3.3000},
.velocity = {-1.9283, -1.5276},
.time = Time(2.0658)},
kTol),
ResultNear({.position = {4.7017, -3.3010},
.velocity = {-1.8380, -1.4512},
.time = Time(2.0665)},
kTol)));
time += kDeltaTime;
results = modeler.Update({.event_type = Input::EventType::kMove,
.position = {4.675, -3.4},
.time = time});
ASSERT_TRUE(results.ok());
EXPECT_THAT(*results, ElementsAre(ResultNear({.position = {4.9288, -3.1046},
.velocity = {-7.2260, -8.0305},
.time = Time(2.0233)},
kTol)));
results = modeler.Predict();
ASSERT_TRUE(results.ok());
EXPECT_THAT(*results, ElementsAre(ResultNear({.position = {4.8816, -3.1582},
.velocity = {-8.4881, -9.6525},
.time = Time(2.0289)},
kTol),
ResultNear({.position = {4.8345, -3.2124},
.velocity = {-8.4738, -9.7482},
.time = Time(2.0344)},
kTol),
ResultNear({.position = {4.7918, -3.2619},
.velocity = {-7.6948, -8.9195},
.time = Time(2.0400)},
kTol),
ResultNear({.position = {4.7555, -3.3042},
.velocity = {-6.5279, -7.6113},
.time = Time(2.0456)},
kTol),
ResultNear({.position = {4.7264, -3.3383},
.velocity = {-5.2343, -6.1345},
.time = Time(2.0511)},
kTol),
ResultNear({.position = {4.7043, -3.3643},
.velocity = {-3.9823, -4.6907},
.time = Time(2.0567)},
kTol),
ResultNear({.position = {4.6884, -3.3832},
.velocity = {-2.8691, -3.3980},
.time = Time(2.0622)},
kTol),
ResultNear({.position = {4.6776, -3.3961},
.velocity = {-1.9403, -2.3135},
.time = Time(2.0678)},
kTol),
ResultNear({.position = {4.6752, -3.3990},
.velocity = {-1.7569, -2.0983},
.time = Time(2.0692)},
kTol)));
time += kDeltaTime;
results = modeler.Update({.event_type = Input::EventType::kMove,
.position = {4.675, -3.525},
.time = time});
ASSERT_TRUE(results.ok());
EXPECT_THAT(*results, ElementsAre(ResultNear({.position = {4.9022, -3.1387},
.velocity = {-7.9833, -10.2310},
.time = Time(2.0267)},
kTol)));
results = modeler.Predict();
ASSERT_TRUE(results.ok());
EXPECT_THAT(*results, ElementsAre(ResultNear({.position = {4.8549, -3.2079},
.velocity = {-8.5070, -12.4602},
.time = Time(2.0322)},
kTol),
ResultNear({.position = {4.8102, -3.2783},
.velocity = {-8.0479, -12.6650},
.time = Time(2.0378)},
kTol),
ResultNear({.position = {4.7711, -3.3429},
.velocity = {-7.0408, -11.6365},
.time = Time(2.0433)},
kTol),
ResultNear({.position = {4.7389, -3.3983},
.velocity = {-5.7965, -9.9616},
.time = Time(2.0489)},
kTol),
ResultNear({.position = {4.7137, -3.4430},
.velocity = {-4.5230, -8.0510},
.time = Time(2.0544)},
kTol),
ResultNear({.position = {4.6951, -3.4773},
.velocity = {-3.3477, -6.1727},
.time = Time(2.0600)},
kTol),
ResultNear({.position = {4.6821, -3.5022},
.velocity = {-2.3381, -4.4846},
.time = Time(2.0656)},
kTol),
ResultNear({.position = {4.6737, -3.5192},
.velocity = {-1.5199, -3.0641},
.time = Time(2.0711)},
kTol),
ResultNear({.position = {4.6718, -3.5231},
.velocity = {-1.3626, -2.7813},
.time = Time(2.0725)},
kTol)));
time += kDeltaTime;
// We get more results at the end of the stroke as it tries to "catch up" to
// the raw input.
results = modeler.Update({.event_type = Input::EventType::kUp,
.position = {4.7, -3.6},
.time = time});
ASSERT_TRUE(results.ok());
EXPECT_THAT(*results, ElementsAre(ResultNear({.position = {4.8753, -3.1797},
.velocity = {-8.0521, -12.3049},
.time = Time(2.0300)},
kTol),
ResultNear({.position = {4.8325, -3.2589},
.velocity = {-7.7000, -14.2607},
.time = Time(2.0356)},
kTol),
ResultNear({.position = {4.7948, -3.3375},
.velocity = {-6.7888, -14.1377},
.time = Time(2.0411)},
kTol),
ResultNear({.position = {4.7636, -3.4085},
.velocity = {-5.6249, -12.7787},
.time = Time(2.0467)},
kTol),
ResultNear({.position = {4.7390, -3.4685},
.velocity = {-4.4152, -10.8015},
.time = Time(2.0522)},
kTol),
ResultNear({.position = {4.7208, -3.5164},
.velocity = {-3.2880, -8.6333},
.time = Time(2.0578)},
kTol),
ResultNear({.position = {4.7079, -3.5528},
.velocity = {-2.3128, -6.5475},
.time = Time(2.0633)},
kTol),
ResultNear({.position = {4.6995, -3.5789},
.velocity = {-1.5174, -4.7008},
.time = Time(2.0689)},
kTol),
ResultNear({.position = {4.6945, -3.5965},
.velocity = {-0.9022, -3.1655},
.time = Time(2.0744)},
kTol),
ResultNear({.position = {4.6942, -3.5976},
.velocity = {-0.8740, -3.0899},
.time = Time(2.0748)},
kTol)));
// The stroke is finished, so there's nothing to predict anymore.
EXPECT_EQ(modeler.Predict().status().code(),
absl::StatusCode::kFailedPrecondition);
}
TEST(StrokeModelerTest, WobbleSmoothed) {
const Duration kDeltaTime{.0167};
StrokeModeler modeler;
ASSERT_TRUE(modeler.Reset(kDefaultParams).ok());
Time time{4};
absl::StatusOr<std::vector<Result>> results =
modeler.Update({.event_type = Input::EventType::kDown,
.position = {-6, -2},
.time = time});
ASSERT_TRUE(results.ok());
EXPECT_THAT(
*results,
ElementsAre(ResultNear(
{.position = {-6, -2}, .velocity = {0, 0}, .time = Time(4)}, kTol)));
time += kDeltaTime;
results = modeler.Update({.event_type = Input::EventType::kMove,
.position = {-6.02, -2},
.time = time});
ASSERT_TRUE(results.ok());
EXPECT_THAT(*results, ElementsAre(ResultNear({.position = {-6.0001, -2},
.velocity = {-0.0328, 0},
.time = Time(4.0042)},
kTol),
ResultNear({.position = {-6.0005, -2},
.velocity = {-0.0869, 0},
.time = Time(4.0084)},
kTol),
ResultNear({.position = {-6.0011, -2},
.velocity = {-0.1531, 0},
.time = Time(4.0125)},
kTol),
ResultNear({.position = {-6.0021, -2},
.velocity = {-0.2244, 0},
.time = Time(4.0167)},
kTol)));
time += kDeltaTime;
results = modeler.Update({.event_type = Input::EventType::kMove,
.position = {-6.02, -2.02},
.time = time});
ASSERT_TRUE(results.ok());
EXPECT_THAT(*results, ElementsAre(ResultNear({.position = {-6.0032, -2.0001},
.velocity = {-0.2709, -0.0205},
.time = Time(4.0209)},
kTol),
ResultNear({.position = {-6.0044, -2.0003},
.velocity = {-0.2977, -0.0543},
.time = Time(4.0251)},
kTol),
ResultNear({.position = {-6.0057, -2.0007},
.velocity = {-0.3093, -0.0956},
.time = Time(4.0292)},
kTol),
ResultNear({.position = {-6.0070, -2.0013},
.velocity = {-0.3097, -0.1401},
.time = Time(4.0334)},
kTol)));
time += kDeltaTime;
results = modeler.Update({.event_type = Input::EventType::kMove,
.position = {-6.04, -2.02},
.time = time});
ASSERT_TRUE(results.ok());
EXPECT_THAT(*results, ElementsAre(ResultNear({.position = {-6.0084, -2.0021},
.velocity = {-0.3350, -0.1845},
.time = Time(4.0376)},
kTol),
ResultNear({.position = {-6.0100, -2.0030},
.velocity = {-0.3766, -0.2266},
.time = Time(4.0418)},
kTol),
ResultNear({.position = {-6.0118, -2.0041},
.velocity = {-0.4273, -0.2649},
.time = Time(4.0459)},
kTol),
ResultNear({.position = {-6.0138, -2.0054},
.velocity = {-0.4818, -0.2986},
.time = Time(4.0501)},
kTol)));
time += kDeltaTime;
results = modeler.Update({.event_type = Input::EventType::kMove,
.position = {-6.04, -2.04},
.time = time});
ASSERT_TRUE(results.ok());
EXPECT_THAT(*results, ElementsAre(ResultNear({.position = {-6.0160, -2.0068},
.velocity = {-0.5157, -0.3478},
.time = Time(4.0543)},
kTol),
ResultNear({.position = {-6.0182, -2.0085},
.velocity = {-0.5334, -0.4054},
.time = Time(4.0585)},
kTol),
ResultNear({.position = {-6.0204, -2.0105},
.velocity = {-0.5389, -0.4658},
.time = Time(4.0626)},
kTol),
ResultNear({.position = {-6.0227, -2.0126},
.velocity = {-0.5356, -0.5251},
.time = Time(4.0668)},
kTol)));
}
TEST(StrokeModelerTest, Reset) {
const Duration kDeltaTime{1. / 50};
StrokeModeler modeler;
ASSERT_TRUE(modeler.Reset(kDefaultParams).ok());
Time time{0};
absl::StatusOr<std::vector<Result>> results =
modeler.Update({.event_type = Input::EventType::kDown,
.position = {-8, -10},
.time = time});
ASSERT_TRUE(results.ok());
EXPECT_THAT(*results, Not(IsEmpty()));
results = modeler.Predict();
ASSERT_TRUE(results.ok());
EXPECT_THAT(*results, IsEmpty());
time += kDeltaTime;
results = modeler.Update({.event_type = Input::EventType::kMove,
.position = {-10, -8},
.time = time});
ASSERT_TRUE(results.ok());
EXPECT_THAT(*results, Not(IsEmpty()));
results = modeler.Predict();
ASSERT_TRUE(results.ok());
EXPECT_THAT(*results, Not(IsEmpty()));
time += kDeltaTime;
results = modeler.Update({.event_type = Input::EventType::kMove,
.position = {-11, -5},
.time = time});
ASSERT_TRUE(results.ok());
EXPECT_THAT(*results, Not(IsEmpty()));
results = modeler.Predict();
ASSERT_TRUE(results.ok());
EXPECT_THAT(*results, Not(IsEmpty()));
ASSERT_TRUE(modeler.Reset(kDefaultParams).ok());
EXPECT_EQ(modeler.Predict().status().code(),
absl::StatusCode::kFailedPrecondition);
}
TEST(StrokeModelerTest, IgnoreInputsBeforeTDown) {
StrokeModeler modeler;
ASSERT_TRUE(modeler.Reset(kDefaultParams).ok());
EXPECT_EQ(modeler
.Update({.event_type = Input::EventType::kMove,
.position = {0, 0},
.time = Time(0)})
.status()
.code(),
absl::StatusCode::kFailedPrecondition);
EXPECT_EQ(modeler
.Update({.event_type = Input::EventType::kUp,
.position = {0, 0},
.time = Time(1)})
.status()
.code(),
absl::StatusCode::kFailedPrecondition);
}
TEST(StrokeModelerTest, IgnoreTDownWhileStrokeIsInProgress) {
StrokeModeler modeler;
ASSERT_TRUE(modeler.Reset(kDefaultParams).ok());
absl::StatusOr<std::vector<Result>> results =
modeler.Update({.event_type = Input::EventType::kDown,
.position = {0, 0},
.time = Time(0)});
ASSERT_TRUE(results.ok());
EXPECT_THAT(*results, Not(IsEmpty()));
EXPECT_EQ(modeler
.Update({.event_type = Input::EventType::kDown,
.position = {1, 1},
.time = Time(1)})
.status()
.code(),
absl::StatusCode::kFailedPrecondition);
results = modeler.Update({.event_type = Input::EventType::kMove,
.position = {1, 1},
.time = Time(1)});
ASSERT_TRUE(results.ok());
EXPECT_THAT(*results, Not(IsEmpty()));
EXPECT_EQ(modeler
.Update({.event_type = Input::EventType::kDown,
.position = {2, 2},
.time = Time(2)})
.status()
.code(),
absl::StatusCode::kFailedPrecondition);
}
TEST(StrokeModelerTest, AlternateParams) {
const Duration kDeltaTime{1. / 50};
StrokeModelParams stroke_model_params = kDefaultParams;
stroke_model_params.sampling_params.min_output_rate = 70;
StrokeModeler modeler;
ASSERT_TRUE(modeler.Reset(stroke_model_params).ok());
Time time{3};
absl::StatusOr<std::vector<Result>> results =
modeler.Update({.event_type = Input::EventType::kDown,
.position = {0, 0},
.time = time,
.pressure = .5,
.tilt = .2,
.orientation = .4});
ASSERT_TRUE(results.ok());
EXPECT_THAT(*results, ElementsAre(ResultNear(
{{0, 0}, {0, 0}, Time{3}, .5, .2, .4}, kTol)));
results = modeler.Predict();
ASSERT_TRUE(results.ok());
EXPECT_THAT(*results, IsEmpty());
time += kDeltaTime;
results = modeler.Update({.event_type = Input::EventType::kMove,
.position = {0, .5},
.time = time,
.pressure = .4,
.tilt = .3,
.orientation = .3});
ASSERT_TRUE(results.ok());
EXPECT_THAT(
*results,
ElementsAre(
ResultNear(
{{0, 0.0736}, {0, 7.3636}, Time{3.0100}, 0.4853, 0.2147, 0.3853},
kTol),
ResultNear(
{{0, 0.2198}, {0, 14.6202}, Time{3.0200}, 0.4560, 0.2440, 0.3560},
kTol)));
results = modeler.Predict();
ASSERT_TRUE(results.ok());
EXPECT_THAT(
*results,
ElementsAre(
ResultNear(
{{0, 0.3823}, {0, 11.3709}, Time{3.0343}, 0.4235, 0.2765, 0.3235},
kTol),
ResultNear(
{{0, 0.4484}, {0, 4.6285}, Time{3.0486}, 0.4103, 0.2897, 0.3103},
kTol),
ResultNear(
{{0, 0.4775}, {0, 2.0389}, Time{3.0629}, 0.4045, 0.2955, 0.3045},
kTol),
ResultNear(
{{0, 0.4902}, {0, 0.8873}, Time{3.0771}, 0.4020, 0.2980, 0.3020},
kTol),
ResultNear(
{{0, 0.4957}, {0, 0.3868}, Time{3.0914}, 0.4009, 0.2991, 0.3009},
kTol),
ResultNear(
{{0, 0.4981}, {0, 0.1686}, Time{3.1057}, 0.4004, 0.2996, 0.3004},
kTol),
ResultNear(
{{0, 0.4992}, {0, 0.0735}, Time{3.1200}, 0.4002, 0.2998, 0.3002},
kTol)));
time += kDeltaTime;
results = modeler.Update({.event_type = Input::EventType::kMove,
.position = {.2, 1},
.time = time,
.pressure = .3,
.tilt = .4,
.orientation = .2});
ASSERT_TRUE(results.ok());
EXPECT_THAT(*results, ElementsAre(ResultNear({{0.0295, 0.4169},
{2.9455, 19.7093},
Time{3.0300},
0.4166,
0.2834,
0.3166},
kTol),
ResultNear({{0.0879, 0.6439},
{5.8481, 22.6926},
Time{3.0400},
0.3691,
0.3309,
0.2691},
kTol)));
results = modeler.Predict();
ASSERT_TRUE(results.ok());
EXPECT_THAT(*results, ElementsAre(ResultNear({{0.1529, 0.8487},
{4.5484, 14.3374},
Time{3.0543},
0.3293,
0.3707,
0.2293},
kTol),
ResultNear({{0.1794, 0.9338},
{1.8514, 5.9577},
Time{3.0686},
0.3128,
0.3872,
0.2128},
kTol),
ResultNear({{0.1910, 0.9712},
{0.8156, 2.6159},
Time{3.0829},
0.3056,
0.3944,
0.2056},
kTol),
ResultNear({{0.1961, 0.9874},
{0.3549, 1.1389},
Time{3.0971},
0.3024,
0.3976,
0.2024},
kTol),
ResultNear({{0.1983, 0.9945},
{0.1547, 0.4965},
Time{3.1114},
0.3011,
0.3989,
0.2011},
kTol),
ResultNear({{0.1993, 0.9976},
{0.0674, 0.2164},
Time{3.1257},
0.3005,
0.3995,
0.2005},
kTol),
ResultNear({{0.1997, 0.9990},
{0.0294, 0.0943},
Time{3.1400},
0.3002,
0.3998,
0.2002},
kTol)));
time += kDeltaTime;
results = modeler.Update({.event_type = Input::EventType::kMove,
.position = {.4, 1.4},
.time = time,
.pressure = .2,
.tilt = .7,
.orientation = 0});
ASSERT_TRUE(results.ok());
EXPECT_THAT(*results, ElementsAre(ResultNear({{0.1668, 0.8712},
{7.8837, 22.7349},
Time{3.0500},
0.3245,
0.3755,
0.2245},
kTol),
ResultNear({{0.2575, 1.0906},
{9.0771, 21.9411},
Time{3.0600},
0.2761,
0.4716,
0.1522},
kTol)));
results = modeler.Predict();
ASSERT_TRUE(results.ok());
EXPECT_THAT(*results, ElementsAre(ResultNear({{0.3395, 1.2676},
{5.7349, 12.3913},
Time{3.0743},
0.2325,
0.6024,
0.0651},
kTol),
ResultNear({{0.3735, 1.3421},
{2.3831, 5.2156},
Time{3.0886},
0.2142,
0.6573,
0.0284},
kTol),
ResultNear({{0.3885, 1.3748},
{1.0463, 2.2854},
Time{3.1029},
0.2062,
0.6814,
0.0124},
kTol),
ResultNear({{0.3950, 1.3890},
{0.4556, 0.9954},
Time{3.1171},
0.2027,
0.6919,
0.0054},
kTol),
ResultNear({{0.3978, 1.3952},
{0.1986, 0.4339},
Time{3.1314},
0.2012,
0.6965,
0.0024},
kTol),
ResultNear({{0.3990, 1.3979},
{0.0866, 0.1891},
Time{3.1457},
0.2005,
0.6985,
0.0010},
kTol),
ResultNear({{0.3996, 1.3991},
{0.0377, 0.0824},
Time{3.1600},
0.2002,
0.6993,
0.0004},
kTol)));
time += kDeltaTime;
results = modeler.Update({.event_type = Input::EventType::kUp,
.position = {.7, 1.7},
.time = time,
.pressure = .1,
.tilt = 1,
.orientation = 0});
ASSERT_TRUE(results.ok());
EXPECT_THAT(*results, ElementsAre(ResultNear({{0.3691, 1.2874},
{11.1558, 19.6744},
Time{3.0700},
0.2256,
0.6231,
0.0512},
kTol),
ResultNear({{0.4978, 1.4640},
{12.8701, 17.6629},
Time{3.0800},
0.1730,
0.7809,
0},
kTol),
ResultNear({{0.6141, 1.5986},
{8.1404, 9.4261},
Time{3.0943},
0.1312,
0.9064,
0},
kTol),
ResultNear({{0.6624, 1.6557},
{3.3822, 3.9953},
Time{3.1086},
0.1136,
0.9591,
0},
kTol),
ResultNear({{0.6836, 1.6807},
{1.4851, 1.7488},
Time{3.1229},
0.1059,
0.9822,
0},
kTol),
ResultNear({{0.6929, 1.6916},
{0.6466, 0.7618},
Time{3.1371},
0.1026,
0.9922,
0},
kTol),
ResultNear({{0.6969, 1.6963},
{0.2819, 0.3321},
Time{3.1514},
0.1011,
0.9966,
0},
kTol),
ResultNear({{0.6986, 1.6984},
{0.1229, 0.1447},
Time{3.1657},
0.1005,
0.9985,
0},
kTol),
ResultNear({{0.6994, 1.6993},
{0.0535, 0.0631},
Time{3.1800},
0.1002,
0.9994,
0},
kTol)));
EXPECT_EQ(modeler.Predict().status().code(),
absl::StatusCode::kFailedPrecondition);
}
TEST(StrokeModelerTest, GenerateOutputOnTUpEvenIfNoTimeDelta) {
const Duration kDeltaTime{1. / 500};
StrokeModeler modeler;
ASSERT_TRUE(modeler.Reset(kDefaultParams).ok());
Time time{0};
absl::StatusOr<std::vector<Result>> results =
modeler.Update({.event_type = Input::EventType::kDown,
.position = {5, 5},
.time = time});
ASSERT_TRUE(results.ok());
EXPECT_THAT(
*results,
ElementsAre(ResultNear(
{.position = {5, 5}, .velocity = {0, 0}, .time = Time(0)}, kTol)));
time += kDeltaTime;
results = modeler.Update({.event_type = Input::EventType::kMove,
.position = {5, 5},
.time = time});
ASSERT_TRUE(results.ok());
EXPECT_THAT(*results,
ElementsAre(ResultNear(
{.position = {5, 5}, .velocity = {0, 0}, .time = Time(0.002)},
kTol)));
results = modeler.Update(
{.event_type = Input::EventType::kUp, .position = {5, 5}, .time = time});
ASSERT_TRUE(results.ok());
EXPECT_THAT(
*results,
ElementsAre(ResultNear(
{.position = {5, 5}, .velocity = {0, 0}, .time = Time(0.0076)},
kTol)));
}
TEST(StrokeModelerTest, RejectInputIfNegativeTimeDelta) {
StrokeModeler modeler;
ASSERT_TRUE(modeler.Reset(kDefaultParams).ok());
absl::StatusOr<std::vector<Result>> results =
modeler.Update({.event_type = Input::EventType::kDown,
.position = {0, 0},
.time = Time(0)});
ASSERT_TRUE(results.ok());
EXPECT_THAT(*results, Not(IsEmpty()));
EXPECT_EQ(modeler
.Update({.event_type = Input::EventType::kMove,
.position = {1, 1},
.time = Time(-.1)})
.status()
.code(),
absl::StatusCode::kInvalidArgument);
results = modeler.Update({.event_type = Input::EventType::kMove,
.position = {1, 1},
.time = Time(1)});
ASSERT_TRUE(results.ok());
EXPECT_THAT(*results, Not(IsEmpty()));
EXPECT_EQ(modeler
.Update({.event_type = Input::EventType::kUp,
.position = {1, 1},
.time = Time(.9)})
.status()
.code(),
absl::StatusCode::kInvalidArgument);
}
TEST(StrokeModelerTest, RejectDuplicateInput) {
StrokeModeler modeler;
ASSERT_TRUE(modeler.Reset(kDefaultParams).ok());
absl::StatusOr<std::vector<Result>> results =
modeler.Update({.event_type = Input::EventType::kDown,
.position = {0, 0},
.time = Time(0),
.pressure = .2,
.tilt = .3,
.orientation = .4});
ASSERT_TRUE(results.ok());
EXPECT_THAT(*results, Not(IsEmpty()));
EXPECT_EQ(modeler
.Update({.event_type = Input::EventType::kDown,
.position = {0, 0},
.time = Time(0),
.pressure = .2,
.tilt = .3,
.orientation = .4})
.status()
.code(),
absl::StatusCode::kInvalidArgument);
results = modeler.Update({.event_type = Input::EventType::kMove,
.position = {1, 2},
.time = Time(1),
.pressure = .1,
.tilt = .2,
.orientation = .3});
ASSERT_TRUE(results.ok());
EXPECT_THAT(*results, Not(IsEmpty()));
EXPECT_EQ(modeler
.Update({.event_type = Input::EventType::kMove,
.position = {1, 2},
.time = Time(1),
.pressure = .1,
.tilt = .2,
.orientation = .3})
.status()
.code(),
absl::StatusCode::kInvalidArgument);
}
} // namespace
} // namespace stroke_model
} // namespace ink