static void UpdateCamera(fplbase::InputSystem* input, Camera* camera) { const fplbase::InputPointer& pointer = input->get_pointers()[0]; if (!pointer.used) return; // Update latitude and longitude: left mouse drag. if (input->GetButton(fplbase::K_POINTER1).is_down() && pointer.mousedelta != mathfu::kZeros2i) { const float lat = camera->latitude.ToRadians() + pointer.mousedelta.y * kLatitudeSensitivity; camera->latitude = Angle(mathfu::Clamp(lat, -kMaxLatitude, kMaxLatitude)); camera->longitude += Angle(pointer.mousedelta.x * kLongitudeSensitivity); } // Update distance: right mouse drag. // (Unfortunately, InputSystem doesn't support scroll wheels yet) if (input->GetButton(fplbase::K_POINTER3).is_down() && pointer.mousedelta != mathfu::kZeros2i) { const float dist = camera->distance + (pointer.mousedelta.x + pointer.mousedelta.y) * kDistanceSensitivity; camera->distance = std::max(dist, camera->z_near); } // Update target: middle mouse drag. if (input->GetButton(fplbase::K_POINTER2).is_down() && pointer.mousedelta != mathfu::kZeros2i) { const vec3 right = CameraRight(*camera); const vec3 up = VectorSystemUp(camera->coordinate_system); const vec2 dist = kTargetSensitivity * vec2(pointer.mousedelta); camera->target += dist[0] * right + dist[1] * up; } }
void TestToVectorSystem(const AngleToVectorSystem system, const int zero_axis, const int ninety_axis, const int ignored_axis) { // Angle zero should translate to the zero axis. const vec3 zero = Angle(0.0f).ToVectorSystem(system); EXPECT_NEAR(zero[zero_axis], 1.0f, kUnitVectorPrecision); EXPECT_NEAR(zero[ninety_axis], 0.0f, kUnitVectorPrecision); EXPECT_NEAR(zero[ignored_axis], 0.0f, kUnitVectorPrecision); // Angle 180 should translate to the negative zero axis. const vec3 minus_zero = Angle(kPi).ToVectorSystem(system); EXPECT_NEAR(minus_zero[zero_axis], -1.0f, kUnitVectorPrecision); EXPECT_NEAR(minus_zero[ninety_axis], 0.0f, kUnitVectorPrecision); EXPECT_NEAR(minus_zero[ignored_axis], 0.0f, kUnitVectorPrecision); // Angle 90 should translate to the 90 axis. const vec3 ninety = Angle(kHalfPi).ToVectorSystem(system); EXPECT_NEAR(ninety[zero_axis], 0.0f, kUnitVectorPrecision); EXPECT_NEAR(ninety[ninety_axis], 1.0f, kUnitVectorPrecision); EXPECT_NEAR(ninety[ignored_axis], 0.0f, kUnitVectorPrecision); // Angle -90 should translate to the -90 axis. const vec3 minus_ninety = Angle(-kHalfPi).ToVectorSystem(system); EXPECT_NEAR(minus_ninety[zero_axis], 0.0f, kUnitVectorPrecision); EXPECT_NEAR(minus_ninety[ninety_axis], -1.0f, kUnitVectorPrecision); EXPECT_NEAR(minus_ninety[ignored_axis], 0.0f, kUnitVectorPrecision); // Angle 45 should translate to a unit vector between the 0 and 90 axes. const vec3 forty_five = Angle(kQuarterPi).ToVectorSystem(system); const float one_over_root_two = 1.0f / sqrt(2.0f); EXPECT_NEAR(forty_five[zero_axis], one_over_root_two, kUnitVectorPrecision); EXPECT_NEAR(forty_five[ninety_axis], one_over_root_two, kUnitVectorPrecision); EXPECT_NEAR(forty_five[ignored_axis], 0.0f, kUnitVectorPrecision); }
int main() { // Since we use the ‘smooth’ animation algorithm, we must register it. motive::SmoothInit::Register(); // The engine is the central place for animation data. motive::MotiveEngine engine; // In this example, we animate a one-dimensional floating point value. motive::Motivator1f facing_angle; // Initialize facing_angle Motivator to animate as a 'Smooth' Motivator. // Alternatively, we could initialize as an 'Overshoot' Motivator. All // Motivator types have the same interface. Internally, they are animated // with different algorithms, and they will move differently towards their // targets. However, to switch between Motivator types it is a simple matter // of initializing with a different kind of MotiveInit struct. // // Angles wrap around with modular arithmetic. That is, -pi is equivalent to // pi. Valid range for angles is -pi..pi, inclusive of +pi and exclusive of // -pi. const motive::SmoothInit init(Range(-kPi, kPi), true); facing_angle.Initialize(init, &engine); // Set initial state of the Motivator, and the target parameters. // 'Smooth' Motivators animate to a target-value in a target-time. Not all // types of Motivators use all target data. const Angle start = Angle::FromDegrees(120.0f); const float start_angular_velocity = 0.0f; const Angle target = Angle::FromDegrees(-120.0f); const float target_angular_velocity = 0.0f; const motive::MotiveTime target_time = 100; const motive::MotiveTime delta_time = 1; facing_angle.SetTarget( motive::CurrentToTarget1f(start.ToRadians(), start_angular_velocity, target.ToRadians(), target_angular_velocity, target_time)); std::vector<vec2> points(target_time / delta_time + 1); for (motive::MotiveTime t = 0; t <= target_time; t += delta_time) { // All Motivators created with 'engine' are animated here. engine.AdvanceFrame(delta_time); // The current value of the variable being animated is always available. const Angle angle_at_t = Angle::FromWithinThreePi(facing_angle.Value()); points.push_back( vec2(static_cast<float>(t), angle_at_t.ToDegrees())); } printf("\n%s", Graph2DPoints(&points[0], points.size()).c_str()); return 0; }
MotiveBenchmarker() { MatrixInit::Register(); SplineInit::Register(); // Create compact splines by modifying some basic functions (straight // line and sign wave). The straight line, in this case, represents an // angle that travels a total of 2pi, so it ends up where it started. CreateSpline(kStraightLine, MOTIVE_ARRAY_SIZE(kStraightLine), kLinearOrbitPeriod, kTwoPi, &splines_[kLinearOrbit]); CreateSpline(kSinWave, MOTIVE_ARRAY_SIZE(kSinWave), kOscillatingSlowlyPeriod, kOscillatingSlowlyAmplitude, &splines_[kOscillatingSlowly]); CreateSpline(kSinWave, MOTIVE_ARRAY_SIZE(kSinWave), kOscillatingQuicklyPeriod, kOscillatingQuicklyAmplitude, &splines_[kOscillatingQuickly]); // Create a matrix initializer with a series of basic matrix operations. // The final matrix will be created by applying these operations, in turn. matrix_ops_.AddOp(motive::kRotateAboutY, kRotateInit, splines_[kLinearOrbit]); matrix_ops_.AddOp(motive::kTranslateX, kTranslateInit, splines_[kOscillatingSlowly]); matrix_ops_.AddOp(motive::kTranslateY, kTranslateInit, splines_[kOscillatingQuickly]); // Initialize the large array of matrix motivators. Note that the // one dimensional motivators that drive the matrix motivators are created // by the matrix motivators themselves. for (size_t i = 0; i < kNumMatrices; ++i) { MatrixMotivator4f& m = matrices_[i]; m.Initialize(MatrixInit(matrix_ops_), &engine_); } }
virtual void SetUp() { above_pi_ = MinutelyDifferentFloat(kPi, 1); below_pi_ = MinutelyDifferentFloat(kPi, -1); above_negative_pi_ = MinutelyDifferentFloat(-kPi, -1); below_negative_pi_ = MinutelyDifferentFloat(-kPi, 1); half_pi_ = Angle(kHalfPi); }
virtual void SetUp() { short_spline_.Init(Range(0.0f, 1.0f), 0.01f); short_spline_.AddNode(0.0f, 0.1f, 0.0f, motive::kAddWithoutModification); short_spline_.AddNode(1.0f, 0.4f, 0.0f, motive::kAddWithoutModification); short_spline_.AddNode(4.0f, 0.2f, 0.0f, motive::kAddWithoutModification); short_spline_.AddNode(40.0f, 0.2f, 0.0f, motive::kAddWithoutModification); short_spline_.AddNode(100.0f, 1.0f, 0.0f, motive::kAddWithoutModification); }
// Take an array of SplineNodes (x, y, derivative) values and scale them // to create a CompactSpline. We use Dual Cubic interpolation to ensure that // the splines are well behaved. static void CreateSpline(const SplineNode* nodes, size_t num_nodes, float x_scale, float y_scale, CompactSpline* spline) { // Find y-extremes. float min = std::numeric_limits<float>::infinity(); float max = -std::numeric_limits<float>::infinity(); for (size_t i = 0; i < num_nodes; ++i) { min = std::min(nodes[i].y, min); max = std::max(nodes[i].y, max); } // Initialize the spline such that it's bounds are tight to the data. spline->Init( Range(y_scale * min, y_scale * max), CompactSpline::RecommendXGranularity(x_scale * nodes[num_nodes - 1].x)); // Scale each node and add it to the curve. for (size_t i = 0; i < num_nodes; ++i) { const SplineNode& n = nodes[i]; spline->AddNode(n.x * x_scale, n.y * y_scale, n.derivative / x_scale); } }
int main() { std::vector<vec2> points; points.reserve(100); // Since we will be using the ‘smooth’ animation algorithm, we must register // it with the engine. SmoothInit::Register(); // The engine is the central place where animation data is stored and // processed. MotiveEngine motive_engine; //! [Duck Example] // Initialize the Motivator to use the "smooth" animation algorithm. Motivator2f duck_position; duck_position.InitializeWithTarget(SmoothInit(), &motive_engine, Tar2f::Current(kDuckStartPosition)); // Smoothly transition the duck to wherever a touch occurs. while (FollowDuck()) { vec2 touch_position; if (ScreenTouch(&touch_position)) { // Set the duck's target position, velocity, and time. duck_position.SetTarget(Tar2f::Target(touch_position, kZeros2f, kTimeToTouchPosition)); } // Once per frame, motive_engine.AdvanceFrame() is called to animate *all* // Motivators at once. In this case, we only have one Motivator, // but in many applications there will be thousands. motive_engine.AdvanceFrame(kDeltaTime); // Draw the duck at its current position. // Use the velocity to draw motion lines behind the duck, too. DrawDuck(duck_position.Value(), duck_position.Velocity()); } //! [Duck Example] return 0; }
// Unary negate should send pi to pi, because -pi is not in range. TEST_F(AngleTests, NegatePi) { const Angle a = Angle(kPi); const Angle negative_a = -a; EXPECT_FLOAT_EQ(negative_a.ToRadians(), kPi); }
// Unary negate should change the sign. TEST_F(AngleTests, Negate) { const Angle a = Angle(kHalfPi); EXPECT_FLOAT_EQ(-a.ToRadians(), -kHalfPi); }
using motive::CubicCurve; using motive::CubicInit; using motive::kPi; using motive::kTwoPi; using motive::QuadraticCurve; using motive::Range; using motive::SplinePlayback; using mathfu::vec2; using mathfu::vec2i; using motive::MotiveEngine; using motive::MatrixInit; using motive::MatrixOpArray; using motive::MatrixMotivator4f; using motive::SplineInit; static const SplineInit kRotateInit(Range(-kPi, kPi), true); static const SplineInit kTranslateInit(Range(-1.0f, 1.0f), true); static const int kNumBenchmarkIds = 10; struct SplineNode { float x; float y; float derivative; }; static const SplineNode kSinWave[] = {{0.0f, 0.0f, 1.0f}, {0.5f * kPi, 1.0f, 0.0f}, {kPi, 0.0f, -1.0f}, {1.5f * kPi, -1.0f, 0.0f}, {kTwoPi, 0.0f, 1.0f}};
static const CubicInit CubicInitScaleX(const CubicInit& init, float scale) { return CubicInit(init.start_y, init.start_derivative / scale, init.end_y, init.end_derivative / scale, init.width_x * scale); }
static const CubicInit CubicInitMirrorY(const CubicInit& init) { return CubicInit(-init.start_y, -init.start_derivative, -init.end_y, -init.end_derivative, init.width_x); }
static const float kDerivativePrecision = 0.01f; static const float kSecondDerivativePrecision = 0.26f; static const float kThirdDerivativePrecision = 6.0f; static const float kNodeXPrecision = 0.0001f; static const float kNodeYPrecision = 0.0001f; static const float kXGranularityScale = 0.01f; static const Range kAngleRange(-kPi, kPi); // Use a ridiculous index that will never hit when doing a search. // We use this to test the binary search algorithm, not the cache. static const CompactSplineIndex kRidiculousSplineIndex = 10000; static const CubicInit kSimpleSplines[] = { // start_y end_y width_x // start_derivative end_derivative CubicInit(0.0f, 1.0f, 0.1f, 0.0f, 1.0f), CubicInit(1.0f, -8.0f, 0.0f, 0.0f, 1.0f), CubicInit(1.0f, -8.0f, -1.0f, 0.0f, 1.0f), }; static const int kNumSimpleSplines = static_cast<int>(MOTIVE_ARRAY_SIZE(kSimpleSplines)); static const CubicInit CubicInitMirrorY(const CubicInit& init) { return CubicInit(-init.start_y, -init.start_derivative, -init.end_y, -init.end_derivative, init.width_x); } static const CubicInit CubicInitScaleX(const CubicInit& init, float scale) { return CubicInit(init.start_y, init.start_derivative / scale, init.end_y, init.end_derivative / scale, init.width_x * scale); }