1 /**
2 * @file smoother.cpp
3 * Interpolator for smoothing out a movement curve.
4 *
5 * The original movement path is composed out of discrete 3D points. Smoother
6 * calculates the points in between.
7 *
8 * This is used by the server to approximate the movement path of the clients'
9 * player mobjs.
10 *
11 * The movement of the smoother is guaranteed to not make jumps back
12 * into the past or change its course once the interpolation has begun
13 * between two points.
14 *
15 * @authors Copyright © 2011-2017 Jaakko Keränen <jaakko.keranen@iki.fi>
16 * @authors Copyright © 2013 Daniel Swanson <danij@dengine.net>
17 *
18 * @par License
19 * GPL: http://www.gnu.org/licenses/gpl.html
20 *
21 * <small>This program is free software; you can redistribute it and/or modify
22 * it under the terms of the GNU General Public License as published by the
23 * Free Software Foundation; either version 2 of the License, or (at your
24 * option) any later version. This program is distributed in the hope that it
25 * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
26 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
27 * Public License for more details. You should have received a copy of the GNU
28 * General Public License along with this program; if not, write to the Free
29 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
30 * 02110-1301 USA</small>
31 */
32
33 #include <stdlib.h>
34 #include <string.h>
35
36 #include "de/smoother.h"
37 #include <de/Log>
38
39 /**
40 * Timed 3D point in space.
41 */
42 typedef struct pos_s {
43 coord_t xyz[3];
44 float time;
45 dd_bool onFloor; // Special Z handling: should be on the floor.
46 } pos_t;
47
48 /** Points from the future. */
49 #define SM_NUM_POINTS 2
50
51 /**
52 * The smoother contains the data necessary to determine the
53 * coordinates on the smooth path at a certain point in time.
54 * It is assumed that time always moves forward.
55 */
56 struct smoother_s {
57 pos_t points[SM_NUM_POINTS]; // Future points.
58 pos_t past, now; // Current interpolation.
59 float at; // Current position in time for the smoother.
60 float maxDeltaBetweenPastAndNow;
61
62 #ifdef _DEBUG
63 float prevEval[2], prevAt;
64 #endif
65 };
66
Smoother_New()67 Smoother *Smoother_New()
68 {
69 Smoother *sm = static_cast<Smoother *>(calloc(sizeof(Smoother), 1));
70 return sm;
71 }
72
Smoother_SetMaximumPastNowDelta(Smoother * sm,float delta)73 void Smoother_SetMaximumPastNowDelta(Smoother *sm, float delta)
74 {
75 DENG_ASSERT(sm);
76 sm->maxDeltaBetweenPastAndNow = delta;
77 }
78
Smoother_Delete(Smoother * sm)79 void Smoother_Delete(Smoother *sm)
80 {
81 DENG_ASSERT(sm);
82 free(sm);
83 }
84
Smoother_Debug(Smoother const * sm)85 void Smoother_Debug(Smoother const *sm)
86 {
87 DENG_ASSERT(sm);
88 LOG_DEBUG("Smoother_Debug: [past=%3.3f / now=%3.3f / future=%3.3f] at=%3.3f")
89 << sm->past.time << sm->now.time << sm->points[0].time << sm->at;
90 }
91
Smoother_IsValid(Smoother const * sm)92 static dd_bool Smoother_IsValid(Smoother const *sm)
93 {
94 DENG_ASSERT(sm);
95 if (sm->past.time == 0 || sm->now.time == 0)
96 {
97 // We don't have valid data.
98 return false;
99 }
100 return true;
101 }
102
Smoother_Clear(Smoother * sm)103 void Smoother_Clear(Smoother *sm)
104 {
105 if (!sm) return;
106
107 float maxDelta = sm->maxDeltaBetweenPastAndNow;
108 memset(sm, 0, sizeof(*sm));
109 sm->maxDeltaBetweenPastAndNow = maxDelta;
110 }
111
Smoother_AddPosXY(Smoother * sm,float time,coord_t x,coord_t y)112 void Smoother_AddPosXY(Smoother *sm, float time, coord_t x, coord_t y)
113 {
114 Smoother_AddPos(sm, time, x, y, 0, false);
115 }
116
Smoother_AddPos(Smoother * sm,float time,coord_t x,coord_t y,coord_t z,dd_bool onFloor)117 void Smoother_AddPos(Smoother *sm, float time, coord_t x, coord_t y, coord_t z, dd_bool onFloor)
118 {
119 pos_t *last;
120 DENG_ASSERT(sm);
121
122 // Is it the same point?
123 last = &sm->points[SM_NUM_POINTS - 1];
124 if (last->time == time)
125 {
126 if (last->xyz[VX] == x && last->xyz[VY] == y && last->xyz[VZ] == z)
127 {
128 // Ignore it.
129 return;
130 }
131
132 // Readjusting a previously set value?
133 goto replaceLastPoint;
134 }
135
136 if (time <= sm->now.time)
137 {
138 // The new point would be in the past, this is no good.
139 #ifdef _DEBUG
140 LOG_DEBUG("Smoother_AddPos: DISCARDING new pos, time=%f, now=%f.") << time << sm->now.time;
141 #endif
142 Smoother_Clear(sm);
143 return;
144 }
145
146 // If we are about to discard an unused future point, we will force
147 // the current interpolation into the future.
148 if (Smoother_IsValid(sm) && sm->points[0].time > sm->now.time)
149 {
150 coord_t mid[3] = { 0, 0, 0 };
151 // Move the past forward in time so that the interpolation remains continuous.
152 float remaining = sm->now.time - sm->at;
153
154 Smoother_Evaluate(sm, mid);
155 sm->at = sm->past.time = sm->points[0].time - remaining;
156 sm->past.xyz[VX] = mid[VX];
157 sm->past.xyz[VY] = mid[VY];
158 sm->past.xyz[VZ] = mid[VZ];
159
160 // Replace the now with the point about to be discarded.
161 memcpy(&sm->now, &sm->points[0], sizeof(pos_t));
162 }
163
164 // Rotate the old points.
165 memmove(&sm->points[0], &sm->points[1], sizeof(pos_t) * (SM_NUM_POINTS - 1));
166
167 replaceLastPoint:
168 last = &sm->points[SM_NUM_POINTS - 1];
169 last->time = time;
170 last->xyz[VX] = x;
171 last->xyz[VY] = y;
172 last->xyz[VZ] = z;
173 last->onFloor = onFloor;
174
175 // Is this the first one?
176 if (sm->now.time == 0)
177 {
178 sm->at = time;
179 memcpy(&sm->past, last, sizeof(pos_t));
180 memcpy(&sm->now, last, sizeof(pos_t));
181 }
182 }
183
Smoother_EvaluateComponent(Smoother const * sm,int component,coord_t * v)184 dd_bool Smoother_EvaluateComponent(Smoother const *sm, int component, coord_t *v)
185 {
186 coord_t xyz[3];
187
188 DENG_ASSERT(component >= 0 && component < 3);
189 DENG_ASSERT(v != 0);
190
191 if (!Smoother_Evaluate(sm, xyz)) return false;
192
193 *v = xyz[component];
194 return true;
195 }
196
Smoother_Evaluate(Smoother const * sm,coord_t * xyz)197 dd_bool Smoother_Evaluate(Smoother const *sm, coord_t *xyz)
198 {
199 DENG2_ASSERT(sm);
200 pos_t const *past = &sm->past;
201 pos_t const *now = &sm->now;
202
203 if (!Smoother_IsValid(sm))
204 return false;
205
206 if (sm->at < past->time)
207 {
208 // Before our time.
209 xyz[VX] = past->xyz[VX];
210 xyz[VY] = past->xyz[VY];
211 xyz[VZ] = past->xyz[VZ];
212 //LOGDEV_XVERBOSE("Smoother %p falling behind") << sm;
213 return true;
214 }
215 //DENG_ASSERT(sm->at <= now->time);
216 if (now->time <= past->time)
217 {
218 // Too far in the ever-shifting future.
219 xyz[VX] = now->xyz[VX];
220 xyz[VY] = now->xyz[VY];
221 xyz[VZ] = now->xyz[VZ];
222 //LOGDEV_XVERBOSE("Smoother %p stalling") << sm;
223 return true;
224 }
225
226 // We're somewhere between past and now.
227 float const t = (sm->at - past->time) / (now->time - past->time);
228 for (int i = 0; i < 3; ++i)
229 {
230 // Linear interpolation.
231 xyz[i] = now->xyz[i] * t + past->xyz[i] * (1-t);
232 }
233
234 /*#ifdef _DEBUG
235 {
236 float dt = sm->at - sm->prevAt;
237 //Smoother_Debug(sm);
238 if (dt > 0)
239 {
240 float diff[2] = { xyz[0] - sm->prevEval[0], xyz[1] - sm->prevEval[1] };
241 LOGDEV_MSG("Smoother_Evaluate: [%05.3f] diff = %06.3f %06.3f")
242 << dt << diff[0]/dt << diff[1]/dt;
243 ((Smoother *)sm)->prevEval[0] = xyz[0];
244 ((Smoother *)sm)->prevEval[1] = xyz[1];
245 }
246 ((Smoother *)sm)->prevAt = sm->at;
247 }
248 #endif*/
249 return true;
250 }
251
Smoother_IsOnFloor(Smoother const * sm)252 dd_bool Smoother_IsOnFloor(Smoother const *sm)
253 {
254 DENG_ASSERT(sm);
255
256 const pos_t *past = &sm->past;
257 const pos_t *now = &sm->now;
258
259 if (!Smoother_IsValid(sm)) return false;
260 return (past->onFloor && now->onFloor);
261 }
262
Smoother_IsMoving(Smoother const * sm)263 dd_bool Smoother_IsMoving(Smoother const *sm)
264 {
265 DENG_ASSERT(sm);
266
267 const pos_t *past = &sm->past;
268 const pos_t *now = &sm->now;
269
270 // The smoother is moving if the current past and present are different
271 // points in time and space.
272 return sm->at >= past->time && sm->at <= now->time && past->time < now->time &&
273 (!INRANGE_OF(past->xyz[VX], now->xyz[VX], SMOOTHER_MOVE_EPSILON) ||
274 !INRANGE_OF(past->xyz[VY], now->xyz[VY], SMOOTHER_MOVE_EPSILON) ||
275 !INRANGE_OF(past->xyz[VZ], now->xyz[VZ], SMOOTHER_MOVE_EPSILON));
276 }
277
Smoother_Advance(Smoother * sm,float period)278 void Smoother_Advance(Smoother *sm, float period)
279 {
280 int i;
281
282 DENG_ASSERT(sm);
283
284 if (period <= 0) return;
285
286 sm->at += period;
287
288 // Did we go past the present?
289 while (sm->at > sm->now.time)
290 {
291 int j = -1;
292
293 // The present has become the past.
294 memcpy(&sm->past, &sm->now, sizeof(pos_t));
295
296 // Choose the next point from the future.
297 for (i = 0; i < SM_NUM_POINTS; ++i)
298 {
299 if (sm->points[i].time > sm->now.time)
300 {
301 // Use this one.
302 j = i;
303 break;
304 }
305 }
306 if (j < 0)
307 {
308 // No points were applicable. We need to stop here until
309 // new points are received.
310 sm->at = sm->now.time;
311 break;
312 }
313 else
314 {
315 memcpy(&sm->now, &sm->points[j], sizeof(pos_t));
316 }
317 }
318
319 if (sm->maxDeltaBetweenPastAndNow > 0 &&
320 sm->now.time - sm->past.time > sm->maxDeltaBetweenPastAndNow)
321 {
322 // Refresh the past.
323 sm->past.time = sm->now.time;
324 }
325
326 if (sm->at < sm->past.time)
327 {
328 // Don't fall too far back.
329 sm->at = sm->past.time;
330 }
331 }
332