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