]> git.xonotic.org Git - voretournament/voretournament.git/blob - misc/source/netradiant-src/libs/splines/splines.cpp
Rename mediasource to source
[voretournament/voretournament.git] / misc / source / netradiant-src / libs / splines / splines.cpp
1 /*
2 Copyright (C) 1999-2006 Id Software, Inc. and contributors.
3 For a list of contributors, see the accompanying CONTRIBUTORS file.
4
5 This file is part of GtkRadiant.
6
7 GtkRadiant is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
11
12 GtkRadiant is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with GtkRadiant; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
20 */
21
22 #include "q_shared.h"
23 #include "splines.h"
24
25 extern "C" {
26 int FS_Write( const void *buffer, int len, fileHandle_t h );
27 int FS_ReadFile( const char *qpath, void **buffer );
28 void FS_FreeFile( void *buffer );
29 fileHandle_t FS_FOpenFileWrite( const char *filename );
30 void FS_FCloseFile( fileHandle_t f );
31 void Cbuf_AddText( const char *text );
32 void Cbuf_Execute (void);
33 }
34
35 float Q_fabs( float f ) {
36         int tmp = * ( int * ) &f;
37         tmp &= 0x7FFFFFFF;
38         return * ( float * ) &tmp;
39 }
40
41 // (SA) making a list of cameras so I can use
42 //              the splines as targets for other things.
43 //              Certainly better ways to do this, but this lets
44 //              me get underway quickly with ents that need spline
45 //              targets.
46 #define MAX_CAMERAS 64
47
48 idCameraDef camera[MAX_CAMERAS];
49
50 extern "C" {
51 qboolean loadCamera(int camNum, const char *name) {
52         if(camNum < 0 || camNum >= MAX_CAMERAS )
53                 return qfalse;
54         camera[camNum].clear();
55         return (qboolean)camera[camNum].load(name);
56 }
57
58 qboolean getCameraInfo(int camNum, int time, float *origin, float *angles, float *fov) {
59         idVec3 dir, org;
60         if(camNum < 0 || camNum >= MAX_CAMERAS )
61                 return qfalse;
62         org[0] = origin[0];
63         org[1] = origin[1];
64         org[2] = origin[2];
65         if (camera[camNum].getCameraInfo(time, org, dir, fov)) {
66                 origin[0] = org[0];
67                 origin[1] = org[1];
68                 origin[2] = org[2];
69                 angles[1] = atan2 (dir[1], dir[0])*180/3.14159;
70                 angles[0] = asin (dir[2])*180/3.14159;
71                 return qtrue;
72         }
73         return qfalse;
74 }
75
76 void startCamera(int camNum, int time) {
77         if(camNum < 0 || camNum >= MAX_CAMERAS )
78                 return;
79         camera[camNum].startCamera(time);
80 }
81
82 }
83
84
85 //#include "../shared/windings.h"
86 //#include "../qcommon/qcommon.h"
87 //#include "../sys/sys_public.h"
88 //#include "../game/game_entity.h"
89
90 idCameraDef splineList;
91 idCameraDef *g_splineList = &splineList;
92
93 idVec3 idSplineList::zero(0,0,0);
94
95 void glLabeledPoint(idVec3 &color, idVec3 &point, float size, const char *label) {
96   glColor3fv(color);
97   glPointSize(size);
98   glBegin(GL_POINTS);
99   glVertex3fv(point);
100   glEnd();
101         idVec3 v = point;
102         v.x += 1;
103         v.y += 1;
104         v.z += 1;
105   glRasterPos3fv (v);
106   glCallLists (strlen(label), GL_UNSIGNED_BYTE, label);
107 }
108
109
110 void glBox(idVec3 &color, idVec3 &point, float size) {
111         idVec3 mins(point);
112         idVec3 maxs(point);
113         mins[0] -= size;
114         mins[1] += size;
115         mins[2] -= size;
116         maxs[0] += size;
117         maxs[1] -= size;
118         maxs[2] += size;
119   glColor3fv(color);
120   glBegin(GL_LINE_LOOP);
121   glVertex3f(mins[0],mins[1],mins[2]);
122   glVertex3f(maxs[0],mins[1],mins[2]);
123   glVertex3f(maxs[0],maxs[1],mins[2]);
124   glVertex3f(mins[0],maxs[1],mins[2]);
125   glEnd();
126   glBegin(GL_LINE_LOOP);
127   glVertex3f(mins[0],mins[1],maxs[2]);
128   glVertex3f(maxs[0],mins[1],maxs[2]);
129   glVertex3f(maxs[0],maxs[1],maxs[2]);
130   glVertex3f(mins[0],maxs[1],maxs[2]);
131   glEnd();
132
133   glBegin(GL_LINES);
134     glVertex3f(mins[0],mins[1],mins[2]);
135   glVertex3f(mins[0],mins[1],maxs[2]);
136   glVertex3f(mins[0],maxs[1],maxs[2]);
137   glVertex3f(mins[0],maxs[1],mins[2]);
138   glVertex3f(maxs[0],mins[1],mins[2]);
139   glVertex3f(maxs[0],mins[1],maxs[2]);
140   glVertex3f(maxs[0],maxs[1],maxs[2]);
141   glVertex3f(maxs[0],maxs[1],mins[2]);
142   glEnd();
143
144 }
145
146 void splineTest() {
147         //g_splineList->load("p:/doom/base/maps/test_base1.camera");
148 }
149
150 void splineDraw() {
151         //g_splineList->addToRenderer();
152 }
153
154
155 //extern void D_DebugLine( const idVec3 &color, const idVec3 &start, const idVec3 &end );
156
157 void debugLine(idVec3 &color, float x, float y, float z, float x2, float y2, float z2) {
158         idVec3 from(x, y, z);
159         idVec3 to(x2, y2, z2);
160         //D_DebugLine(color, from, to);
161 }
162
163 void idSplineList::addToRenderer() {
164
165         if (controlPoints.Num() == 0) {
166                 return;
167         }
168
169         idVec3 mins, maxs;
170         idVec3 yellow(1.0, 1.0, 0);
171         idVec3 white(1.0, 1.0, 1.0);
172         int i;
173         
174         for(i = 0; i < controlPoints.Num(); i++) {
175                 VectorCopy(*controlPoints[i], mins);
176                 VectorCopy(mins, maxs);
177                 mins[0] -= 8;
178                 mins[1] += 8;
179                 mins[2] -= 8;
180                 maxs[0] += 8;
181                 maxs[1] -= 8;
182                 maxs[2] += 8;
183                 debugLine( yellow, mins[0], mins[1], mins[2], maxs[0], mins[1], mins[2]);
184                 debugLine( yellow, maxs[0], mins[1], mins[2], maxs[0], maxs[1], mins[2]);
185                 debugLine( yellow, maxs[0], maxs[1], mins[2], mins[0], maxs[1], mins[2]);
186                 debugLine( yellow, mins[0], maxs[1], mins[2], mins[0], mins[1], mins[2]);
187                 
188                 debugLine( yellow, mins[0], mins[1], maxs[2], maxs[0], mins[1], maxs[2]);
189                 debugLine( yellow, maxs[0], mins[1], maxs[2], maxs[0], maxs[1], maxs[2]);
190                 debugLine( yellow, maxs[0], maxs[1], maxs[2], mins[0], maxs[1], maxs[2]);
191                 debugLine( yellow, mins[0], maxs[1], maxs[2], mins[0], mins[1], maxs[2]);
192             
193         }
194
195         int step = 0;
196         idVec3 step1;
197         for(i = 3; i < controlPoints.Num(); i++) {
198                 for (float tension = 0.0f; tension < 1.001f; tension += 0.1f) {
199                         float x = 0;
200                         float y = 0;
201                         float z = 0;
202                         for (int j = 0; j < 4; j++) {
203                                 x += controlPoints[i - (3 - j)]->x * calcSpline(j, tension);
204                                 y += controlPoints[i - (3 - j)]->y * calcSpline(j, tension);
205                                 z += controlPoints[i - (3 - j)]->z * calcSpline(j, tension);
206                         }
207                         if (step == 0) {
208                                 step1[0] = x;
209                                 step1[1] = y;
210                                 step1[2] = z;
211                                 step = 1;
212                         } else {
213                                 debugLine( white, step1[0], step1[1], step1[2], x, y, z);
214                                 step = 0;
215                         }
216
217                 }
218         }
219 }
220
221 void idSplineList::buildSpline() {
222         //int start = Sys_Milliseconds();
223         clearSpline();
224         for(int i = 3; i < controlPoints.Num(); i++) {
225                 for (float tension = 0.0f; tension < 1.001f; tension += granularity) {
226                         float x = 0;
227                         float y = 0;
228                         float z = 0;
229                         for (int j = 0; j < 4; j++) {
230                                 x += controlPoints[i - (3 - j)]->x * calcSpline(j, tension);
231                                 y += controlPoints[i - (3 - j)]->y * calcSpline(j, tension);
232                                 z += controlPoints[i - (3 - j)]->z * calcSpline(j, tension);
233                         }
234                         splinePoints.Append(new idVec3(x, y, z));
235                 }
236         }
237         dirty = false;
238         //Com_Printf("Spline build took %f seconds\n", (float)(Sys_Milliseconds() - start) / 1000);
239 }
240
241
242 void idSplineList::draw(bool editMode) {
243         int i;
244         idVec4 yellow(1, 1, 0, 1);
245         
246         if (controlPoints.Num() == 0) {
247                 return;
248         }
249
250         if (dirty) {
251                 buildSpline();
252         }
253
254
255   glColor3fv(controlColor);
256   glPointSize(5);
257         
258   glBegin(GL_POINTS);
259         for (i = 0; i < controlPoints.Num(); i++) {
260           glVertex3fv(*controlPoints[i]);
261         }
262   glEnd();
263         
264         if (editMode) {
265                 for(i = 0; i < controlPoints.Num(); i++) {
266                         glBox(activeColor, *controlPoints[i], 4);
267                 }
268         }
269
270         //Draw the curve
271   glColor3fv(pathColor);
272   glBegin(GL_LINE_STRIP);
273         int count = splinePoints.Num();
274         for (i = 0; i < count; i++) {
275           glVertex3fv(*splinePoints[i]);
276         }
277   glEnd();
278
279         if (editMode) {
280           glColor3fv(segmentColor);
281           glPointSize(3);
282           glBegin(GL_POINTS);
283                 for (i = 0; i < count; i++) {
284                   glVertex3fv(*splinePoints[i]);
285                 }
286           glEnd();
287         }
288         if (count > 0) {
289                 //assert(activeSegment >=0 && activeSegment < count);
290                 if (activeSegment >=0 && activeSegment < count) {
291                         glBox(activeColor, *splinePoints[activeSegment], 6);
292                         glBox(yellow, *splinePoints[activeSegment], 8);
293                 }
294         }
295
296 }
297
298 float idSplineList::totalDistance() {
299
300         // FIXME: save dist and return
301         // 
302         if (controlPoints.Num() == 0) {
303                 return 0.0;
304         }
305
306         if (dirty) {
307                 buildSpline();
308         }
309
310         float dist = 0.0;
311         idVec3 temp;
312         int count = splinePoints.Num();
313         for(int i = 1; i < count; i++) {
314                 temp = *splinePoints[i-1];
315                 temp -= *splinePoints[i];
316                 dist += temp.Length();
317         }
318         return dist;
319 }
320
321 void idSplineList::initPosition(long bt, long totalTime) {
322
323         if (dirty) {
324                 buildSpline();
325         }
326
327         if (splinePoints.Num() == 0) {
328                 return;
329         }
330
331         baseTime = bt;
332         time = totalTime;
333
334         // calc distance to travel ( this will soon be broken into time segments )
335         splineTime.Clear();
336         splineTime.Append(bt);
337         double dist = totalDistance();
338         double distSoFar = 0.0;
339         idVec3 temp;
340         int count = splinePoints.Num();
341         //for(int i = 2; i < count - 1; i++) {
342         for(int i = 1; i < count; i++) {
343                 temp = *splinePoints[i-1];
344                 temp -= *splinePoints[i];
345                 distSoFar += temp.Length();
346                 double percent = distSoFar / dist;
347                 percent *= totalTime;
348                 splineTime.Append(percent + bt);
349         }
350         assert(splineTime.Num() == splinePoints.Num());
351         activeSegment = 0;
352 }
353
354
355
356 float idSplineList::calcSpline(int step, float tension) {
357         switch(step) {
358                 case 0: return (pow(1 - tension, 3)) / 6;
359                 case 1: return (3 * pow(tension, 3) - 6 * pow(tension, 2) + 4) / 6;
360                 case 2: return (-3 * pow(tension, 3) + 3 * pow(tension, 2) + 3 * tension + 1) / 6;
361                 case 3: return pow(tension, 3) / 6;
362         }
363         return 0.0;
364 }
365
366
367
368 void idSplineList::updateSelection(const idVec3 &move) {
369         if (selected) {
370                 dirty = true;
371                 VectorAdd(*selected, move, *selected);
372         }
373 }
374
375
376 void idSplineList::setSelectedPoint(idVec3 *p) {
377         if (p) {
378                 p->Snap();
379                 for(int i = 0; i < controlPoints.Num(); i++) {
380                         if (*p == *controlPoints[i]) {
381                                 selected = controlPoints[i];
382                         }
383                 }
384         } else {
385                 selected = NULL;
386         }
387 }
388
389 const idVec3 *idSplineList::getPosition(long t) {
390         static idVec3 interpolatedPos;
391         static long lastTime = -1;
392
393         int count = splineTime.Num();
394         if (count == 0) {
395                 return &zero;
396         }
397
398 //      Com_Printf("Time: %d\n", t);
399         assert(splineTime.Num() == splinePoints.Num());
400
401         while (activeSegment < count) {
402                 if (splineTime[activeSegment] >= t) {
403                         if (activeSegment > 0 && activeSegment < count - 1) {
404                                 double timeHi = splineTime[activeSegment + 1];
405                                 double timeLo = splineTime[activeSegment - 1];
406                                 double percent = (timeHi - t) / (timeHi - timeLo); 
407                                 // pick two bounding points
408                                 idVec3 v1 = *splinePoints[activeSegment-1];
409                                 idVec3 v2 = *splinePoints[activeSegment+1];
410                                 v2 *= (1.0 - percent);
411                                 v1 *= percent;
412                                 v2 += v1;
413                                 interpolatedPos = v2;
414                                 return &interpolatedPos;
415                         }
416                         return splinePoints[activeSegment];
417                 } else {
418                         activeSegment++;
419                 }
420         }
421         return splinePoints[count-1];
422 }
423
424 void idSplineList::parse(const char *(*text)  ) {
425         const char *token;
426         //Com_MatchToken( text, "{" );
427         do {
428                 token = Com_Parse( text );
429         
430                 if ( !token[0] ) {
431                         break;
432                 }
433                 if ( !Q_stricmp (token, "}") ) {
434                         break;
435                 }
436
437                 do {
438                         // if token is not a brace, it is a key for a key/value pair
439                         if ( !token[0] || !Q_stricmp (token, "(") || !Q_stricmp(token, "}")) {
440                                 break;
441                         }
442
443                         Com_UngetToken();
444                         idStr key = Com_ParseOnLine(text);
445                         const char *token = Com_Parse(text);
446                         if (Q_stricmp(key.c_str(), "granularity") == 0) {
447                                 granularity = atof(token);
448                         } else if (Q_stricmp(key.c_str(), "name") == 0) {
449                                 name = token;
450                         }
451                         token = Com_Parse(text);
452
453                 } while (1);
454
455                 if ( !Q_stricmp (token, "}") ) {
456                         break;
457                 }
458
459                 Com_UngetToken();
460                 // read the control point
461                 idVec3 point;
462                 Com_Parse1DMatrix( text, 3, point );
463                 addPoint(point.x, point.y, point.z);
464         } while (1);
465  
466         //Com_UngetToken();
467         //Com_MatchToken( text, "}" );
468         dirty = true;
469 }
470
471 void idSplineList::write(fileHandle_t file, const char *p) {
472         idStr s = va("\t\t%s {\n", p);
473         FS_Write(s.c_str(), s.length(), file);
474         //s = va("\t\tname %s\n", name.c_str());
475         //FS_Write(s.c_str(), s.length(), file);
476         s = va("\t\t\tgranularity %f\n", granularity);
477         FS_Write(s.c_str(), s.length(), file);
478         int count = controlPoints.Num();
479         for (int i = 0; i < count; i++) {
480                 s = va("\t\t\t( %f %f %f )\n", controlPoints[i]->x, controlPoints[i]->y, controlPoints[i]->z);
481                 FS_Write(s.c_str(), s.length(), file);
482         }
483         s = "\t\t}\n";
484         FS_Write(s.c_str(), s.length(), file);
485 }
486
487
488 void idCameraDef::getActiveSegmentInfo(int segment, idVec3 &origin, idVec3 &direction, float *fov) {
489 #if 0
490         if (!cameraSpline.validTime()) {
491                 buildCamera();
492         }
493         double d = (double)segment / numSegments();
494         getCameraInfo(d * totalTime * 1000, origin, direction, fov);
495 #endif
496 /*
497         if (!cameraSpline.validTime()) {
498                 buildCamera();
499         }
500         origin = *cameraSpline.getSegmentPoint(segment);
501         
502
503         idVec3 temp;
504
505         int numTargets = getTargetSpline()->controlPoints.Num();
506         int count = cameraSpline.splineTime.Num();
507         if (numTargets == 0) {
508                 // follow the path
509                 if (cameraSpline.getActiveSegment() < count - 1) {
510                         temp = *cameraSpline.splinePoints[cameraSpline.getActiveSegment()+1];
511                 }
512         } else if (numTargets == 1) {
513                 temp = *getTargetSpline()->controlPoints[0];
514         } else {
515                 temp = *getTargetSpline()->getSegmentPoint(segment);
516         }
517
518         temp -= origin;
519         temp.Normalize();
520         direction = temp;
521 */
522 }
523
524 bool idCameraDef::getCameraInfo(long time, idVec3 &origin, idVec3 &direction, float *fv) {
525
526         char buff[1024];
527
528         if ((time - startTime) / 1000 > totalTime) {
529                 return false;
530         }
531
532
533         for (int i = 0; i < events.Num(); i++) {
534                 if (time >= startTime + events[i]->getTime() && !events[i]->getTriggered()) {
535                         events[i]->setTriggered(true);
536                         if (events[i]->getType() == idCameraEvent::EVENT_TARGET) {
537                                 setActiveTargetByName(events[i]->getParam());
538                                 getActiveTarget()->start(startTime + events[i]->getTime());
539                                 //Com_Printf("Triggered event switch to target: %s\n",events[i]->getParam());
540                         } else if (events[i]->getType() == idCameraEvent::EVENT_TRIGGER) {
541                                 //idEntity *ent = NULL;
542                                 //ent = level.FindTarget( ent, events[i]->getParam());
543                                 //if (ent) {
544                                 //      ent->signal( SIG_TRIGGER );
545                                 //      ent->ProcessEvent( &EV_Activate, world );
546                                 //}
547                         } else if (events[i]->getType() == idCameraEvent::EVENT_FOV) {
548                                 memset(buff, 0, sizeof(buff));
549                                 strcpy(buff, events[i]->getParam());
550                                 const char *param1 = strtok(buff, " \t,\0");
551                                 const char *param2 = strtok(NULL, " \t,\0");
552                                 float len = (param2) ? atof(param2) : 0;
553                                 float newfov = (param1) ? atof(param1) : 90;
554                                 fov.reset(fov.getFOV(time), newfov, time, len); 
555                                 //*fv = fov = atof(events[i]->getParam());
556                         } else if (events[i]->getType() == idCameraEvent::EVENT_FADEIN) {
557                                 float time = atof(events[i]->getParam());
558                                 Cbuf_AddText(va("fade 0 0 0 0 %f", time));
559                                 Cbuf_Execute();
560                         } else if (events[i]->getType() == idCameraEvent::EVENT_FADEOUT) {
561                                 float time = atof(events[i]->getParam());
562                                 Cbuf_AddText(va("fade 0 0 0 255 %f", time));
563                                 Cbuf_Execute();
564                         } else if (events[i]->getType() == idCameraEvent::EVENT_CAMERA) {
565                                 memset(buff, 0, sizeof(buff));
566                                 strcpy(buff, events[i]->getParam());
567                                 const char *param1 = strtok(buff, " \t,\0");
568                                 const char *param2 = strtok(NULL, " \t,\0");
569
570                                 if(param2) {
571                                         loadCamera(atoi(param1), va("cameras/%s.camera", param2));
572                                         startCamera(time);
573                                 } else {
574                                         loadCamera(0, va("cameras/%s.camera", events[i]->getParam()));
575                                         startCamera(time);
576                                 }
577                                 return true;
578                         } else if (events[i]->getType() == idCameraEvent::EVENT_STOP) {
579                                 return false;
580                         }
581                 }
582         }
583
584         origin = *cameraPosition->getPosition(time);
585         
586         *fv = fov.getFOV(time);
587
588         idVec3 temp = origin;
589
590         int numTargets = targetPositions.Num();
591         if (numTargets == 0) {
592 /*
593                 // follow the path
594                 if (cameraSpline.getActiveSegment() < count - 1) {
595                         temp = *cameraSpline.splinePoints[cameraSpline.getActiveSegment()+1];
596                         if (temp == origin) {
597                                 int index = cameraSpline.getActiveSegment() + 2;
598                                 while (temp == origin && index < count - 1) {
599                                         temp = *cameraSpline.splinePoints[index++];
600                                 }
601                         }
602                 }
603 */
604         } else {
605     if( getActiveTarget()->numPoints() > 0 ) {
606                   temp = *getActiveTarget()->getPosition(time);
607     }
608         }
609         
610         temp -= origin;
611         temp.Normalize();
612         direction = temp;
613
614         return true;
615 }
616
617 bool idCameraDef::waitEvent(int index) {
618         //for (int i = 0; i < events.Num(); i++) {
619         //      if (events[i]->getSegment() == index && events[i]->getType() == idCameraEvent::EVENT_WAIT) {
620         //              return true;
621         //      }
622     //}
623         return false;
624 }
625
626
627 #define NUM_CCELERATION_SEGS 10
628 #define CELL_AMT 5
629
630 void idCameraDef::buildCamera() {
631         int i;
632         int lastSwitch = 0;
633         idList<float> waits;
634         idList<int> targets;
635
636         totalTime = baseTime;
637         cameraPosition->setTime((long)totalTime * 1000);
638         // we have a base time layout for the path and the target path
639         // now we need to layer on any wait or speed changes
640         for (i = 0; i < events.Num(); i++) {
641                 idCameraEvent *ev = events[i];
642                 events[i]->setTriggered(false);
643                 switch (events[i]->getType()) {
644                         case idCameraEvent::EVENT_TARGET : {
645                                 targets.Append(i);
646                                 break;
647                         }
648                         case idCameraEvent::EVENT_FEATHER : {
649                                 long startTime = 0;
650                                 float speed = 0;
651                                 long loopTime = 10;
652                                 float stepGoal = cameraPosition->getBaseVelocity() / (1000 / loopTime);
653                                 while (startTime <= 1000) {
654                                         cameraPosition->addVelocity(startTime, loopTime, speed);
655                                         speed += stepGoal;
656                                         if (speed > cameraPosition->getBaseVelocity()) {
657                                                 speed = cameraPosition->getBaseVelocity();
658                                         }
659                                         startTime += loopTime;
660                                 }
661
662                                 startTime = (long)(totalTime * 1000 - 1000);
663                                 long endTime = startTime + 1000;
664                                 speed = cameraPosition->getBaseVelocity();
665                                 while (startTime < endTime) {
666                                         speed -= stepGoal;
667                                         if (speed < 0) {
668                                                 speed = 0;
669                                         }
670                                         cameraPosition->addVelocity(startTime, loopTime, speed);
671                                         startTime += loopTime;
672                                 }
673                                 break;
674
675                         }
676                         case idCameraEvent::EVENT_WAIT : {
677                                 waits.Append(atof(events[i]->getParam()));
678
679                                 //FIXME: this is quite hacky for Wolf E3, accel and decel needs
680                                 // do be parameter based etc.. 
681                                 long startTime = events[i]->getTime() - 1000;
682                                 if (startTime < 0) {
683                                         startTime = 0;
684                                 }
685                                 float speed = cameraPosition->getBaseVelocity();
686                                 long loopTime = 10;
687                                 float steps = speed / ((events[i]->getTime() - startTime) / loopTime);
688                                 while (startTime <= events[i]->getTime() - loopTime) {
689                                         cameraPosition->addVelocity(startTime, loopTime, speed);
690                                         speed -= steps;
691                                         startTime += loopTime;
692                                 }
693                                 cameraPosition->addVelocity(events[i]->getTime(), (long)atof(events[i]->getParam()) * 1000, 0);
694
695                                 startTime = (long)(events[i]->getTime() + atof(events[i]->getParam()) * 1000);
696                                 long endTime = startTime + 1000;
697                                 speed = 0;
698                                 while (startTime <= endTime) {
699                                         cameraPosition->addVelocity(startTime, loopTime, speed);
700                                         speed += steps;
701                                         startTime += loopTime;
702                                 }
703                                 break;
704                         }
705                         case idCameraEvent::EVENT_TARGETWAIT : {
706                                 //targetWaits.Append(i);
707                                 break;
708                         }
709                         case idCameraEvent::EVENT_SPEED : {
710 /*
711                                 // take the average delay between up to the next five segments
712                                 float adjust = atof(events[i]->getParam());
713                                 int index = events[i]->getSegment();
714                                 total = 0;
715                                 count = 0;
716
717                                 // get total amount of time over the remainder of the segment
718                                 for (j = index; j < cameraSpline.numSegments() - 1; j++) {
719                                         total += cameraSpline.getSegmentTime(j + 1) - cameraSpline.getSegmentTime(j);
720                                         count++;
721                                 }
722
723                                 // multiply that by the adjustment
724                                 double newTotal = total * adjust;
725                                 // what is the difference.. 
726                                 newTotal -= total;
727                                 totalTime += newTotal / 1000;
728
729                                 // per segment difference
730                                 newTotal /= count;
731                                 int additive = newTotal;
732
733                                 // now propogate that difference out to each segment
734                                 for (j = index; j < cameraSpline.numSegments(); j++) {
735                                         cameraSpline.addSegmentTime(j, additive);
736                                         additive += newTotal;
737                                 }
738                                 break;
739 */
740                         }
741                 }
742         }
743
744
745         for (i = 0; i < waits.Num(); i++) {
746                 totalTime += waits[i];
747         }
748
749         // on a new target switch, we need to take time to this point ( since last target switch ) 
750         // and allocate it across the active target, then reset time to this point
751         long timeSoFar = 0;
752         long total = (long)(totalTime * 1000);
753         for (i = 0; i < targets.Num(); i++) {
754                 long t;
755                 if (i < targets.Num() - 1) {
756                         t = events[targets[i+1]]->getTime();
757                 } else {
758                         t = total - timeSoFar;
759                 }
760                 // t is how much time to use for this target
761                 setActiveTargetByName(events[targets[i]]->getParam());
762                 getActiveTarget()->setTime(t);
763                 timeSoFar += t;
764         }
765
766         
767 }
768
769 void idCameraDef::startCamera(long t) {
770         cameraPosition->clearVelocities();
771         cameraPosition->start(t);
772         buildCamera();
773         fov.reset(90, 90, t, 0);
774         //for (int i = 0; i < targetPositions.Num(); i++) {
775         //      targetPositions[i]->
776         //}
777         startTime = t;
778         cameraRunning = true;
779 }
780
781
782 void idCameraDef::parse(const char *(*text)  ) {
783
784         const char      *token;
785         do {
786                 token = Com_Parse( text );
787         
788                 if ( !token[0] ) {
789                         break;
790                 }
791                 if ( !Q_stricmp (token, "}") ) {
792                         break;
793                 }
794
795                 if (Q_stricmp(token, "time") == 0) {
796                         baseTime = Com_ParseFloat(text);
797                 }
798                 else if (Q_stricmp(token, "camera_fixed") == 0) {
799                         cameraPosition = new idFixedPosition();
800                         cameraPosition->parse(text);
801                 }
802                 else if (Q_stricmp(token, "camera_interpolated") == 0) {
803                         cameraPosition = new idInterpolatedPosition();
804                         cameraPosition->parse(text);
805                 }
806                 else if (Q_stricmp(token, "camera_spline") == 0) {
807                         cameraPosition = new idSplinePosition();
808                         cameraPosition->parse(text);
809                 }
810                 else if (Q_stricmp(token, "target_fixed") == 0) {
811                         idFixedPosition *pos = new idFixedPosition();
812                         pos->parse(text);
813                         targetPositions.Append(pos);
814                 }
815                 else if (Q_stricmp(token, "target_interpolated") == 0) {
816                         idInterpolatedPosition *pos = new idInterpolatedPosition();
817                         pos->parse(text);
818                         targetPositions.Append(pos);
819                 }
820                 else if (Q_stricmp(token, "target_spline") == 0) {
821                         idSplinePosition *pos = new idSplinePosition();
822                         pos->parse(text);
823                         targetPositions.Append(pos);
824                 }
825                 else if (Q_stricmp(token, "fov") == 0) {
826                         fov.parse(text);
827                 }
828                 else if (Q_stricmp(token, "event") == 0) {
829                         idCameraEvent *event = new idCameraEvent();
830                         event->parse(text);
831                         addEvent(event);
832                 }
833
834
835         } while (1);
836
837         if ( !cameraPosition ) {
838                 Com_Printf( "no camera position specified\n" );
839                 // prevent a crash later on
840                 cameraPosition = new idFixedPosition();
841         }
842
843         Com_UngetToken();
844         Com_MatchToken( text, "}" );
845
846 }
847
848 bool idCameraDef::load(const char *filename) {
849         char *buf;
850         const char *buf_p;
851         int length = FS_ReadFile( filename, (void **)&buf );
852         if ( !buf ) {
853                 return false;
854         }
855
856         clear();
857         Com_BeginParseSession( filename );
858         buf_p = buf;
859         parse(&buf_p);
860         Com_EndParseSession();
861         FS_FreeFile( buf );
862
863         return true;
864 }
865
866 void idCameraDef::save(const char *filename) {
867         fileHandle_t file = FS_FOpenFileWrite(filename);
868         if (file) {
869                 int i;
870                 idStr s = "cameraPathDef { \n"; 
871                 FS_Write(s.c_str(), s.length(), file);
872                 s = va("\ttime %f\n", baseTime);
873                 FS_Write(s.c_str(), s.length(), file);
874
875                 cameraPosition->write(file, va("camera_%s",cameraPosition->typeStr()));
876
877                 for (i = 0; i < numTargets(); i++) {
878                         targetPositions[i]->write(file, va("target_%s", targetPositions[i]->typeStr()));
879                 }
880
881                 for (i = 0; i < events.Num(); i++) {
882                         events[i]->write(file, "event");
883                 }
884
885                 fov.write(file, "fov");
886
887                 s = "}\n";
888                 FS_Write(s.c_str(), s.length(), file);
889         }
890         FS_FCloseFile(file);
891 }
892
893 int idCameraDef::sortEvents(const void *p1, const void *p2) {
894         idCameraEvent *ev1 = (idCameraEvent*)(p1);
895         idCameraEvent *ev2 = (idCameraEvent*)(p2);
896
897         if (ev1->getTime() > ev2->getTime()) {
898                 return -1;
899         }
900         if (ev1->getTime() < ev2->getTime()) {
901                 return 1;
902         }
903         return 0; 
904 }
905
906 void idCameraDef::addEvent(idCameraEvent *event) {
907         events.Append(event);
908         //events.Sort(&sortEvents);
909
910 }
911 void idCameraDef::addEvent(idCameraEvent::eventType t, const char *param, long time) {
912         addEvent(new idCameraEvent(t, param, time));
913         buildCamera();
914 }
915
916 void idCameraDef::removeEvent(int index) {
917         events.RemoveIndex(index);
918         buildCamera();
919 }
920
921
922 const char *idCameraEvent::eventStr[] = {
923         "NA",
924         "WAIT",
925         "TARGETWAIT",
926         "SPEED",
927         "TARGET",
928         "SNAPTARGET",
929         "FOV",
930         "CMD",
931         "TRIGGER",
932         "STOP",
933         "CAMERA",
934         "FADEOUT",
935         "FADEIN",
936         "FEATHER"
937 };
938
939 void idCameraEvent::parse(const char *(*text)  ) {
940         const char *token;
941         Com_MatchToken( text, "{" );
942         do {
943                 token = Com_Parse( text );
944         
945                 if ( !token[0] ) {
946                         break;
947                 }
948                 if ( !strcmp (token, "}") ) {
949                         break;
950                 }
951
952                 // here we may have to jump over brush epairs ( only used in editor )
953                 do {
954                         // if token is not a brace, it is a key for a key/value pair
955                         if ( !token[0] || !strcmp (token, "(") || !strcmp(token, "}")) {
956                                 break;
957                         }
958
959                         Com_UngetToken();
960                         idStr key = Com_ParseOnLine(text);
961                         const char *token = Com_Parse(text);
962                         if (Q_stricmp(key.c_str(), "type") == 0) {
963                                 type = static_cast<idCameraEvent::eventType>(atoi(token));
964                         } else if (Q_stricmp(key.c_str(), "param") == 0) {
965                                 paramStr = token;
966                         } else if (Q_stricmp(key.c_str(), "time") == 0) {
967                                 time = atoi(token);
968                         }
969                         token = Com_Parse(text);
970
971                 } while (1);
972
973                 if ( !strcmp (token, "}") ) {
974                         break;
975                 }
976
977         } while (1);
978  
979         Com_UngetToken();
980         Com_MatchToken( text, "}" );
981 }
982
983 void idCameraEvent::write(fileHandle_t file, const char *name) {
984         idStr s = va("\t%s {\n", name);
985         FS_Write(s.c_str(), s.length(), file);
986         s = va("\t\ttype %d\n", static_cast<int>(type));
987         FS_Write(s.c_str(), s.length(), file);
988         s = va("\t\tparam \"%s\"\n", paramStr.c_str());
989         FS_Write(s.c_str(), s.length(), file);
990         s = va("\t\ttime %d\n", time);
991         FS_Write(s.c_str(), s.length(), file);
992         s = "\t}\n";
993         FS_Write(s.c_str(), s.length(), file);
994 }
995
996
997 const char *idCameraPosition::positionStr[] = {
998         "Fixed",
999         "Interpolated",
1000         "Spline",
1001 };
1002
1003
1004
1005 const idVec3 *idInterpolatedPosition::getPosition(long t) { 
1006         static idVec3 interpolatedPos;
1007
1008         float velocity = getVelocity(t);
1009         float timePassed = t - lastTime;
1010         lastTime = t;
1011
1012         // convert to seconds   
1013         timePassed /= 1000;
1014
1015         float distToTravel = timePassed * velocity;
1016
1017         idVec3 temp = startPos;
1018         temp -= endPos;
1019         float distance = temp.Length();
1020
1021         distSoFar += distToTravel;
1022         float percent = (float)(distSoFar) / distance;
1023
1024         if (percent > 1.0) {
1025                 percent = 1.0;
1026         } else if (percent < 0.0) {
1027                 percent = 0.0;
1028         }
1029
1030         // the following line does a straigt calc on percentage of time
1031         // float percent = (float)(startTime + time - t) / time;
1032
1033         idVec3 v1 = startPos;
1034         idVec3 v2 = endPos;
1035         v1 *= (1.0 - percent);
1036         v2 *= percent;
1037         v1 += v2;
1038         interpolatedPos = v1;
1039         return &interpolatedPos;
1040 }
1041
1042
1043 void idCameraFOV::parse(const char *(*text)  ) {
1044         const char *token;
1045         Com_MatchToken( text, "{" );
1046         do {
1047                 token = Com_Parse( text );
1048         
1049                 if ( !token[0] ) {
1050                         break;
1051                 }
1052                 if ( !strcmp (token, "}") ) {
1053                         break;
1054                 }
1055
1056                 // here we may have to jump over brush epairs ( only used in editor )
1057                 do {
1058                         // if token is not a brace, it is a key for a key/value pair
1059                         if ( !token[0] || !strcmp (token, "(") || !strcmp(token, "}")) {
1060                                 break;
1061                         }
1062
1063                         Com_UngetToken();
1064                         idStr key = Com_ParseOnLine(text);
1065                         const char *token = Com_Parse(text);
1066                         if (Q_stricmp(key.c_str(), "fov") == 0) {
1067                                 fov = atof(token);
1068                         } else if (Q_stricmp(key.c_str(), "startFOV") == 0) {
1069                                 startFOV = atof(token);
1070                         } else if (Q_stricmp(key.c_str(), "endFOV") == 0) {
1071                                 endFOV = atof(token);
1072                         } else if (Q_stricmp(key.c_str(), "time") == 0) {
1073                                 time = atoi(token);
1074                         }
1075                         token = Com_Parse(text);
1076
1077                 } while (1);
1078
1079                 if ( !strcmp (token, "}") ) {
1080                         break;
1081                 }
1082
1083         } while (1);
1084  
1085         Com_UngetToken();
1086         Com_MatchToken( text, "}" );
1087 }
1088
1089 bool idCameraPosition::parseToken(const char *key, const char *(*text)) {
1090         const char *token = Com_Parse(text);
1091         if (Q_stricmp(key, "time") == 0) {
1092                 time = atol(token);
1093                 return true;
1094         } else if (Q_stricmp(key, "type") == 0) {
1095                 type = static_cast<idCameraPosition::positionType>(atoi(token));
1096                 return true;
1097         } else if (Q_stricmp(key, "velocity") == 0) {
1098                 long t = atol(token);
1099                 token = Com_Parse(text);
1100                 long d = atol(token);
1101                 token = Com_Parse(text);
1102                 float s = atof(token);
1103                 addVelocity(t, d, s);
1104                 return true;
1105         } else if (Q_stricmp(key, "baseVelocity") == 0) {
1106                 baseVelocity = atof(token);
1107                 return true;
1108         } else if (Q_stricmp(key, "name") == 0) {
1109                 name = token;
1110                 return true;
1111         } else if (Q_stricmp(key, "time") == 0) {
1112                 time = atoi(token);
1113                 return true;
1114         }
1115         Com_UngetToken();
1116         return false;
1117 }
1118
1119
1120
1121 void idFixedPosition::parse(const char *(*text)  ) {
1122         const char *token;
1123         Com_MatchToken( text, "{" );
1124         do {
1125                 token = Com_Parse( text );
1126         
1127                 if ( !token[0] ) {
1128                         break;
1129                 }
1130                 if ( !strcmp (token, "}") ) {
1131                         break;
1132                 }
1133
1134                 // here we may have to jump over brush epairs ( only used in editor )
1135                 do {
1136                         // if token is not a brace, it is a key for a key/value pair
1137                         if ( !token[0] || !strcmp (token, "(") || !strcmp(token, "}")) {
1138                                 break;
1139                         }
1140
1141                         Com_UngetToken();
1142                         idStr key = Com_ParseOnLine(text);
1143                         
1144                         const char *token = Com_Parse(text);
1145                         if (Q_stricmp(key.c_str(), "pos") == 0) {
1146                                 Com_UngetToken();
1147                                 Com_Parse1DMatrix( text, 3, pos );
1148                         } else {
1149                                 Com_UngetToken();
1150                                 idCameraPosition::parseToken(key.c_str(), text);        
1151                         }
1152                         token = Com_Parse(text);
1153
1154                 } while (1);
1155
1156                 if ( !strcmp (token, "}") ) {
1157                         break;
1158                 }
1159
1160         } while (1);
1161  
1162         Com_UngetToken();
1163         Com_MatchToken( text, "}" );
1164 }
1165
1166 void idInterpolatedPosition::parse(const char *(*text)  ) {
1167         const char *token;
1168         Com_MatchToken( text, "{" );
1169         do {
1170                 token = Com_Parse( text );
1171         
1172                 if ( !token[0] ) {
1173                         break;
1174                 }
1175                 if ( !strcmp (token, "}") ) {
1176                         break;
1177                 }
1178
1179                 // here we may have to jump over brush epairs ( only used in editor )
1180                 do {
1181                         // if token is not a brace, it is a key for a key/value pair
1182                         if ( !token[0] || !strcmp (token, "(") || !strcmp(token, "}")) {
1183                                 break;
1184                         }
1185
1186                         Com_UngetToken();
1187                         idStr key = Com_ParseOnLine(text);
1188                         
1189                         const char *token = Com_Parse(text);
1190                         if (Q_stricmp(key.c_str(), "startPos") == 0) {
1191                                 Com_UngetToken();
1192                                 Com_Parse1DMatrix( text, 3, startPos );
1193                         } else if (Q_stricmp(key.c_str(), "endPos") == 0) {
1194                                 Com_UngetToken();
1195                                 Com_Parse1DMatrix( text, 3, endPos );
1196                         } else {
1197                                 Com_UngetToken();
1198                                 idCameraPosition::parseToken(key.c_str(), text);        
1199                         }
1200                         token = Com_Parse(text);
1201
1202                 } while (1);
1203
1204                 if ( !strcmp (token, "}") ) {
1205                         break;
1206                 }
1207
1208         } while (1);
1209  
1210         Com_UngetToken();
1211         Com_MatchToken( text, "}" );
1212 }
1213
1214
1215 void idSplinePosition::parse(const char *(*text)  ) {
1216         const char *token;
1217         Com_MatchToken( text, "{" );
1218         do {
1219                 token = Com_Parse( text );
1220         
1221                 if ( !token[0] ) {
1222                         break;
1223                 }
1224                 if ( !strcmp (token, "}") ) {
1225                         break;
1226                 }
1227
1228                 // here we may have to jump over brush epairs ( only used in editor )
1229                 do {
1230                         // if token is not a brace, it is a key for a key/value pair
1231                         if ( !token[0] || !strcmp (token, "(") || !strcmp(token, "}")) {
1232                                 break;
1233                         }
1234
1235                         Com_UngetToken();
1236                         idStr key = Com_ParseOnLine(text);
1237                         
1238                         const char *token = Com_Parse(text);
1239                         if (Q_stricmp(key.c_str(), "target") == 0) {
1240                                 target.parse(text);
1241                         } else {
1242                                 Com_UngetToken();
1243                                 idCameraPosition::parseToken(key.c_str(), text);        
1244                         }
1245                         token = Com_Parse(text);
1246
1247                 } while (1);
1248
1249                 if ( !strcmp (token, "}") ) {
1250                         break;
1251                 }
1252
1253         } while (1);
1254  
1255         Com_UngetToken();
1256         Com_MatchToken( text, "}" );
1257 }
1258
1259
1260
1261 void idCameraFOV::write(fileHandle_t file, const char *p) {
1262         idStr s = va("\t%s {\n", p);
1263         FS_Write(s.c_str(), s.length(), file);
1264         
1265         s = va("\t\tfov %f\n", fov);
1266         FS_Write(s.c_str(), s.length(), file);
1267
1268         s = va("\t\tstartFOV %f\n", startFOV);
1269         FS_Write(s.c_str(), s.length(), file);
1270
1271         s = va("\t\tendFOV %f\n", endFOV);
1272         FS_Write(s.c_str(), s.length(), file);
1273
1274         s = va("\t\ttime %i\n", time);
1275         FS_Write(s.c_str(), s.length(), file);
1276
1277         s = "\t}\n";
1278         FS_Write(s.c_str(), s.length(), file);
1279 }
1280
1281
1282 void idCameraPosition::write(fileHandle_t file, const char *p) {
1283         
1284         idStr s = va("\t\ttime %i\n", time);
1285         FS_Write(s.c_str(), s.length(), file);
1286
1287         s = va("\t\ttype %i\n", static_cast<int>(type));
1288         FS_Write(s.c_str(), s.length(), file);
1289
1290         s = va("\t\tname %s\n", name.c_str());
1291         FS_Write(s.c_str(), s.length(), file);
1292
1293         s = va("\t\tbaseVelocity %f\n", baseVelocity);
1294         FS_Write(s.c_str(), s.length(), file);
1295
1296         for (int i = 0; i < velocities.Num(); i++) {
1297                 s = va("\t\tvelocity %i %i %f\n", velocities[i]->startTime, velocities[i]->time, velocities[i]->speed);
1298                 FS_Write(s.c_str(), s.length(), file);
1299         }
1300
1301 }
1302
1303 void idFixedPosition::write(fileHandle_t file, const char *p) {
1304         idStr s = va("\t%s {\n", p);
1305         FS_Write(s.c_str(), s.length(), file);
1306         idCameraPosition::write(file, p);
1307         s = va("\t\tpos ( %f %f %f )\n", pos.x, pos.y, pos.z);
1308         FS_Write(s.c_str(), s.length(), file);
1309         s = "\t}\n";
1310         FS_Write(s.c_str(), s.length(), file);
1311 }
1312
1313 void idInterpolatedPosition::write(fileHandle_t file, const char *p) {
1314         idStr s = va("\t%s {\n", p);
1315         FS_Write(s.c_str(), s.length(), file);
1316         idCameraPosition::write(file, p);
1317         s = va("\t\tstartPos ( %f %f %f )\n", startPos.x, startPos.y, startPos.z);
1318         FS_Write(s.c_str(), s.length(), file);
1319         s = va("\t\tendPos ( %f %f %f )\n", endPos.x, endPos.y, endPos.z);
1320         FS_Write(s.c_str(), s.length(), file);
1321         s = "\t}\n";
1322         FS_Write(s.c_str(), s.length(), file);
1323 }
1324
1325 void idSplinePosition::write(fileHandle_t file, const char *p) {
1326         idStr s = va("\t%s {\n", p);
1327         FS_Write(s.c_str(), s.length(), file);
1328         idCameraPosition::write(file, p);
1329         target.write(file, "target");
1330         s = "\t}\n";
1331         FS_Write(s.c_str(), s.length(), file);
1332 }
1333
1334 void idCameraDef::addTarget(const char *name, idCameraPosition::positionType type) {
1335         const char *text = (name == NULL) ? va("target0%d", numTargets()+1) : name;
1336         idCameraPosition *pos = newFromType(type);
1337         if (pos) {
1338                 pos->setName(name);
1339                 targetPositions.Append(pos);
1340                 activeTarget = numTargets()-1;
1341                 if (activeTarget == 0) {
1342                         // first one
1343                         addEvent(idCameraEvent::EVENT_TARGET, name, 0);
1344                 }
1345         }
1346 }
1347
1348 const idVec3 *idSplinePosition::getPosition(long t) {
1349         static idVec3 interpolatedPos;
1350
1351         float velocity = getVelocity(t);
1352         float timePassed = t - lastTime;
1353         lastTime = t;
1354
1355         // convert to seconds   
1356         timePassed /= 1000;
1357
1358         float distToTravel = timePassed * velocity;
1359
1360         distSoFar += distToTravel;
1361         double tempDistance = target.totalDistance();
1362
1363         double percent = (double)(distSoFar) / tempDistance;
1364
1365         double targetDistance = percent * tempDistance;
1366         tempDistance = 0;
1367
1368         double lastDistance1,lastDistance2;
1369         lastDistance1 = lastDistance2 = 0;
1370         idVec3 temp;
1371         int count = target.numSegments();
1372         int i;
1373         for(i = 1; i < count; i++) {
1374                 temp = *target.getSegmentPoint(i-1);
1375                 temp -= *target.getSegmentPoint(i);
1376                 tempDistance += temp.Length();
1377                 if (i & 1) {
1378                         lastDistance1 = tempDistance;
1379                 } else {
1380                         lastDistance2 = tempDistance;
1381                 }
1382                 if (tempDistance >= targetDistance) {
1383                         break;
1384                 }
1385         }
1386
1387         if ( i >= count - 1) {
1388                 interpolatedPos = *target.getSegmentPoint(i-1);
1389         } else {
1390 #if 0
1391                 double timeHi = target.getSegmentTime(i + 1);
1392                 double timeLo = target.getSegmentTime(i - 1);
1393                 double percent = (timeHi - t) / (timeHi - timeLo); 
1394                 idVec3 v1 = *target.getSegmentPoint(i - 1);
1395                 idVec3 v2 = *target.getSegmentPoint(i + 1);
1396                 v2 *= (1.0 - percent);
1397                 v1 *= percent;
1398                 v2 += v1;
1399                 interpolatedPos = v2;
1400 #else
1401                 if (lastDistance1 > lastDistance2) {
1402                         double d = lastDistance2;
1403                         lastDistance2 = lastDistance1;
1404                         lastDistance1 = d;
1405                 }
1406
1407                 idVec3 v1 = *target.getSegmentPoint(i - 1);
1408                 idVec3 v2 = *target.getSegmentPoint(i);
1409                 double percent = (lastDistance2 - targetDistance) / (lastDistance2 - lastDistance1); 
1410                 v2 *= (1.0 - percent);
1411                 v1 *= percent;
1412                 v2 += v1;
1413                 interpolatedPos = v2;
1414 #endif
1415         }
1416         return &interpolatedPos;
1417
1418 }
1419
1420
1421