1 //-----------------------------------------------------------------------------
2 // Demo Header
3 //-----------------------------------------------------------------------------
4 
5 #include "demo.h"
6 #include "console.h"
7 #include "vars.h"
8 #include "timer.h"
9 #include "math.h"
10 #include "mem.h"
11 
12 #define DEMO_VERSION        "0.1.1"
13 #define FRAMES_ALLOCATION_BLOC    64
14 #define SPLINE_LINEAR_LIMIT     0.1f
15 
Demo()16 Demo::Demo()
17 {
18   numframes = 0;
19   numframesAllocated = 0;
20   frames = NULL;
21   cam_positions = NULL;
22   cam_rotations = NULL;
23   cam_positions_tangents = NULL;
24   cam_rotations_tangents = NULL;
25   timings = NULL;
26   isLooping = false;
27 
28   noclipstate = false;
29 
30   currclient = NULL;
31 
32   isRecording = false;
33   isPlaying = false;
34 }
35 
~Demo()36 Demo::~Demo()
37 {
38   if (frames) cake_free(frames);
39   if (cam_positions) cake_free(cam_positions);
40   if (cam_rotations) cake_free(cam_rotations);
41   if (cam_positions_tangents) cake_free(cam_positions_tangents);
42   if (cam_rotations_tangents) cake_free(cam_rotations_tangents);
43   if (timings) cake_free(timings);
44   numframes = 0;
45   numframesAllocated = 0;
46   frames = NULL;
47   cam_positions = NULL;
48   cam_rotations = NULL;
49   cam_positions_tangents = NULL;
50   cam_rotations_tangents = NULL;
51   timings = NULL;
52   currclient = NULL;
53 }
54 
StartDemoRecording(Client * client,const char * mapname,float framesinterval)55 int Demo::StartDemoRecording(Client *client, const char *mapname, float framesinterval)
56 {
57   if (!client || isRecording || isPlaying) return 0;
58 
59   currclient = client;
60   frame_interval = max(framesinterval, 0);
61   last_frame_time = start_demo_time = (float) Timer::fTime;
62   memset(currmapname, '\0', 32);
63   strcpy(currmapname, mapname);
64 
65   // Allocate memory for frames
66   frames = (DemoFrame*) cake_malloc(FRAMES_ALLOCATION_BLOC*sizeof(DemoFrame), "Demo::StartDemoRecording.frames");
67   if (!frames) { gConsole->Insertln("^1Demo::StartDemoRecording: Cannot allocate required size"); return 0; }
68 
69   // generate first frame
70   frames[0].timing = 0;
71   VectorCopy(currclient->cam.pos, frames[0].campos);
72   VectorCopy(currclient->cam.rot, frames[0].camrot);
73   numframes = 1;
74   numframesAllocated = FRAMES_ALLOCATION_BLOC;
75 
76   isRecording = true;
77 
78   return 1;
79 }
80 
StopDemoRecording(const char * destfile)81 int Demo::StopDemoRecording(const char* destfile)
82 {
83   if (isPlaying || !isRecording) return 0;
84 
85   // Generate last frame
86   AddFrame();
87 
88   FILE *fp = fopen(destfile, "wb");
89   if (!fp)
90   {
91     gConsole->Insertln("Unable to create demo file \"%s\".", destfile);
92     return 0;
93   }
94 
95   // Write header
96   DemoHeader head;
97   head.num_frames = numframes;
98   strcpy(head.version, DEMO_VERSION);
99   memset(head.map, '\0', 32);
100   strcpy(head.map, currmapname);
101   head.capture_interval = frame_interval;
102   fwrite(&head, sizeof(DemoHeader), 1, fp);
103 
104   // Write frames lump
105   fwrite(frames, sizeof(DemoFrame), numframes, fp);
106 
107   fclose(fp);
108 
109   // Unallocate memory
110   if (frames) cake_free(frames);
111   frames = NULL;
112   numframes = 0;
113   numframesAllocated = 0;
114   currclient = NULL;
115 
116   isRecording = false;
117 
118   return 1;
119 }
120 
AddFrame(void)121 void Demo::AddFrame(void)
122 {
123   if (numframes == numframesAllocated)
124   {
125     // Reallocate memory for frames
126     frames = (DemoFrame*) cake_realloc(frames, (numframesAllocated+FRAMES_ALLOCATION_BLOC)*sizeof(DemoFrame), "Demo::AddFrame");
127     if (!frames) { gConsole->Insertln("^1Demo::AddFrame: Cannot allocate required size"); return; }
128     numframesAllocated += FRAMES_ALLOCATION_BLOC;
129   }
130 
131   frames[numframes].timing = (float) Timer::fTime - start_demo_time;
132   VectorCopy(currclient->cam.pos, frames[numframes].campos);
133   VectorCopy(currclient->cam.rot, frames[numframes].camrot);
134   ++numframes;
135 }
136 
LoadDemo(const char * demofile,enum_InterpolationMode mode)137 char* Demo::LoadDemo(const char* demofile, enum_InterpolationMode mode)
138 {
139   if (isRecording || isPlaying || currclient) return "";
140 
141   FILE *fp = fopen(demofile, "rb");
142   if (!fp)
143   {
144     gConsole->Insertln("^1Unable to open demo file \"%s\".", demofile);
145     return "";
146   }
147 
148   DemoHeader head;
149   fread(&head, sizeof(DemoHeader), 1, fp);
150   if (strcmp(head.version, DEMO_VERSION))
151   {
152     gConsole->Insertln("^1Unsupported demo version (file version: %s - supported: %s).", head.version, DEMO_VERSION);
153     fclose(fp);
154     return "";
155   }
156 
157   // Read frames from file
158   numframes = numframesAllocated = head.num_frames;
159   frames = (DemoFrame*) cake_malloc(numframes*sizeof(DemoFrame), "Demo::LoadDemo.frames");
160   fread(frames, sizeof(DemoFrame), numframes, fp);
161   fclose(fp);
162 
163   // Allocate memory for frames
164   cam_positions = (vec3_t*) cake_malloc(numframes*sizeof(vec3_t), "Demo::LoadDemo.cam_positions");
165   cam_rotations = (vec3_t*) cake_malloc(numframes*sizeof(vec3_t), "Demo::LoadDemo.cam_rotations");
166   timings = (float*) cake_malloc(numframes*sizeof(float), "Demo::LoadDemo.timings");
167   if (!cam_positions || !cam_rotations || !timings)
168   {
169     gConsole->Insertln("^1Demo::LoadDemo: Cannot allocate required size");
170     return "";
171   }
172   for (int i = 0; i < numframes; ++i)
173   {
174     VectorCopy(frames[i].campos, cam_positions[i]);
175     VectorCopy(frames[i].camrot, cam_rotations[i]);
176     timings[i] = frames[i].timing;
177   }
178 
179   // Free unused memory
180   free(frames);
181   frames = NULL;
182 
183   // Copy map name
184   memset(currmapname, '\0', 32);
185   strcpy(currmapname, head.map);
186 
187   interpolationmode = mode;
188   if (interpolationmode == AUTO_INTERPOLATION)
189   {
190     if (head.capture_interval < SPLINE_LINEAR_LIMIT)
191       interpolationmode = LINEAR_INTERPOLATION;
192     else
193       interpolationmode = SPLINE_INTERPOLATION;
194   }
195 
196   if (interpolationmode == SPLINE_INTERPOLATION)
197   {
198     // Computes the tangents at keyframes
199     cam_positions_tangents = (vec3_t*) cake_malloc(numframes*sizeof(vec3_t), "Demo::LoadDemo.cam_rotations_tangents");
200     cam_rotations_tangents = (vec3_t*) cake_malloc(numframes*sizeof(vec3_t), "Demo::LoadDemo.cam_rotations_tangents");
201     if (!cam_positions_tangents || !cam_rotations_tangents)
202     {
203       gConsole->Insertln("^1Demo::LoadDemo_spline: Cannot allocate required size");
204       return "";
205     }
206     VectorClear(cam_positions_tangents[0]);
207     VectorClear(cam_rotations_tangents[0]);
208     VectorClear(cam_positions_tangents[numframes-1]);
209     VectorClear(cam_rotations_tangents[numframes-1]);
210     vec3_t a, b;
211     int i, j, k;
212     for (j = 1; j < numframes-1; ++j)
213     {
214       i = max(j-1, 0);
215       k = min(j+1, numframes-1);
216       VectorSub(cam_positions[j], cam_positions[i], a);
217       VectorSub(cam_positions[k], cam_positions[j], b);
218       VectorScale(a, 0.5f, a);
219       VectorScale(b, 0.5f, b);
220       VectorAdd(a, b, cam_positions_tangents[j]);
221 
222       VectorSub(cam_rotations[j], cam_rotations[i], a);
223       VectorSub(cam_rotations[k], cam_rotations[j], b);
224       VectorScale(a, 0.5f, a);
225       VectorScale(b, 0.5f, b);
226       VectorAdd(a, b, cam_rotations_tangents[j]);
227     }
228   }
229 
230   return currmapname;
231 }
232 
StartDemoPlaying(Client * client,bool loop)233 void Demo::StartDemoPlaying(Client *client, bool loop)
234 {
235   if (!client || isPlaying || isRecording) return;
236   Timer::Refresh();
237   start_demo_time = (float) Timer::fTime;
238   currframenum = 1;
239   isPlaying = true;
240   isLooping = loop;
241 
242   // set client flying and noclipping
243   currclient = client;
244   currclient->flying = true;
245   noclipstate = client->noclip;
246   client->noclip = true;
247 }
248 
StopDemoPlaying(void)249 void Demo::StopDemoPlaying(void)
250 {
251   if (!currclient || !isPlaying || isRecording) return;
252 
253   // restore noclip value
254   currclient->noclip = noclipstate;
255 
256   // Unallocate memory
257   if (cam_positions) cake_free(cam_positions); cam_positions = NULL;
258   if (cam_rotations) cake_free(cam_rotations); cam_rotations = NULL;
259   if (cam_positions_tangents) cake_free(cam_positions_tangents); cam_positions_tangents = NULL;
260   if (cam_rotations_tangents) cake_free(cam_rotations_tangents); cam_rotations_tangents = NULL;
261 
262   if (timings) cake_free(timings); timings = NULL;
263   numframes = numframesAllocated = 0;
264   currclient = NULL;
265   isPlaying = false;
266 }
267 
NewFrame(void)268 float Demo::NewFrame(void)
269 {
270   if (!currclient) return -1;
271 
272   if (isRecording)
273   {
274     if (Timer::fTime - last_frame_time < frame_interval) return -1;
275 
276     AddFrame();
277 
278     last_frame_time = (float) Timer::fTime;
279   }
280   else if (isPlaying)
281   {
282     while (timings[currframenum] < (Timer::fTime - start_demo_time))
283     {
284       if (++currframenum >= numframes)
285       {
286         // demo is over
287         if (isLooping)
288         {
289           start_demo_time = (float) Timer::fTime;
290           currframenum = 1;
291         }
292         else
293         {
294           StopDemoPlaying();
295           gConsole->Insertln("Demo ended.");
296           return -1;
297         }
298       }
299     }
300 
301     float timeframe_a, timeframe_b;
302     timeframe_b = timings[currframenum];
303     timeframe_a = timings[currframenum-1];
304 
305     float currtime = (float) Timer::fTime - start_demo_time;    // elapsed time since demo has started
306     float u = (currtime - timeframe_a)/(timeframe_b - timeframe_a); // current progress in the frame (0 -> 1)
307 
308     switch (interpolationmode)
309     {
310       case LINEAR_INTERPOLATION:
311       {
312         // Set client interpolated position and rotation
313         vec3_t interpol_vect;
314         VectorSub(cam_positions[currframenum], cam_positions[currframenum-1], interpol_vect);
315         VectorMA(cam_positions[currframenum-1], u, interpol_vect, currclient->cam.pos);
316 
317         VectorSub(cam_rotations[currframenum], cam_rotations[currframenum-1], interpol_vect);
318         VectorMA(cam_rotations[currframenum-1], u, interpol_vect, currclient->cam.rot);
319       }
320       break;
321       case SPLINE_INTERPOLATION:
322       {
323         // Based on gamedev article "Smooth interpolation of irregularly spaced keyframes"
324         vec3_t interpol_vect;
325         float a = 2*u*u*u-3*u*u+1;
326         float b = 3*u*u-2*u*u*u;
327         float c = u*u*u-2*u*u+u;
328         float d = u*u*u-u*u;
329 
330         // need tangents at keyframes
331         VectorScale(cam_positions[currframenum-1], a, interpol_vect);
332         VectorMA(interpol_vect, b, cam_positions[currframenum], interpol_vect);
333         VectorMA(interpol_vect, c, cam_positions_tangents[currframenum-1], interpol_vect);
334         VectorMA(interpol_vect, d, cam_positions_tangents[currframenum], currclient->cam.pos);
335 
336         VectorScale(cam_rotations[currframenum-1], a, interpol_vect);
337         VectorMA(interpol_vect, b, cam_rotations[currframenum], interpol_vect);
338         VectorMA(interpol_vect, c, cam_rotations_tangents[currframenum-1], interpol_vect);
339         VectorMA(interpol_vect, d, cam_rotations_tangents[currframenum], currclient->cam.rot);
340       }
341       break;
342       default:
343         break;
344     }
345   }
346 
347   return 100.f*(float)currframenum/(float)numframes;
348 }
349 
DemoDump(char * demofile,int n)350 void Demo::DemoDump(char *demofile, int n)
351 {
352   FILE *fp = fopen(demofile, "rb");
353   if (!fp)
354   {
355     gConsole->Insertln("^1Unable to open demo file \"%s\".", demofile);
356     return;
357   }
358 
359   DemoHeader head;
360   fread(&head, sizeof(DemoHeader), 1, fp);
361 
362   DemoFrame *tmp_frames = (DemoFrame*) cake_malloc(head.num_frames*sizeof(DemoFrame), "Demo::DemoDump.tmp_frames");
363   fread(tmp_frames, sizeof(DemoFrame), head.num_frames, fp);
364   fclose(fp);
365 
366   gConsole->Insertln("Demo file \"%s\":", demofile);
367   gConsole->Insertln("\tHeader:");
368   gConsole->Insertln("\t\tVersion: %s", head.version);
369   gConsole->Insertln("\t\tUsed map: %s", head.map);
370   gConsole->Insertln("\t\tNumber of frames: %d", head.num_frames);
371   gConsole->Insertln("\t\tRecord interval: %f", head.capture_interval);
372   if (n > 0) gConsole->Insertln("\tContent:");
373   for (int i = 0; i < head.num_frames; ++i)
374   {
375     if (i == n) break;
376     gConsole->Insertln("\t\tFrame %d:", i);
377     gConsole->Insertln("\t\t\tTiming: %f", tmp_frames[i].timing);
378     gConsole->Insertln("\t\t\tCamera position: %f %f %f", tmp_frames[i].campos[0], tmp_frames[i].campos[1], tmp_frames[i].campos[2]);
379     gConsole->Insertln("\t\t\tCamera rotation: %f %f %f", tmp_frames[i].camrot[0], tmp_frames[i].camrot[1], tmp_frames[i].camrot[2]);
380   }
381 
382   cake_free(tmp_frames);
383 
384   return;
385 }
386