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