Motive Animation System
An open source project by FPL.
 All Classes Functions Variables Typedefs Friends Pages
spline_util.h
1 // Copyright 2015 Google Inc. All rights reserved.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #ifndef MOTIVE_MATH_SPLINE_UTIL_H_
16 #define MOTIVE_MATH_SPLINE_UTIL_H_
17 
18 #include "mathfu/glsl_mappings.h"
19 
20 namespace motive {
21 
22 inline int NormalizeIdx(const int idx, const int max, const bool wraps) {
23  return wraps ? (idx < 0 ? idx + max : idx >= max ? idx - max : idx)
24  : (idx < 0 ? 0 : idx >= max ? max - 1 : idx);
25 }
26 
27 template <int kDimensions>
28 int FindFartherIdx(
29  const mathfu::VectorPacked<float, kDimensions>* const positions,
30  const int num_positions, const bool wraps, const int start_idx,
31  const int delta_idx, const float min_dist) {
32  typedef typename mathfu::Vector<float, kDimensions> Vec;
33 
34  const Vec start_position(positions[start_idx]);
35  const int end_idx = NormalizeIdx(
36  start_idx + delta_idx * (num_positions / 2 - 1), num_positions, wraps);
37  for (int i = NormalizeIdx(start_idx + delta_idx, num_positions, wraps);
38  i != end_idx; i = NormalizeIdx(i + delta_idx, num_positions, wraps)) {
39  const Vec delta = Vec(positions[i]) - start_position;
40  const float dist = delta.Length();
41  if (dist >= min_dist) return i;
42  }
43  return end_idx;
44 }
45 
46 /// @brief Calculate times and derivatives for a series of n-dimensional
47 /// `positions` such that the speed is approximately constant
48 /// and the entire traversal requires `total_time`.
49 /// @param positions Array of n-dimensional positions, length `num_positions`.
50 /// Note that `VectorPacked` is simply n-floats packed
51 /// together, so if necessary you can probably cast from your
52 /// own data type.
53 /// @param num_positions Length of the input `positions`, and the output
54 /// `times`, and `derivatives`.
55 /// @param total_time Total time to traverse all the `positions`.
56 /// Except in degenerate cases, when this function returns,
57 /// `times[num_positions - 1] = total_time`.
58 /// @param min_reliable_dist When calculating the tangents, we search adjacent
59 /// positions until we find one that is farther than
60 /// this distance. That is, we ignore `positions`
61 /// that are very close together in the tangent
62 /// calculation, and use positions that are farther
63 /// away. `min_reliable_dist` defines "very close
64 /// together".
65 /// @param times Output array that recieves the time we achieve each position.
66 /// Length `num_positions`.
67 /// @param derivatives Output array that recieves the derivative at each
68 /// position. Length `num_positions`.
69 template <int kDimensions>
70 void CalculateConstSpeedCurveFromPositions(
71  const mathfu::VectorPacked<float, kDimensions>* const positions,
72  const int num_positions, const float total_time,
73  const float min_reliable_dist, float* const times,
74  mathfu::VectorPacked<float, kDimensions>* const derivatives) {
75  typedef typename mathfu::Vector<float, kDimensions> Vec;
76 
77  // Handle degenerate cases where there aren't enough points to do calculate
78  // tangents.
79  if (num_positions <= 0) return;
80  if (num_positions == 1) {
81  derivatives[0] = Vec(0.0f);
82  times[0] = 0.0f;
83  return;
84  }
85 
86  // Approximate the curve distance with straight lines.
87  // This is a poor approximation, but it's good enough for our first pass.
88  std::vector<float> dist(num_positions);
89  dist[0] = 0.0f;
90  for (int i = 1; i < num_positions; ++i) {
91  const Vec delta = Vec(positions[i]) - Vec(positions[i - 1]);
92  dist[i] = dist[i - 1] + delta.Length();
93  }
94 
95  // Estimate the times based on the distance.
96  const float total_dist = dist[num_positions - 1];
97  const float const_speed_inv = total_time / total_dist;
98  const float const_speed = total_dist / total_time;
99  for (int i = 0; i < num_positions; ++i) {
100  times[i] = const_speed_inv * dist[i];
101  }
102 
103  // If the positions start and end at the same location, then assume the
104  // course wraps around. This affects the tangent calculation.
105  const Vec start_to_end =
106  Vec(positions[0]) - Vec(positions[num_positions - 1]);
107  const bool wraps = start_to_end.Length() < min_reliable_dist;
108 
109  // Calculate the tangents by searching backwards and forwards for points that
110  // are sufficiently far away to make a good calculation.
111  for (int i = 0; i < num_positions; ++i) {
112  const int prev_idx = FindFartherIdx(positions, num_positions, wraps, i, -1,
113  min_reliable_dist);
114  const int next_idx = FindFartherIdx(positions, num_positions, wraps, i, 1,
115  min_reliable_dist);
116  const Vec delta = Vec(positions[next_idx]) - Vec(positions[prev_idx]);
117  derivatives[i] = const_speed * delta.Normalized();
118  }
119 
120  // TODO: Create curves to get more accurate calculation of distance.
121  // Then adjust the times and scale the derivatives to match.
122 }
123 
124 } // namespace motive
125 
126 #endif // MOTIVE_MATH_SPLINE_UTIL_H_