cp_map.c

Go to the documentation of this file.
00001 
00006 /*
00007 Copyright (C) 2002-2010 UFO: Alien Invasion.
00008 
00009 This program is free software; you can redistribute it and/or
00010 modify it under the terms of the GNU General Public License
00011 as published by the Free Software Foundation; either version 2
00012 of the License, or (at your option) any later version.
00013 
00014 This program is distributed in the hope that it will be useful,
00015 but WITHOUT ANY WARRANTY; without even the implied warranty of
00016 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
00017 
00018 See the GNU General Public License for more details.
00019 
00020 You should have received a copy of the GNU General Public License
00021 along with this program; if not, write to the Free Software
00022 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
00023 
00024 */
00025 
00026 #include "../client.h" /* cls */
00027 #include "../renderer/r_image.h"
00028 #include "../renderer/r_framebuffer.h"
00029 #include "../renderer/r_draw.h"
00030 #include "../renderer/r_geoscape.h"
00031 #include "../ui/ui_main.h"
00032 #include "../ui/ui_font.h" /* UI_GetFontFromNode */
00033 #include "../ui/ui_render.h" /* UI_DrawString */
00034 #include "../ui/node/ui_node_abstractnode.h" /* UI_GetNodeAbsPos */
00035 #include "../ui/node/ui_node_map.h" /* paddingRight */
00036 #include "cp_overlay.h"
00037 #include "cp_campaign.h"
00038 #include "cp_popup.h"
00039 #include "cp_mapfightequip.h"
00040 #include "cp_map.h"
00041 #include "cp_missions.h"
00042 #include "cp_ufo.h"
00043 #include "cp_time.h"
00044 #include "cp_xvi.h"
00045 
00046 cvar_t *cl_3dmap;               
00047 static cvar_t *cl_3dmapAmbient;
00048 cvar_t *cl_mapzoommax;
00049 cvar_t *cl_mapzoommin;
00050 cvar_t *cl_geoscape_overlay;
00051 
00052 extern image_t *r_dayandnightTexture;
00053 extern image_t *r_radarTexture;
00054 extern image_t *r_xviTexture;
00055 
00056 #ifdef DEBUG
00057 static cvar_t *debug_showInterest;
00058 #endif
00059 
00060 #define ZOOM_LIMIT  2.5f
00061 
00062 enum {
00063     GEOSCAPE_IMAGE_MISSION,
00064     GEOSCAPE_IMAGE_MISSION_SELECTED,
00065     GEOSCAPE_IMAGE_MISSION_ACTIVE,
00066     GEOSCAPE_IMAGE_BASE,
00067     GEOSCAPE_IMAGE_BASE_ATTACK,
00068 
00069     GEOSCAPE_IMAGE_MAX
00070 };
00071 
00072 static const char *geoscapeImageNames[] = {
00073     "pics/geoscape/mission",
00074     "pics/geoscape/circle",
00075     "pics/geoscape/circleactive",
00076     "pics/geoscape/base",
00077     "pics/geoscape/baseattack"
00078 };
00079 CASSERT(lengthof(geoscapeImageNames) == GEOSCAPE_IMAGE_MAX);
00080 
00081 static image_t *geoscapeImages[GEOSCAPE_IMAGE_MAX];
00082 
00083 /*
00084 ==============================================================
00085 MULTI SELECTION DEFINITION
00086 ==============================================================
00087 */
00088 
00089 #define MULTISELECT_MAXSELECT 6 
00094 typedef enum {
00095     MULTISELECT_TYPE_BASE,
00096     MULTISELECT_TYPE_INSTALLATION,
00097     MULTISELECT_TYPE_MISSION,
00098     MULTISELECT_TYPE_AIRCRAFT,
00099     MULTISELECT_TYPE_UFO,
00100     MULTISELECT_TYPE_NONE
00101 } multiSelectType_t;
00102 
00103 
00107 typedef struct multiSelect_s {
00108     int nbSelect;                       
00109     multiSelectType_t selectType[MULTISELECT_MAXSELECT];    
00110     int selectId[MULTISELECT_MAXSELECT];    
00111     char popupText[2048];               
00112 } multiSelect_t;
00113 
00114 static multiSelect_t multiSelect;   
00117 /*
00118 ==============================================================
00119 STATIC DEFINITION
00120 ==============================================================
00121 */
00122 
00123 /* Functions */
00124 static qboolean MAP_IsMapPositionSelected(const uiNode_t* node, const vec2_t pos, int x, int y);
00125 static void MAP3D_ScreenToMap(const uiNode_t* node, int x, int y, vec2_t pos);
00126 static void MAP_ScreenToMap(const uiNode_t* node, int x, int y, vec2_t pos);
00127 
00128 /* static variables */
00129 static char textStandard[2048];     
00130 static int centerOnEventIdx;        
00132 /* Colors */
00133 static const vec4_t green = {0.0f, 1.0f, 0.0f, 0.8f};
00134 static const vec4_t yellow = {1.0f, 0.874f, 0.294f, 1.0f};
00135 static const vec4_t red = {1.0f, 0.0f, 0.0f, 0.8f};
00136 
00137 /* Smoothing variables */
00138 static qboolean smoothRotation = qfalse;    
00139 static vec3_t smoothFinalGlobeAngle = {0, GLOBE_ROTATE, 0}; 
00140 static vec2_t smoothFinal2DGeoscapeCenter = {0.5, 0.5};     
00141 static float smoothDeltaLength = 0.0f;      
00142 static float smoothFinalZoom = 0.0f;        
00143 static float smoothDeltaZoom = 0.0f;        
00144 static const float smoothAcceleration = 0.06f;      
00145 static float curZoomSpeed = 0.0f;           
00146 static float curRotationSpeed = 0.0f;       
00149 static const float defaultBaseAngle = 90.0f;    
00151 static byte *terrainPic;                
00153 static int terrainWidth, terrainHeight;     
00155 static byte *culturePic;                
00157 static int cultureWidth, cultureHeight;     
00159 static byte *populationPic;             
00161 static int populationWidth, populationHeight;       
00163 static byte *nationsPic;                
00165 static int nationsWidth, nationsHeight; 
00168 /*
00169 ==============================================================
00170 CLICK ON MAP and MULTI SELECTION FUNCTIONS
00171 ==============================================================
00172 */
00173 
00177 static void MAP_MultiSelectListAddItem (multiSelectType_t itemType, int itemID,
00178     const char* itemDescription, const char* itemName)
00179 {
00180     Q_strcat(multiSelect.popupText, va("%s\t%s\n", itemDescription, itemName), sizeof(multiSelect.popupText));
00181     multiSelect.selectType[multiSelect.nbSelect] = itemType;
00182     multiSelect.selectId[multiSelect.nbSelect] = itemID;
00183     multiSelect.nbSelect++;
00184 }
00185 
00190 static void MAP_MultiSelectExecuteAction_f (void)
00191 {
00192     int selected, id;
00193     aircraft_t* aircraft;
00194     qboolean multiSelection = qfalse;
00195 
00196     if (Cmd_Argc() < 2) {
00197         /* Direct call from code, not from a popup menu */
00198         selected = 0;
00199     } else {
00200         /* Call from a geoscape popup menu (popup_multi_selection) */
00201         UI_PopWindow(qfalse);
00202         selected = atoi(Cmd_Argv(1));
00203         multiSelection = qtrue;
00204     }
00205 
00206     if (selected < 0 || selected >= multiSelect.nbSelect)
00207         return;
00208     id = multiSelect.selectId[selected];
00209 
00210     /* Execute action on element */
00211     switch (multiSelect.selectType[selected]) {
00212     case MULTISELECT_TYPE_BASE: /* Select a base */
00213         if (id >= ccs.numBases)
00214             break;
00215         MAP_ResetAction();
00216         B_SelectBase(B_GetFoundedBaseByIDX(id));
00217         break;
00218     case MULTISELECT_TYPE_INSTALLATION: /* Select a installation */
00219         if (id >= ccs.numInstallations)
00220             break;
00221         MAP_ResetAction();
00222         INS_SelectInstallation(INS_GetFoundedInstallationByIDX(id));
00223         break;
00224     case MULTISELECT_TYPE_MISSION: /* Select a mission */
00225         if (ccs.mapAction == MA_INTERCEPT && ccs.selectedMission && ccs.selectedMission == MAP_GetMissionByIDX(id)) {
00226             CL_DisplayPopupInterceptMission(ccs.selectedMission);
00227             return;
00228         }
00229         MAP_SelectMission(MAP_GetMissionByIDX(id));
00230         if (multiSelection) {
00231             /* if we come from a multiSelection menu, no need to open twice this popup to go to mission */
00232             CL_DisplayPopupInterceptMission(ccs.selectedMission);
00233             return;
00234         }
00235         break;
00236     case MULTISELECT_TYPE_AIRCRAFT: /* Selection of an aircraft */
00237         aircraft = AIR_AircraftGetFromIDX(id);
00238         if (aircraft == NULL) {
00239             Com_DPrintf(DEBUG_CLIENT, "MAP_MultiSelectExecuteAction: selection of an unknown aircraft idx %i\n", id);
00240             return;
00241         }
00242 
00243         if (aircraft == ccs.selectedAircraft) {
00244             /* Selection of an already selected aircraft */
00245             CL_DisplayPopupAircraft(aircraft);  /* Display popup_aircraft */
00246         } else {
00247             /* Selection of an unselected aircraft */
00248             MAP_SelectAircraft(aircraft);
00249             if (multiSelection)
00250                 /* if we come from a multiSelection menu, no need to open twice this popup to choose an action */
00251                 CL_DisplayPopupAircraft(aircraft);
00252         }
00253         break;
00254     case MULTISELECT_TYPE_UFO : /* Selection of a UFO */
00255         /* Get the ufo selected */
00256         if (id < 0 || id >= ccs.numUFOs)
00257             return;
00258         aircraft = ccs.ufos + id;
00259 
00260         if (aircraft == ccs.selectedUFO) {
00261             /* Selection of an already selected ufo */
00262             CL_DisplayPopupInterceptUFO(ccs.selectedUFO);
00263         } else {
00264             /* Selection of an unselected ufo */
00265             MAP_SelectUFO(aircraft);
00266             if (multiSelection)
00267                 /* if we come from a multiSelection menu, no need to open twice this popup to choose an action */
00268                 CL_DisplayPopupInterceptUFO(ccs.selectedUFO);
00269         }
00270         break;
00271     case MULTISELECT_TYPE_NONE :    /* Selection of an element that has been removed */
00272         break;
00273     default:
00274         Com_DPrintf(DEBUG_CLIENT, "MAP_MultiSelectExecuteAction: selection of an unknown element type %i\n",
00275                 multiSelect.selectType[selected]);
00276     }
00277 }
00278 
00282 void MAP_MapClick (uiNode_t* node, int x, int y)
00283 {
00284     aircraft_t *aircraft;
00285     int i;
00286     vec2_t pos;
00287     const linkedList_t *list;
00288 
00289     /* get map position */
00290     if (cl_3dmap->integer)
00291         MAP3D_ScreenToMap(node, x, y, pos);
00292     else
00293         MAP_ScreenToMap(node, x, y, pos);
00294 
00295     /* new base construction */
00296     switch (ccs.mapAction) {
00297     case MA_NEWBASE:
00299         if (!MapIsWater(MAP_GetColor(pos, MAPTYPE_TERRAIN))) {
00300             Vector2Copy(pos, newBasePos);
00301 
00302             CL_GameTimeStop();
00303 
00304             if (ccs.numBases < MAX_BASES) {
00305                 Cmd_ExecuteString("mn_set_base_title");
00306                 UI_PushWindow("popup_newbase", NULL);
00307             }
00308             return;
00309         }
00310         break;
00311     case MA_NEWINSTALLATION:
00312         if (!MapIsWater(MAP_GetColor(pos, MAPTYPE_TERRAIN))) {
00313             Vector2Copy(pos, newBasePos);
00314 
00315             if (ccs.numInstallations < MAX_INSTALLATIONS) {
00316                 CL_GameTimeStop();
00317                 UI_PushWindow("popup_newinstallation", NULL);
00318             }
00319             return;
00320         }
00321         break;
00322     case MA_UFORADAR:
00323         UI_PushWindow("popup_intercept_ufo", NULL);
00324         break;
00325     default:
00326         break;
00327     }
00328 
00329     /* Init data for multi selection */
00330     multiSelect.nbSelect = 0;
00331     memset(multiSelect.popupText, 0, sizeof(multiSelect.popupText));
00332 
00333     /* Get selected missions */
00334     for (list = ccs.missions; list; list = list->next) {
00335         const mission_t *tempMission = (mission_t *)list->data;
00336         if (multiSelect.nbSelect >= MULTISELECT_MAXSELECT)
00337             break;
00338         if (tempMission->stage == STAGE_NOT_ACTIVE || !tempMission->onGeoscape)
00339             continue;
00340         if (tempMission->pos && MAP_IsMapPositionSelected(node, tempMission->pos, x, y))
00341             MAP_MultiSelectListAddItem(MULTISELECT_TYPE_MISSION, MAP_GetIDXByMission(tempMission),
00342                 CP_MissionToTypeString(tempMission), _(tempMission->location));
00343     }
00344 
00345     /* Get selected bases */
00346     for (i = 0; i < MAX_BASES && multiSelect.nbSelect < MULTISELECT_MAXSELECT; i++) {
00347         const base_t *base = B_GetFoundedBaseByIDX(i);
00348         aircraft_t *aircraft;
00349 
00350         if (!base)
00351             continue;
00352 
00353         if (MAP_IsMapPositionSelected(node, base->pos, x, y))
00354             MAP_MultiSelectListAddItem(MULTISELECT_TYPE_BASE, i, _("Base"), base->name);
00355 
00356         /* Get selected aircraft which belong to the base */
00357         aircraft = NULL;
00358         while ((aircraft = AIR_GetNextFromBase(base, aircraft)) != NULL)
00359             if (AIR_IsAircraftOnGeoscape(aircraft) && aircraft->fuel > 0 && MAP_IsMapPositionSelected(node, aircraft->pos, x, y))
00360                 MAP_MultiSelectListAddItem(MULTISELECT_TYPE_AIRCRAFT, aircraft->idx, _("Aircraft"), aircraft->name);
00361     }
00362 
00363     /* Get selected installations */
00364     for (i = 0; i < MAX_INSTALLATIONS && multiSelect.nbSelect < MULTISELECT_MAXSELECT; i++) {
00365         const installation_t *installation = INS_GetFoundedInstallationByIDX(i);
00366         if (!installation)
00367             continue;
00368         if (MAP_IsMapPositionSelected(node, installation->pos, x, y))
00369             MAP_MultiSelectListAddItem(MULTISELECT_TYPE_INSTALLATION, i, _("Installation"), installation->name);
00370     }
00371 
00372     /* Get selected ufos */
00373     for (aircraft = ccs.ufos + ccs.numUFOs - 1; aircraft >= ccs.ufos; aircraft--)
00374         if (UFO_IsUFOSeenOnGeoscape(aircraft)
00375 #ifdef DEBUG
00376         || Cvar_GetInteger("debug_showufos")
00377 #endif
00378         )
00379             if (AIR_IsAircraftOnGeoscape(aircraft) && MAP_IsMapPositionSelected(node, aircraft->pos, x, y))
00380                 MAP_MultiSelectListAddItem(MULTISELECT_TYPE_UFO, aircraft - ccs.ufos, _("UFO Sighting"), UFO_AircraftToIDOnGeoscape(aircraft));
00381 
00382     if (multiSelect.nbSelect == 1) {
00383         /* Execute directly action for the only one element selected */
00384         Cmd_ExecuteString("multi_select_click");
00385     } else if (multiSelect.nbSelect > 1) {
00386         /* Display popup for multi selection */
00387         UI_RegisterText(TEXT_MULTISELECTION, multiSelect.popupText);
00388         CL_GameTimeStop();
00389         UI_PushWindow("popup_multi_selection", NULL);
00390     } else {
00391         /* Nothing selected */
00392         if (!ccs.selectedAircraft)
00393             MAP_ResetAction();
00394         else if (AIR_IsAircraftOnGeoscape(ccs.selectedAircraft) && AIR_AircraftHasEnoughFuel(ccs.selectedAircraft, pos)) {
00395             /* Move the selected aircraft to the position clicked */
00396             MAP_MapCalcLine(ccs.selectedAircraft->pos, pos, &ccs.selectedAircraft->route);
00397             ccs.selectedAircraft->status = AIR_TRANSIT;
00398             ccs.selectedAircraft->time = aircraft->point = 0;
00399         }
00400     }
00401 }
00402 
00403 
00404 /*
00405 ==============================================================
00406 GEOSCAPE DRAWING AND COORDINATES
00407 ==============================================================
00408 */
00413 #define UI_MAP_DIST_SELECTION 15
00414 
00417 static qboolean MAP_IsMapPositionSelected (const uiNode_t* node, const vec2_t pos, int x, int y)
00418 {
00419     int msx, msy;
00420 
00421     if (MAP_AllMapToScreen(node, pos, &msx, &msy, NULL))
00422         if (x >= msx - UI_MAP_DIST_SELECTION && x <= msx + UI_MAP_DIST_SELECTION
00423          && y >= msy - UI_MAP_DIST_SELECTION && y <= msy + UI_MAP_DIST_SELECTION)
00424             return qtrue;
00425 
00426     return qfalse;
00427 }
00428 
00433 const float STANDARD_3D_ZOOM = 40.0f;
00434 
00436 #define GLOBE_RADIUS EARTH_RADIUS * (ccs.zoom / STANDARD_3D_ZOOM)
00437 
00450 static qboolean MAP_3DMapToScreen (const uiNode_t* node, const vec2_t pos, int *x, int *y, int *z)
00451 {
00452     vec2_t mid;
00453     vec3_t v, v1, rotationAxis;
00454     const float radius = GLOBE_RADIUS;
00455 
00456     PolarToVec(pos, v);
00457 
00458     /* rotate the vector to switch of reference frame.
00459      * We switch from the static frame of earth to the local frame of the player (opposite rotation of MAP3D_ScreenToMap) */
00460     VectorSet(rotationAxis, 0, 0, 1);
00461     RotatePointAroundVector(v1, rotationAxis, v, - ccs.angles[PITCH]);
00462 
00463     VectorSet(rotationAxis, 0, 1, 0);
00464     RotatePointAroundVector(v, rotationAxis, v1, - ccs.angles[YAW]);
00465 
00466     /* set mid to the coordinates of the center of the globe */
00467     Vector2Set(mid, ccs.mapPos[0] + ccs.mapSize[0] / 2.0f, ccs.mapPos[1] + ccs.mapSize[1] / 2.0f);
00468 
00469     /* We now convert those coordinates relative to the center of the globe to coordinates of the screen
00470      * (which are relative to the upper left side of the screen) */
00471     *x = (int) (mid[0] - radius * v[1]);
00472     *y = (int) (mid[1] - radius * v[0]);
00473 
00474     if (z)
00475         *z = (int) (radius * v[2]);
00476 
00477     /* if the point is on the wrong side of earth, the player cannot see it */
00478     if (v[2] > 0)
00479         return qfalse;
00480 
00481     /* if the point is outside the screen, the player cannot see it */
00482     if (*x < ccs.mapPos[0] && *y < ccs.mapPos[1] && *x > ccs.mapPos[0] + ccs.mapSize[0] && *y > ccs.mapPos[1] + ccs.mapSize[1])
00483         return qfalse;
00484 
00485     return qtrue;
00486 }
00487 
00498 qboolean MAP_MapToScreen (const uiNode_t* node, const vec2_t pos, int *x, int *y)
00499 {
00500     float sx;
00501 
00502     /* get "raw" position */
00503     sx = pos[0] / 360 + ccs.center[0] - 0.5;
00504 
00505     /* shift it on screen */
00506     if (sx < -0.5)
00507         sx += 1.0;
00508     else if (sx > +0.5)
00509         sx -= 1.0;
00510 
00511     *x = ccs.mapPos[0] + 0.5 * ccs.mapSize[0] - sx * ccs.mapSize[0] * ccs.zoom;
00512     *y = ccs.mapPos[1] + 0.5 * ccs.mapSize[1] -
00513         (pos[1] / 180 + ccs.center[1] - 0.5) * ccs.mapSize[1] * ccs.zoom;
00514 
00515     if (*x < ccs.mapPos[0] && *y < ccs.mapPos[1] &&
00516         *x > ccs.mapPos[0] + ccs.mapSize[0] && *y > ccs.mapPos[1] + ccs.mapSize[1])
00517         return qfalse;
00518     return qtrue;
00519 }
00520 
00532 qboolean MAP_AllMapToScreen (const uiNode_t* node, const vec2_t pos, int *x, int *y, int *z)
00533 {
00534     if (cl_3dmap->integer)
00535         return MAP_3DMapToScreen(node, pos, x, y, z);
00536     else {
00537         if (z)
00538             *z = -10;
00539         return MAP_MapToScreen(node, pos, x, y);
00540     }
00541 }
00542 
00551 void MAP_Draw3DMarkerIfVisible (const uiNode_t* node, const vec2_t pos, float theta, const char *model, int skin)
00552 {
00553     if (cl_3dmap->integer) {
00554         R_Draw3DMapMarkers(ccs.mapPos[0], ccs.mapPos[1], ccs.mapSize[0], ccs.mapSize[1], ccs.angles, pos, theta, GLOBE_RADIUS, model, skin);
00555     } else {
00556         int x, y;
00557         vec3_t screenPos;
00558 
00559         MAP_AllMapToScreen(node, pos, &x, &y, NULL);
00560         VectorSet(screenPos, x, y, 0);
00561         /* models are used on 2D geoscape for aircraft */
00562         R_Draw2DMapMarkers(screenPos, theta, model, skin);
00563     }
00564 }
00565 
00566 
00574 static void MAP_ScreenToMap (const uiNode_t* node, int x, int y, vec2_t pos)
00575 {
00576     pos[0] = (((ccs.mapPos[0] - x) / ccs.mapSize[0] + 0.5) / ccs.zoom - (ccs.center[0] - 0.5)) * 360.0;
00577     pos[1] = (((ccs.mapPos[1] - y) / ccs.mapSize[1] + 0.5) / ccs.zoom - (ccs.center[1] - 0.5)) * 180.0;
00578 
00579     while (pos[0] > 180.0)
00580         pos[0] -= 360.0;
00581     while (pos[0] < -180.0)
00582         pos[0] += 360.0;
00583 }
00584 
00593 static void MAP3D_ScreenToMap (const uiNode_t* node, int x, int y, vec2_t pos)
00594 {
00595     vec2_t mid;
00596     vec3_t v, v1, rotationAxis;
00597     float dist;
00598     const float radius = GLOBE_RADIUS;
00599 
00600     /* set mid to the coordinates of the center of the globe */
00601     Vector2Set(mid, ccs.mapPos[0] + ccs.mapSize[0] / 2.0f, ccs.mapPos[1] + ccs.mapSize[1] / 2.0f);
00602 
00603     /* stop if we click outside the globe (distance is the distance of the point to the center of the globe) */
00604     dist = sqrt((x - mid[0]) * (x - mid[0]) + (y - mid[1]) * (y - mid[1]));
00605     if (dist > radius) {
00606         Vector2Set(pos, -1.0, -1.0);
00607         return;
00608     }
00609 
00610     /* calculate the coordinates in the local frame
00611      * this frame is the frame of the screen.
00612      * v[0] is the vertical axis of the screen
00613      * v[1] is the horizontal axis of the screen
00614      * v[2] is the axis perpendicular to the screen - we get its value knowing that norm of v is egal to radius
00615      *  (because the point is on the globe) */
00616     v[0] = - (y - mid[1]);
00617     v[1] = - (x - mid[0]);
00618     v[2] = - sqrt(radius * radius - (x - mid[0]) * (x - mid[0]) - (y - mid[1]) * (y - mid[1]));
00619     VectorNormalize(v);
00620 
00621     /* rotate the vector to switch of reference frame
00622      * note the ccs.angles[ROLL] is always 0, so there is only 2 rotations and not 3
00623      * and that GLOBE_ROTATE is already included in ccs.angles[YAW]
00624      * first rotation is along the horizontal axis of the screen, to put north-south axis of the earth
00625      * perpendicular to the screen */
00626     VectorSet(rotationAxis, 0, 1, 0);
00627     RotatePointAroundVector(v1, rotationAxis, v, ccs.angles[YAW]);
00628 
00629     /* second rotation is to rotate the earth around its north-south axis
00630      * so that Greenwich meridian is along the vertical axis of the screen */
00631     VectorSet(rotationAxis, 0, 0, 1);
00632     RotatePointAroundVector(v, rotationAxis, v1, ccs.angles[PITCH]);
00633 
00634     /* we therefore got in v the coordinates of the point in the static frame of the earth
00635      * that we can convert in polar coordinates to get its latitude and longitude */
00636     VecToPolar(v, pos);
00637 }
00638 
00646 void MAP_MapCalcLine (const vec2_t start, const vec2_t end, mapline_t* line)
00647 {
00648     vec3_t s, e, v;
00649     vec3_t normal;
00650     vec2_t trafo, sa, ea;
00651     float cosTrafo, sinTrafo;
00652     float phiStart, phiEnd, dPhi, phi;
00653     float *p, *last;
00654     int i, n;
00655 
00656     /* get plane normal */
00657     PolarToVec(start, s);
00658     PolarToVec(end, e);
00659     /* Procedure below won't work if start is the same than end */
00660     if (VectorCompareEps(s, e, UFO_EPSILON)) {
00661         line->distance = 0;
00662         line->numPoints = 2;
00663         Vector2Set(line->point[0], end[0], end[1]);
00664         Vector2Set(line->point[1], end[0], end[1]);
00665         return;
00666     }
00667 
00668     CrossProduct(s, e, normal);
00669     VectorNormalize(normal);
00670 
00671     /* get transformation */
00672     VecToPolar(normal, trafo);
00673     cosTrafo = cos(trafo[1] * torad);
00674     sinTrafo = sin(trafo[1] * torad);
00675 
00676     sa[0] = start[0] - trafo[0];
00677     sa[1] = start[1];
00678     PolarToVec(sa, s);
00679     ea[0] = end[0] - trafo[0];
00680     ea[1] = end[1];
00681     PolarToVec(ea, e);
00682 
00683     phiStart = atan2(s[1], cosTrafo * s[2] - sinTrafo * s[0]);
00684     phiEnd = atan2(e[1], cosTrafo * e[2] - sinTrafo * e[0]);
00685 
00686     /* get waypoints */
00687     if (phiEnd < phiStart - M_PI)
00688         phiEnd += 2 * M_PI;
00689     if (phiEnd > phiStart + M_PI)
00690         phiEnd -= 2 * M_PI;
00691 
00692     n = (phiEnd - phiStart) / M_PI * LINE_MAXSEG;
00693     if (n > 0)
00694         n = n + 1;
00695     else
00696         n = -n + 1;
00697 
00698     line->distance = fabs(phiEnd - phiStart) / n * todeg;
00699     line->numPoints = n + 1;
00700     /* make sure we do not exceed route array size */
00701     assert(line->numPoints <= LINE_MAXPTS);
00702     dPhi = (phiEnd - phiStart) / n;
00703     p = NULL;
00704     for (phi = phiStart, i = 0; i <= n; phi += dPhi, i++) {
00705         last = p;
00706         p = line->point[i];
00707         VectorSet(v, -sinTrafo * cos(phi), sin(phi), cosTrafo * cos(phi));
00708         VecToPolar(v, p);
00709         p[0] += trafo[0];
00710 
00711         if (!last) {
00712             while (p[0] < -180.0)
00713                 p[0] += 360.0;
00714             while (p[0] > +180.0)
00715                 p[0] -= 360.0;
00716         } else {
00717             while (p[0] - last[0] > +180.0)
00718                 p[0] -= 360.0;
00719             while (p[0] - last[0] < -180.0)
00720                 p[0] += 360.0;
00721         }
00722     }
00723 }
00724 
00732 static void MAP_MapDrawLine (const uiNode_t* node, const mapline_t* line)
00733 {
00734     const vec4_t color = {1, 0.5, 0.5, 1};
00735     screenPoint_t pts[LINE_MAXPTS];
00736     screenPoint_t *p;
00737     int i, start, old;
00738 
00739     /* draw */
00740     R_Color(color);
00741     start = 0;
00742     old = ccs.mapSize[0] / 2;
00743     for (i = 0, p = pts; i < line->numPoints; i++, p++) {
00744         MAP_MapToScreen(node, line->point[i], &p->x, &p->y);
00745 
00746         /* If we cross longitude 180 degree (right/left edge of the screen), draw the first part of the path */
00747         if (i > start && abs(p->x - old) > ccs.mapSize[0] / 2) {
00748             /* shift last point */
00749             int diff;
00750 
00751             if (p->x - old > ccs.mapSize[0] / 2)
00752                 diff = -ccs.mapSize[0] * ccs.zoom;
00753             else
00754                 diff = ccs.mapSize[0] * ccs.zoom;
00755             p->x += diff;
00756 
00757             /* wrap around screen border */
00758             R_DrawLineStrip(i - start, (int*)(&pts));
00759 
00760             /* first path of the path is drawn, now we begin the second part of the path */
00761             /* shift first point, continue drawing */
00762             start = i;
00763             pts[0].x = p[-1].x - diff;
00764             pts[0].y = p[-1].y;
00765             p = pts;
00766         }
00767         old = p->x;
00768     }
00769 
00770     R_DrawLineStrip(i - start, (int*)(&pts));
00771     R_Color(NULL);
00772 }
00773 
00781 static void MAP_3DMapDrawLine (const uiNode_t* node, const mapline_t* line)
00782 {
00783     const vec4_t color = {1, 0.5, 0.5, 1};
00784     screenPoint_t pts[LINE_MAXPTS];
00785     int i, numPoints, start;
00786 
00787     start = 0;
00788 
00789     /* draw only when the point of the path is visible */
00790     R_Color(color);
00791     for (i = 0, numPoints = 0; i < line->numPoints; i++) {
00792         if (MAP_3DMapToScreen(node, line->point[i], &pts[i].x, &pts[i].y, NULL))
00793             numPoints++;
00794         else if (!numPoints)
00795             /* the point which is not drawn is at the beginning of the path */
00796             start++;
00797     }
00798 
00799     R_DrawLineStrip(numPoints, (int*)(&pts[start]));
00800     R_Color(NULL);
00801 }
00802 
00803 #define CIRCLE_DRAW_POINTS  60
00804 
00813 void MAP_MapDrawEquidistantPoints (const uiNode_t* node, const vec2_t center, const float angle, const vec4_t color)
00814 {
00815     int i, xCircle, yCircle;
00816     screenPoint_t pts[CIRCLE_DRAW_POINTS + 1];
00817     vec2_t posCircle;
00818     qboolean oldDraw = qfalse;
00819     int numPoints = 0;
00820     vec3_t initialVector, rotationAxis, currentPoint, centerPos;
00821 
00822     R_Color(color);
00823 
00824     /* Set centerPos corresponding to cartesian coordinates of the center point */
00825     PolarToVec(center, centerPos);
00826 
00827     /* Find a perpendicular vector to centerPos, and rotate centerPos around him to obtain one point distant of angle from centerPos */
00828     PerpendicularVector(rotationAxis, centerPos);
00829     RotatePointAroundVector(initialVector, rotationAxis, centerPos, angle);
00830 
00831     /* Now, each equidistant point is given by a rotation around centerPos */
00832     for (i = 0; i <= CIRCLE_DRAW_POINTS; i++) {
00833         qboolean draw = qfalse;
00834         const float degrees = i * 360.0f / (float)CIRCLE_DRAW_POINTS;
00835         RotatePointAroundVector(currentPoint, centerPos, initialVector, degrees);
00836         VecToPolar(currentPoint, posCircle);
00837         if (MAP_AllMapToScreen(node, posCircle, &xCircle, &yCircle, NULL)) {
00838             draw = qtrue;
00839             if (!cl_3dmap->integer && numPoints != 0 && abs(pts[numPoints - 1].x - xCircle) > 512)
00840                 oldDraw = qfalse;
00841         }
00842 
00843         /* if moving from a point of the screen to a distant one, draw the path we already calculated, and begin a new path
00844          * (to avoid unwanted lines) */
00845         if (draw != oldDraw && i != 0) {
00846             R_DrawLineStrip(numPoints, (int*)(&pts));
00847             numPoints = 0;
00848         }
00849         /* if the current point is to be drawn, add it to the path */
00850         if (draw) {
00851             pts[numPoints].x = xCircle;
00852             pts[numPoints].y = yCircle;
00853             numPoints++;
00854         }
00855         /* update value of oldDraw */
00856         oldDraw = draw;
00857     }
00858 
00859     /* Draw the last path */
00860     R_DrawLineStrip(numPoints, (int*)(&pts));
00861     R_Color(NULL);
00862 }
00863 
00872 static float MAP_AngleOfPath3D (const vec3_t start, const vec2_t end, vec3_t direction, vec3_t ortVector)
00873 {
00874     float angle = 0.0f;
00875     vec3_t start3D, end3D, north3D, ortToDest, ortToPole, v;
00876     const vec2_t northPole = {0.0f, 90.0f}; 
00878     PolarToVec(start, start3D);
00879     PolarToVec(end, end3D);
00880     PolarToVec(northPole, north3D);
00881 
00882     /* calculate the vector othogonal to movement */
00883     CrossProduct(start3D, end3D, ortToDest);
00884     VectorNormalize(ortToDest);
00885     if (ortVector) {
00886         VectorCopy(ortToDest, ortVector);
00887     }
00888 
00889     /* calculate the vector othogonal to north pole (from model location) */
00890     CrossProduct(start3D, north3D, ortToPole);
00891     VectorNormalize(ortToPole);
00892 
00897     /* smooth change of direction if the model is not idle */
00898     if (direction) {
00899         float dist;
00900         VectorSubtract(ortToDest, direction, v);
00901         dist = VectorLength(v);
00902         if (dist > 0.01) {
00903             vec3_t rotationAxis;
00904             CrossProduct(direction, ortToDest, rotationAxis);
00905             VectorNormalize(rotationAxis);
00906             RotatePointAroundVector(v, rotationAxis, direction, 5.0);
00907             VectorCopy(v, direction);
00908             VectorSubtract(ortToDest, direction, v);
00909             if (VectorLength(v) < dist)
00910                 VectorCopy(direction, ortToDest);
00911             else
00912                 VectorCopy(ortToDest, direction);
00913         }
00914     }
00915 
00916     /* calculate the angle the model is making at earth surface with north pole direction */
00917     angle = todeg * acos(DotProduct(ortToDest, ortToPole));
00918     /* with arcos, you only get the absolute value of the angle: get the sign */
00919     CrossProduct(ortToDest, ortToPole, v);
00920     if (DotProduct(start3D, v) < 0)
00921         angle = - angle;
00922 
00923     return angle;
00924 }
00925 
00934 static float MAP_AngleOfPath2D (const vec3_t start, const vec2_t end, vec3_t direction, vec3_t ortVector)
00935 {
00936     float angle = 0.0f;
00937     vec3_t start3D, end3D, tangentVector, v, rotationAxis;
00938 
00939     /* calculate the vector tangent to movement */
00940     PolarToVec(start, start3D);
00941     PolarToVec(end, end3D);
00942     if (ortVector) {
00943         CrossProduct(start3D, end3D, ortVector);
00944         VectorNormalize(ortVector);
00945         CrossProduct(ortVector, start3D, tangentVector);
00946     } else {
00947         CrossProduct(start3D, end3D, v);
00948         CrossProduct(v, start3D, tangentVector);
00949     }
00950     VectorNormalize(tangentVector);
00951 
00952     /* smooth change of direction if the model is not idle */
00953     if (direction) {
00954         float dist;
00955         VectorSubtract(tangentVector, direction, v);
00956         dist = VectorLength(v);
00957         if (dist > 0.01) {
00958             CrossProduct(direction, tangentVector, rotationAxis);
00959             VectorNormalize(rotationAxis);
00960             RotatePointAroundVector(v, rotationAxis, direction, 5.0);
00961             VectorSubtract(tangentVector, direction, v);
00962             if (VectorLength(v) < dist)
00963                 VectorCopy(direction, tangentVector);
00964             else
00965                 VectorCopy(tangentVector, direction);
00966         }
00967     }
00968 
00969     VectorSet(rotationAxis, 0, 0, 1);
00970     RotatePointAroundVector(v, rotationAxis, tangentVector, - start[0]);
00971     VectorSet(rotationAxis, 0, 1, 0);
00972     RotatePointAroundVector(tangentVector, rotationAxis, v, start[1] + 90.0f);
00973 
00974     /* calculate the orientation angle of the model around axis perpendicular to the screen */
00975     angle = todeg * atan(tangentVector[0] / tangentVector[1]);
00976     if (tangentVector[1] > 0)
00977         angle -= 90.0f;
00978     else
00979         angle += 90.0f;
00980 
00981     return angle;
00982 }
00983 
00992 float MAP_AngleOfPath (const vec3_t start, const vec2_t end, vec3_t direction, vec3_t ortVector)
00993 {
00994     float angle;
00995 
00996     if (cl_3dmap->integer)
00997         angle = MAP_AngleOfPath3D(start, end, direction, ortVector);
00998     else
00999         angle = MAP_AngleOfPath2D(start, end, direction, ortVector);
01000 
01001     return angle;
01002 }
01003 
01009 static void MAP_ConvertObjectPositionToGeoscapePosition (float* vector, const vec2_t objectPos)
01010 {
01011     if (cl_3dmap->integer)
01012         VectorSet(vector, objectPos[0], -objectPos[1], 0);
01013     else
01014         Vector2Set(vector, objectPos[0], objectPos[1]);
01015 }
01016 
01020 static void MAP_GetMissionAngle (float *vector, int id)
01021 {
01022     mission_t *mission = MAP_GetMissionByIDX(id);
01023     assert(mission);
01024     MAP_ConvertObjectPositionToGeoscapePosition(vector, mission->pos);
01025     MAP_SelectMission(mission);
01026 }
01027 
01031 static void MAP_GetUFOAngle (float *vector, int idx)
01032 {
01033     aircraft_t *aircraft;
01034     /* Cycle through UFO (only those visible on geoscape) */
01035     for (aircraft = ccs.ufos + ccs.numUFOs - 1; aircraft >= ccs.ufos; aircraft --) {
01036         if (aircraft->idx != idx)
01037             continue;
01038         if (UFO_IsUFOSeenOnGeoscape(aircraft)) {
01039             MAP_ConvertObjectPositionToGeoscapePosition(vector, aircraft->pos);
01040             MAP_SelectUFO(aircraft);
01041             return;
01042         }
01043     }
01044 }
01045 
01046 
01054 static void MAP_StartCenter (void)
01055 {
01056     if (cl_3dmap->integer) {
01057         /* case 3D geoscape */
01058         vec3_t diff;
01059 
01060         smoothFinalGlobeAngle[1] += GLOBE_ROTATE;
01061         VectorSubtract(smoothFinalGlobeAngle, ccs.angles, diff);
01062         smoothDeltaLength = VectorLength(diff);
01063     } else {
01064         /* case 2D geoscape */
01065         vec2_t diff;
01066 
01067         Vector2Set(smoothFinal2DGeoscapeCenter, 0.5f - smoothFinal2DGeoscapeCenter[0] / 360.0f, 0.5f - smoothFinal2DGeoscapeCenter[1] / 180.0f);
01068         if (smoothFinal2DGeoscapeCenter[1] < 0.5 / ZOOM_LIMIT)
01069             smoothFinal2DGeoscapeCenter[1] = 0.5 / ZOOM_LIMIT;
01070         if (smoothFinal2DGeoscapeCenter[1] > 1.0 - 0.5 / ZOOM_LIMIT)
01071             smoothFinal2DGeoscapeCenter[1] = 1.0 - 0.5 / ZOOM_LIMIT;
01072         diff[0] = smoothFinal2DGeoscapeCenter[0] - ccs.center[0];
01073         diff[1] = smoothFinal2DGeoscapeCenter[1] - ccs.center[1];
01074         smoothDeltaLength = sqrt(diff[0] * diff[0] + diff[1] * diff[1]);
01075     }
01076 
01077     smoothFinalZoom = ZOOM_LIMIT;
01078     smoothDeltaZoom = fabs(smoothFinalZoom - ccs.zoom);
01079     smoothRotation = qtrue;
01080 }
01081 
01085 static void MAP_SelectObject_f (void)
01086 {
01087     const char *type;
01088     int idx;
01089 
01090     if (Cmd_Argc() != 3) {
01091         Com_Printf("Usage: %s <mission|ufo> <id>\n", Cmd_Argv(0));
01092         return;
01093     }
01094 
01095     type = Cmd_Argv(1);
01096     idx = atoi(Cmd_Argv(2));
01097 
01098     if (cl_3dmap->integer) {
01099         if (!strcmp(type, "mission"))
01100             MAP_GetMissionAngle(smoothFinalGlobeAngle, idx);
01101         else if (!strcmp(type, "ufo"))
01102             MAP_GetUFOAngle(smoothFinalGlobeAngle, idx);
01103         else {
01104             Com_Printf("MAP_SelectObject_f: type %s unsupported.", type);
01105             return;
01106         }
01107     } else {
01108         if (!strcmp(type, "mission"))
01109             MAP_GetMissionAngle(smoothFinal2DGeoscapeCenter, idx);
01110         else if (!strcmp(type, "ufo"))
01111             MAP_GetUFOAngle(smoothFinal2DGeoscapeCenter, idx);
01112         else {
01113             Com_Printf("MAP_SelectObject_f: type %s unsupported.", type);
01114             return;
01115         }
01116     }
01117     MAP_StartCenter();
01118 }
01119 
01126 static void MAP_GetGeoscapeAngle (float *vector)
01127 {
01128     int baseIdx;
01129     int counter = 0;
01130     int maxEventIdx;
01131     const int numMissions = CP_CountMissionOnGeoscape();
01132     aircraft_t *aircraft;
01133 
01134     /* If the value of maxEventIdx is too big or to low, restart from begining */
01135     maxEventIdx = numMissions + ccs.numBases + ccs.numInstallations - 1;
01136     for (baseIdx = 0; baseIdx < MAX_BASES; baseIdx++) {
01137         base_t *base = B_GetFoundedBaseByIDX(baseIdx);
01138         aircraft_t *aircraft;
01139 
01140         aircraft = NULL;
01141         while ((aircraft = AIR_GetNextFromBase(base, aircraft)) != NULL) {
01142             if (AIR_IsAircraftOnGeoscape(aircraft))
01143                 maxEventIdx++;
01144         }
01145     }
01146     for (aircraft = ccs.ufos + ccs.numUFOs - 1; aircraft >= ccs.ufos; aircraft --) {
01147         if (UFO_IsUFOSeenOnGeoscape(aircraft))
01148             maxEventIdx++;
01149     }
01150 
01151     /* if there's nothing to center the view on, just go to 0,0 pos */
01152     if (maxEventIdx < 0) {
01153         MAP_ConvertObjectPositionToGeoscapePosition(vector, vec2_origin);
01154         return;
01155     }
01156 
01157     /* check centerOnEventIdx is within the bounds */
01158     if (centerOnEventIdx < 0)
01159         centerOnEventIdx = maxEventIdx;
01160     if (centerOnEventIdx > maxEventIdx)
01161         centerOnEventIdx = 0;
01162 
01163     /* Cycle through missions */
01164     if (centerOnEventIdx < numMissions) {
01165         const linkedList_t *list = ccs.missions;
01166         mission_t *mission = NULL;
01167         for (;list && (centerOnEventIdx != counter - 1); list = list->next) {
01168             mission = (mission_t *)list->data;
01169             if (mission->stage != STAGE_NOT_ACTIVE && mission->stage != STAGE_OVER && mission->onGeoscape)
01170                 counter++;
01171         }
01172         assert(mission);
01173 
01174         MAP_ConvertObjectPositionToGeoscapePosition(vector, mission->pos);
01175         MAP_SelectMission(mission);
01176         return;
01177     }
01178     counter += numMissions;
01179 
01180     /* Cycle through bases */
01181     if (centerOnEventIdx < ccs.numBases + counter) {
01182         for (baseIdx = 0; baseIdx < MAX_BASES; baseIdx++) {
01183             const base_t *base = B_GetFoundedBaseByIDX(baseIdx);
01184             if (!base)
01185                 continue;
01186 
01187             if (counter == centerOnEventIdx) {
01188                 MAP_ConvertObjectPositionToGeoscapePosition(vector, ccs.bases[baseIdx].pos);
01189                 return;
01190             }
01191             counter++;
01192         }
01193     }
01194     counter += ccs.numBases;
01195 
01196     /* Cycle through installations */
01197     if (centerOnEventIdx < ccs.numInstallations + counter) {
01198         int instIdx;
01199         for (instIdx = 0; instIdx < MAX_INSTALLATIONS; instIdx++) {
01200             const installation_t *inst = INS_GetFoundedInstallationByIDX(instIdx);
01201             if (!inst)
01202                 continue;
01203 
01204             if (counter == centerOnEventIdx) {
01205                 MAP_ConvertObjectPositionToGeoscapePosition(vector, ccs.installations[instIdx].pos);
01206                 return;
01207             }
01208             counter++;
01209         }
01210     }
01211     counter += ccs.numInstallations;
01212 
01213     /* Cycle through aircraft (only those present on geoscape) */
01214     for (baseIdx = 0; baseIdx < MAX_BASES; baseIdx++) {
01215         base_t *base = B_GetFoundedBaseByIDX(baseIdx);
01216 
01217         aircraft = NULL;
01218         while ((aircraft = AIR_GetNextFromBase(base, aircraft)) != NULL) {
01219             if (AIR_IsAircraftOnGeoscape(aircraft)) {
01220                 if (centerOnEventIdx == counter) {
01221                     MAP_ConvertObjectPositionToGeoscapePosition(vector, aircraft->pos);
01222                     MAP_SelectAircraft(aircraft);
01223                     return;
01224                 }
01225                 counter++;
01226             }
01227         }
01228     }
01229 
01230     /* Cycle through UFO (only those visible on geoscape) */
01231     for (aircraft = ccs.ufos + ccs.numUFOs - 1; aircraft >= ccs.ufos; aircraft --) {
01232         if (UFO_IsUFOSeenOnGeoscape(aircraft)) {
01233             if (centerOnEventIdx == counter) {
01234                 MAP_ConvertObjectPositionToGeoscapePosition(vector, aircraft->pos);
01235                 MAP_SelectUFO(aircraft);
01236                 return;
01237             }
01238             counter++;
01239         }
01240     }
01241 }
01242 
01253 void MAP_CenterOnPoint_f (void)
01254 {
01255     if (strcmp(UI_GetActiveWindowName(), "geoscape"))
01256         return;
01257 
01258     centerOnEventIdx++;
01259 
01260     if (cl_3dmap->integer)
01261         MAP_GetGeoscapeAngle(smoothFinalGlobeAngle);
01262     else
01263         MAP_GetGeoscapeAngle(smoothFinal2DGeoscapeCenter);
01264     MAP_StartCenter();
01265 }
01266 
01273 static void MAP3D_SmoothRotate (void)
01274 {
01275     vec3_t diff;
01276     const float diffZoom = smoothFinalZoom - ccs.zoom;
01277 
01278     VectorSubtract(smoothFinalGlobeAngle, ccs.angles, diff);
01279 
01280     if (smoothDeltaLength > smoothDeltaZoom) {
01281         /* when we rotate (and zoom) */
01282         const float diffAngle = VectorLength(diff);
01283         const float epsilon = 0.1f;
01284         if (diffAngle > epsilon) {
01285             float rotationSpeed;
01286             /* Append the old speed to the new speed if this is the first half of a new rotation, but never exceed the max speed.
01287              * This allows the globe to rotate at maximum speed when the button is held down. */
01288             if (diffAngle / smoothDeltaLength > 0.5)
01289                 rotationSpeed = min(diffAngle, curRotationSpeed + sin(3.05f * diffAngle / smoothDeltaLength) * diffAngle * 0.5);
01290             else
01291                 rotationSpeed = sin(3.05f * diffAngle / smoothDeltaLength) * diffAngle;
01292             curRotationSpeed = rotationSpeed;
01293             VectorScale(diff, smoothAcceleration / diffAngle * rotationSpeed, diff);
01294             VectorAdd(ccs.angles, diff, ccs.angles);
01295             ccs.zoom = ccs.zoom + smoothAcceleration * diffZoom / diffAngle * rotationSpeed;
01296             return;
01297         }
01298     } else {
01299         const float epsilonZoom = 0.01f;
01300         /* when we zoom only */
01301         if (fabs(diffZoom) > epsilonZoom) {
01302             float speed;
01303             /* Append the old speed to the new speed if this is the first half of a new zoom operation, but never exceed the max speed.
01304              * This allows the globe to zoom at maximum speed when the button is held down. */
01305             if (fabs(diffZoom) / smoothDeltaZoom > 0.5)
01306                 speed = min(smoothAcceleration * 2.0, curZoomSpeed + sin(3.05f * (fabs(diffZoom) / smoothDeltaZoom)) * smoothAcceleration);
01307             else {
01308                 speed = sin(3.05f * (fabs(diffZoom) / smoothDeltaZoom)) * smoothAcceleration * 2.0;
01309             }
01310             curZoomSpeed = speed;
01311             ccs.zoom = ccs.zoom + diffZoom * speed;
01312             return;
01313         }
01314     }
01315 
01316     /* if we reach this point, that means that movement is over */
01317     VectorCopy(smoothFinalGlobeAngle, ccs.angles);
01318     smoothRotation = qfalse;
01319     ccs.zoom = smoothFinalZoom;
01320 }
01321 
01327 void MAP_StopSmoothMovement (void)
01328 {
01329     smoothRotation = qfalse;
01330 }
01331 
01332 #define SMOOTHING_STEP_2D   0.02f
01333 
01340 static void MAP_SmoothTranslate (void)
01341 {
01342     const float dist1 = smoothFinal2DGeoscapeCenter[0] - ccs.center[0];
01343     const float dist2 = smoothFinal2DGeoscapeCenter[1] - ccs.center[1];
01344     const float length = sqrt(dist1 * dist1 + dist2 * dist2);
01345 
01346     if (length < SMOOTHING_STEP_2D) {
01347         ccs.center[0] = smoothFinal2DGeoscapeCenter[0];
01348         ccs.center[1] = smoothFinal2DGeoscapeCenter[1];
01349         ccs.zoom = smoothFinalZoom;
01350         smoothRotation = qfalse;
01351     } else {
01352         const float diffZoom = smoothFinalZoom - ccs.zoom;
01353         ccs.center[0] = ccs.center[0] + SMOOTHING_STEP_2D * dist1 / length;
01354         ccs.center[1] = ccs.center[1] + SMOOTHING_STEP_2D * dist2 / length;
01355         ccs.zoom = ccs.zoom + SMOOTHING_STEP_2D * diffZoom;
01356     }
01357 }
01358 
01359 #define BULLET_SIZE 1
01360 
01366 static void MAP_DrawBullets (const uiNode_t* node, const vec3_t pos)
01367 {
01368     int x, y;
01369 
01370     if (MAP_AllMapToScreen(node, pos, &x, &y, NULL))
01371         R_DrawFill(x, y, BULLET_SIZE, BULLET_SIZE, yellow);
01372 }
01373 
01382 static void MAP_DrawBeam (const uiNode_t* node, const vec3_t start, const vec3_t end, const vec4_t color)
01383 {
01384     int points[4];
01385 
01386     if (!MAP_AllMapToScreen(node, start, &(points[0]), &(points[1]), NULL))
01387         return;
01388     if (!MAP_AllMapToScreen(node, end, &(points[2]), &(points[3]), NULL))
01389         return;
01390 
01391     R_Color(color);
01392     R_DrawLine(points, 2.0);
01393     R_Color(NULL);
01394 }
01395 
01396 #define SELECT_CIRCLE_RADIUS    1.5f + 3.0f / ccs.zoom
01397 
01403 static void MAP_DrawMapOneMission (const uiNode_t* node, const mission_t *ms)
01404 {
01405     int x, y;
01406 
01407     if (ms == ccs.selectedMission)
01408         Cvar_Set("mn_mapdaytime", MAP_IsNight(ms->pos) ? _("Night") : _("Day"));
01409 
01410     if (!MAP_AllMapToScreen(node, ms->pos, &x, &y, NULL))
01411         return;
01412 
01413     if (ms == ccs.selectedMission) {
01414         /* Draw circle around the mission */
01415         if (cl_3dmap->integer) {
01416             if (!ccs.selectedMission->active)
01417                 MAP_MapDrawEquidistantPoints(node, ms->pos, SELECT_CIRCLE_RADIUS, yellow);
01418         } else {
01419             const image_t *image;
01420             if (ccs.selectedMission->active) {
01421                 image = geoscapeImages[GEOSCAPE_IMAGE_MISSION_ACTIVE];
01422             } else {
01423                 image = geoscapeImages[GEOSCAPE_IMAGE_MISSION_SELECTED];
01424             }
01425             R_DrawImage(x - image->width / 2, y - image->height / 2, image);
01426         }
01427     }
01428 
01429     /* Draw mission model (this must be after drawing 'selected circle' so that the model looks above it)*/
01430     if (cl_3dmap->integer) {
01431         MAP_Draw3DMarkerIfVisible(node, ms->pos, defaultBaseAngle, MAP_GetMissionModel(ms), 0);
01432     } else {
01433         const image_t *image = geoscapeImages[GEOSCAPE_IMAGE_MISSION];
01434         R_DrawImage(x - image->width / 2, y - image->height / 2, image);
01435     }
01436 
01437     UI_DrawString("f_verysmall", ALIGN_UL, x + 10, y, 0, 0, 0,  _(ms->location), 0, 0, NULL, qfalse, 0);
01438 }
01439 
01448 static void MAP_DrawMapOneInstallation (const uiNode_t* node, const installation_t *installation,
01449     qboolean oneUFOVisible, const char* font)
01450 {
01451     const installationTemplate_t *tpl = installation->installationTemplate;
01452     int x, y;
01453 
01454     /* Draw weapon range if at least one UFO is visible */
01455     if (oneUFOVisible && AII_InstallationCanShoot(installation)) {
01456         int i;
01457         for (i = 0; i < tpl->maxBatteries; i++) {
01458             const aircraftSlot_t const *slot = &installation->batteries[i].slot;
01459             if (slot->item && slot->ammoLeft != 0 && slot->installationTime == 0) {
01460                 MAP_MapDrawEquidistantPoints(node, installation->pos,
01461                     slot->ammo->craftitem.stats[AIR_STATS_WRANGE], red);
01462             }
01463         }
01464     }
01465 
01466     /* Draw installation radar (only the "wire" style part) */
01467     if (MAP_IsRadarOverlayActivated())
01468         RADAR_DrawInMap(node, &installation->radar, installation->pos);
01469 
01470     /* Draw installation */
01471     if (cl_3dmap->integer) {
01472         MAP_Draw3DMarkerIfVisible(node, installation->pos, defaultBaseAngle, tpl->model, 0);
01473     } else if (MAP_MapToScreen(node, installation->pos, &x, &y)) {
01474         const image_t *image = R_FindImage(tpl->image, it_pic);
01475         if (image)
01476             R_DrawImage(x - image->width / 2, y - image->height / 2, image);
01477     }
01478 
01479     /* Draw installation names */
01480     if (MAP_AllMapToScreen(node, installation->pos, &x, &y, NULL))
01481         UI_DrawString(font, ALIGN_UL, x, y + 10, 0, 0, 0, installation->name, 0, 0, NULL, qfalse, 0);
01482 }
01483 
01491 static void MAP_DrawMapOneBase (const uiNode_t* node, const base_t *base,
01492     qboolean oneUFOVisible, const char* font)
01493 {
01494     int x, y;
01495 
01496     /* Draw weapon range if at least one UFO is visible */
01497     if (oneUFOVisible && AII_BaseCanShoot(base)) {
01498         int i;
01499         for (i = 0; i < base->numBatteries; i++) {
01500             const aircraftSlot_t const *slot = &base->batteries[i].slot;
01501             if (slot->item && slot->ammoLeft != 0 && slot->installationTime == 0) {
01502                 MAP_MapDrawEquidistantPoints(node, base->pos,
01503                     slot->ammo->craftitem.stats[AIR_STATS_WRANGE], red);
01504             }
01505         }
01506         for (i = 0; i < base->numLasers; i++) {
01507             const aircraftSlot_t const *slot = &base->lasers[i].slot;
01508             if (slot->item && slot->ammoLeft != 0 && slot->installationTime == 0) {
01509                 MAP_MapDrawEquidistantPoints(node, base->pos,
01510                     slot->ammo->craftitem.stats[AIR_STATS_WRANGE], red);
01511             }
01512         }
01513     }
01514 
01515     /* Draw base radar (only the "wire" style part) */
01516     if (MAP_IsRadarOverlayActivated())
01517         RADAR_DrawInMap(node, &base->radar, base->pos);
01518 
01519     /* Draw base */
01520     if (cl_3dmap->integer) {
01521         if (base->baseStatus == BASE_UNDER_ATTACK)
01522             /* two skins - second skin is for baseattack */
01523             MAP_Draw3DMarkerIfVisible(node, base->pos, defaultBaseAngle, "geoscape/base", 1);
01524         else
01525             MAP_Draw3DMarkerIfVisible(node, base->pos, defaultBaseAngle, "geoscape/base", 0);
01526     } else if (MAP_MapToScreen(node, base->pos, &x, &y)) {
01527         const image_t *image;
01528         if (base->baseStatus == BASE_UNDER_ATTACK)
01529             image = geoscapeImages[GEOSCAPE_IMAGE_BASE_ATTACK];
01530         else
01531             image = geoscapeImages[GEOSCAPE_IMAGE_BASE];
01532 
01533         R_DrawImage(x - image->width / 2, y - image->height / 2, image);
01534     }
01535 
01536     /* Draw base names */
01537     if (MAP_AllMapToScreen(node, base->pos, &x, &y, NULL))
01538         UI_DrawString(font, ALIGN_UL, x, y + 10, 0, 0, 0, base->name, 0, 0, NULL, qfalse, 0);
01539 }
01540 
01547 static void MAP_DrawAircraftHealthBar (const uiNode_t* node, const aircraft_t *aircraft) {
01548     const vec4_t bordercolor = {1, 1, 1, 1};
01549     const int width = 8 * ccs.zoom;
01550     const int height = 1 * ccs.zoom * 0.9;
01551     vec4_t color;
01552     int centerX;
01553     int centerY;
01554 
01555     if (!aircraft)
01556         return;
01557     if (aircraft->stats[AIR_STATS_DAMAGE] <= 0)
01558         return;
01559 
01560     if (((float)aircraft->damage / aircraft->stats[AIR_STATS_DAMAGE]) <= .33) {
01561         Vector4Copy(red, color);
01562     } else if (((float)aircraft->damage / aircraft->stats[AIR_STATS_DAMAGE]) <= .75) {
01563         Vector4Copy(yellow, color);
01564     } else {
01565         Vector4Copy(green, color);
01566     }
01567 
01568     MAP_AllMapToScreen(node, aircraft->pos, &centerX, &centerY, NULL);
01569 
01570     R_DrawFill(centerX - width / 2 , centerY - 5 * ccs.zoom, round(width * ((float)aircraft->damage / aircraft->stats[AIR_STATS_DAMAGE])), height, color);
01571     R_DrawRect(centerX - width / 2, centerY - 5 * ccs.zoom, width, height, bordercolor, 1.0, 1);
01572 
01573 }
01574 
01581 static void MAP_DrawMapOnePhalanxAircraft (const uiNode_t* node, aircraft_t *aircraft, qboolean oneUFOVisible)
01582 {
01583     float angle;
01584 
01585     /* Draw aircraft radar (only the "wire" style part) */
01586     if (MAP_IsRadarOverlayActivated())
01587         RADAR_DrawInMap(node, &aircraft->radar, aircraft->pos);
01588 
01589     /* Draw only the bigger weapon range on geoscape: more detail will be given on airfight map */
01590     if (oneUFOVisible)
01591         MAP_MapDrawEquidistantPoints(node, aircraft->pos, aircraft->stats[AIR_STATS_WRANGE] / 1000.0f, red);
01592 
01593     /* Draw aircraft route */
01594     if (aircraft->status >= AIR_TRANSIT) {
01595         /* aircraft is moving */
01596         mapline_t path;
01597 
01598         path.numPoints = aircraft->route.numPoints - aircraft->point;
01600         if (path.numPoints > 1) {
01601             memcpy(path.point, aircraft->pos, sizeof(vec2_t));
01602             memcpy(path.point + 1, aircraft->route.point + aircraft->point + 1, (path.numPoints - 1) * sizeof(vec2_t));
01603             if (cl_3dmap->integer)
01604                 MAP_3DMapDrawLine(node, &path);
01605             else
01606                 MAP_MapDrawLine(node, &path);
01607         }
01608         angle = MAP_AngleOfPath(aircraft->pos, aircraft->route.point[aircraft->route.numPoints - 1], aircraft->direction, NULL);
01609     } else {
01610         /* aircraft is idle */
01611         angle = 0.0f;
01612     }
01613 
01614     /* Draw a circle around selected aircraft */
01615     if (aircraft == ccs.selectedAircraft) {
01616         const image_t *image = geoscapeImages[GEOSCAPE_IMAGE_MISSION];
01617         int x;
01618         int y;
01619 
01620         if (cl_3dmap->integer)
01621             MAP_MapDrawEquidistantPoints(node, aircraft->pos, SELECT_CIRCLE_RADIUS, yellow);
01622         else {
01623             MAP_AllMapToScreen(node, aircraft->pos, &x, &y, NULL);
01624             R_DrawImage(x - image->width / 2, y - image->height / 2, image);
01625         }
01626 
01627         /* Draw a circle around ufo purchased by selected aircraft */
01628         if (aircraft->status == AIR_UFO && MAP_AllMapToScreen(node, aircraft->aircraftTarget->pos, &x, &y, NULL)) {
01629             if (cl_3dmap->integer)
01630                 MAP_MapDrawEquidistantPoints(node, aircraft->aircraftTarget->pos, SELECT_CIRCLE_RADIUS, yellow);
01631             else
01632                 R_DrawImage(x - image->width / 2, y - image->height / 2, image);
01633         }
01634     }
01635 
01636     /* Draw aircraft (this must be after drawing 'selected circle' so that the aircraft looks above it)*/
01637     MAP_Draw3DMarkerIfVisible(node, aircraft->pos, angle, aircraft->model, 0);
01638     VectorCopy(aircraft->pos, aircraft->oldDrawPos);
01639 
01641     if (oneUFOVisible || Cvar_GetInteger("debug_showcrafthealth") >= 1)
01642         MAP_DrawAircraftHealthBar(node, aircraft);
01643 }
01644 
01654 static const char *MAP_GetMissionText (char *buffer, size_t size, const mission_t *mission)
01655 {
01656     assert(mission);
01657     Com_sprintf(buffer, size, _("Location: %s\nType: %s\nObjective: %s"), mission->location,
01658             CP_MissionToTypeString(mission), (mission->mapDef) ? _(mission->mapDef->description) : _("Unknown"));
01659     return buffer;
01660 }
01661 
01671 static const char *MAP_GetShortMissionText (char *buffer, size_t size, const mission_t *mission)
01672 {
01673     assert(mission);
01674     Com_sprintf(buffer, size, _("%s (%s)\n%s"),
01675             CP_MissionToTypeString(mission),
01676             mission->location,
01677             (mission->mapDef) ? _(mission->mapDef->description) : _("Unknown"));
01678     return buffer;
01679 }
01680 
01690 static const char *MAP_GetAircraftText (char *buffer, size_t size, const aircraft_t *aircraft)
01691 {
01692     if (aircraft->status == AIR_UFO) {
01693         const float distance = GetDistanceOnGlobe(aircraft->pos, aircraft->aircraftTarget->pos);
01694         Com_sprintf(buffer, size, _("Name:\t%s (%i/%i)\n"), aircraft->name, AIR_GetTeamSize(aircraft), aircraft->maxTeamSize);
01695         Q_strcat(buffer, va(_("Status:\t%s\n"), AIR_AircraftStatusToName(aircraft)), size);
01696         Q_strcat(buffer, va(_("Distance to target:\t\t%.0f\n"), distance), size);
01697         Q_strcat(buffer, va(_("Speed:\t%i km/h\n"), CL_AircraftMenuStatsValues(aircraft->stats[AIR_STATS_SPEED], AIR_STATS_SPEED)), size);
01698         Q_strcat(buffer, va(_("Fuel:\t%i/%i\n"), CL_AircraftMenuStatsValues(aircraft->fuel, AIR_STATS_FUELSIZE),
01699             CL_AircraftMenuStatsValues(aircraft->stats[AIR_STATS_FUELSIZE], AIR_STATS_FUELSIZE)), size);
01700         Q_strcat(buffer, va(_("ETA:\t%sh\n"), CL_SecondConvert((float)SECONDS_PER_HOUR * distance / aircraft->stats[AIR_STATS_SPEED])), size);
01701     } else {
01702         Com_sprintf(buffer, size, _("Name:\t%s (%i/%i)\n"), aircraft->name, AIR_GetTeamSize(aircraft), aircraft->maxTeamSize);
01703         Q_strcat(buffer, va(_("Status:\t%s\n"), AIR_AircraftStatusToName(aircraft)), size);
01704         Q_strcat(buffer, va(_("Speed:\t%i km/h\n"), CL_AircraftMenuStatsValues(aircraft->stats[AIR_STATS_SPEED], AIR_STATS_SPEED)), size);
01705         Q_strcat(buffer, va(_("Fuel:\t%i/%i\n"), CL_AircraftMenuStatsValues(aircraft->fuel, AIR_STATS_FUELSIZE),
01706             CL_AircraftMenuStatsValues(aircraft->stats[AIR_STATS_FUELSIZE], AIR_STATS_FUELSIZE)), size);
01707         if (aircraft->status != AIR_IDLE) {
01708             const float distance = GetDistanceOnGlobe(aircraft->pos,
01709                     aircraft->route.point[aircraft->route.numPoints - 1]);
01710             Q_strcat(buffer, va(_("ETA:\t%sh\n"), CL_SecondConvert((float)SECONDS_PER_HOUR * distance / aircraft->stats[AIR_STATS_SPEED])), size);
01711         }
01712     }
01713     return buffer;
01714 }
01715 
01725 static const char *MAP_GetUFOText (char *buffer, size_t size, const aircraft_t* ufo)
01726 {
01727     Com_sprintf(buffer, size, "%s\n", UFO_AircraftToIDOnGeoscape(ufo));
01728     Q_strcat(buffer, va(_("Speed: %i km/h\n"), CL_AircraftMenuStatsValues(ufo->stats[AIR_STATS_SPEED], AIR_STATS_SPEED)), size);
01729     return buffer;
01730 }
01731 
01735 void MAP_UpdateGeoscapeDock (void)
01736 {
01737     const linkedList_t *list;
01738     int ufoIDX;
01739     char buf[512];
01740 
01741     UI_ExecuteConfunc("clean_geoscape_object");
01742 
01743     /* draw mission pics */
01744     for (list = ccs.missions; list; list = list->next) {
01745         const mission_t *ms = (mission_t *)list->data;
01746         if (!ms->onGeoscape)
01747             continue;
01748         UI_ExecuteConfunc("add_geoscape_object mission %i \"%s\" %s \"%s\"",
01749                 ms->idx, ms->location, MAP_GetMissionModel(ms), MAP_GetShortMissionText(buf, sizeof(buf), ms));
01750     }
01751 
01752     /* draws ufos */
01753     for (ufoIDX = 0; ufoIDX < ccs.numUFOs; ufoIDX++) {
01754         aircraft_t *ufo = &ccs.ufos[ufoIDX];
01755         if (!UFO_IsUFOSeenOnGeoscape(ufo))
01756             continue;
01757 
01758         UI_ExecuteConfunc("add_geoscape_object ufo %i %i %s \"%s\"",
01759                 ufoIDX, ufoIDX, ufo->model, MAP_GetUFOText(buf, sizeof(buf), ufo));
01760     }
01761 }
01762 
01772 static void MAP_DrawMapMarkers (const uiNode_t* node)
01773 {
01774     const linkedList_t *list;
01775     int x, y, i, baseIdx, installationIdx, aircraftIdx, idx;
01776     const char* font;
01777 
01778     const vec4_t white = {1.f, 1.f, 1.f, 0.7f};
01779     qboolean showXVI = qfalse;
01780     qboolean oneUFOVisible = qfalse;
01781     static char buffer[512] = "";
01782     int maxInterpolationPoints;
01783 
01784     assert(node);
01785 
01786     /* font color on geoscape */
01787     R_Color(node->color);
01788     /* default font */
01789     font = UI_GetFontFromNode(node);
01790 
01791     /* check if at least 1 UFO is visible */
01792     for (aircraftIdx = 0; aircraftIdx < ccs.numUFOs; aircraftIdx++) {
01793         const aircraft_t const *aircraft = &ccs.ufos[aircraftIdx];
01794         if (UFO_IsUFOSeenOnGeoscape(aircraft)) {
01795             oneUFOVisible = qtrue;
01796             break;
01797         }
01798     }
01799 
01800     /* draw mission pics */
01801     Cvar_Set("mn_mapdaytime", "");
01802     for (list = ccs.missions; list; list = list->next) {
01803         const mission_t *ms = (mission_t *)list->data;
01804         if (!ms->onGeoscape)
01805             continue;
01806         MAP_DrawMapOneMission(node, ms);
01807     }
01808 
01809     /* draw installations */
01810     for (installationIdx = 0; installationIdx < MAX_INSTALLATIONS; installationIdx++) {
01811         const installation_t *installation = INS_GetFoundedInstallationByIDX(installationIdx);
01812         if (!installation)
01813             continue;
01814         MAP_DrawMapOneInstallation(node, installation, oneUFOVisible, font);
01815     }
01816 
01817     /* draw bases */
01818     for (baseIdx = 0; baseIdx < MAX_BASES; baseIdx++) {
01819         base_t *base = B_GetFoundedBaseByIDX(baseIdx);
01820         aircraft_t *aircraft;
01821         if (!base)
01822             continue;
01823 
01824         MAP_DrawMapOneBase(node, base, oneUFOVisible, font);
01825 
01826         /* draw all aircraft of base */
01827         aircraft = NULL;
01828         while ((aircraft = AIR_GetNextFromBase(base, aircraft)) != NULL) {
01829             if (AIR_IsAircraftOnGeoscape(aircraft))
01830                 MAP_DrawMapOnePhalanxAircraft(node, aircraft, oneUFOVisible);
01831         }
01832     }
01833 
01834     /* draws ufos */
01835     for (aircraftIdx = 0; aircraftIdx < ccs.numUFOs; aircraftIdx++) {
01836         aircraft_t *aircraft = &ccs.ufos[aircraftIdx];
01837 #ifdef DEBUG
01838         /* in debug mode you execute set showufos 1 to see the ufos on geoscape */
01839         if (Cvar_GetInteger("debug_showufos")) {
01840             /* Draw ufo route */
01841             if (cl_3dmap->integer)
01842                 MAP_3DMapDrawLine(node, &aircraft->route);
01843             else
01844                 MAP_MapDrawLine(node, &aircraft->route);
01845         } else
01846 #endif
01847         if (!oneUFOVisible || !UFO_IsUFOSeenOnGeoscape(aircraft))
01848             continue;
01849 
01850         {
01851             const float angle = MAP_AngleOfPath(aircraft->pos, aircraft->route.point[aircraft->route.numPoints - 1], aircraft->direction, NULL);
01852 
01853             if (cl_3dmap->integer)
01854                 MAP_MapDrawEquidistantPoints(node, aircraft->pos, SELECT_CIRCLE_RADIUS, white);
01855             if (aircraft == ccs.selectedUFO) {
01856                 if (cl_3dmap->integer)
01857                     MAP_MapDrawEquidistantPoints(node, aircraft->pos, SELECT_CIRCLE_RADIUS, yellow);
01858                 else {
01859                     const image_t *image = geoscapeImages[GEOSCAPE_IMAGE_MISSION_SELECTED];
01860                     MAP_AllMapToScreen(node, aircraft->pos, &x, &y, NULL);
01861                     R_DrawImage(x - image->width / 2, y - image->height / 2, image);
01862                 }
01863             }
01864             MAP_Draw3DMarkerIfVisible(node, aircraft->pos, angle, aircraft->model, 0);
01865             VectorCopy(aircraft->pos, aircraft->oldDrawPos);
01866 
01868             if (RS_IsResearched_ptr(aircraft->tech)
01869              || Cvar_GetInteger("debug_showcrafthealth") >= 1)
01870                 MAP_DrawAircraftHealthBar(node, aircraft);
01871         }
01872     }
01873 
01874     if (ccs.gameTimeScale > 0)
01875         maxInterpolationPoints = floor(1.0f / (cls.frametime * (float)ccs.gameTimeScale));
01876     else
01877         maxInterpolationPoints = 0;
01878 
01879     /* draws projectiles */
01880     for (idx = 0; idx < ccs.numProjectiles; idx++) {
01881         aircraftProjectile_t *projectile = &ccs.projectiles[idx];
01882         vec3_t drawPos = {0, 0, 0};
01883 
01884         if (projectile->hasMoved) {
01885             projectile->hasMoved = qfalse;
01886             VectorCopy(projectile->pos[0], drawPos);
01887         } else {
01888             if (maxInterpolationPoints > 2 && projectile->numInterpolationPoints < maxInterpolationPoints) {
01889                 /* If a new point hasn't been given and there is at least 3 points need to be filled in then
01890                  * use linear interpolation to draw the points until a new projectile point is provided.
01891                  * The reason you need at least 3 points is that acceptable results can be achieved with 2 or less
01892                  * gaps in points so don't add the overhead of interpolation. */
01893                 const float xInterpolStep = (projectile->projectedPos[0][0] - projectile->pos[0][0]) / (float)maxInterpolationPoints;
01894                 projectile->numInterpolationPoints += 1;
01895                 drawPos[0] = projectile->pos[0][0] + (xInterpolStep * projectile->numInterpolationPoints);
01896                 LinearInterpolation(projectile->pos[0], projectile->projectedPos[0], drawPos[0], drawPos[1]);
01897             } else {
01898                 VectorCopy(projectile->pos[0], drawPos);
01899             }
01900         }
01901 
01902         if (projectile->bullets) {
01903             MAP_DrawBullets(node, drawPos);
01904         } else if (projectile->beam) {
01905             vec3_t start;
01906             vec3_t end;
01907 
01908             if (projectile->attackingAircraft)
01909                 VectorCopy(projectile->attackingAircraft->pos, start);
01910             else
01911                 VectorCopy(projectile->attackerPos, start);
01912 
01913             if (projectile->aimedAircraft)
01914                 VectorCopy(projectile->aimedAircraft->pos, end);
01915             else
01916                 VectorCopy(projectile->idleTarget, end);
01917 
01918             MAP_DrawBeam(node, start, end, projectile->aircraftItem->craftitem.beamColor);
01919         } else {
01920             MAP_Draw3DMarkerIfVisible(node, drawPos, projectile->angle, projectile->aircraftItem->model, 0);
01921         }
01922     }
01923 
01924     showXVI = CP_IsXVIResearched();
01925 
01926     /* Draw nation names */
01927     for (i = 0; i < ccs.numNations; i++) {
01928         if (MAP_AllMapToScreen(node, ccs.nations[i].pos, &x, &y, NULL))
01929             UI_DrawString("f_verysmall", ALIGN_UC, x , y, 0, 0, 0, _(ccs.nations[i].name), 0, 0, NULL, qfalse, 0);
01930         if (showXVI)
01931             Q_strcat(buffer, va(_("%s\t%i%%\n"), _(ccs.nations[i].name), ccs.nations[i].stats[0].xviInfection), sizeof(buffer));
01932     }
01933     if (showXVI)
01934         UI_RegisterText(TEXT_XVI, buffer);
01935     else
01936         UI_ResetData(TEXT_XVI);
01937 
01938     R_Color(NULL);
01939 }
01940 
01946 void MAP_DrawMap (const uiNode_t* node)
01947 {
01948     vec2_t pos;
01949 
01950     /* store these values in ccs struct to be able to handle this even in the input code */
01951     UI_GetNodeAbsPos(node, pos);
01952     Vector2Copy(pos, ccs.mapPos);
01953     Vector2Copy(node->size, ccs.mapSize);
01954     if (cl_3dmap->integer) {
01955         /* remove the left padding */
01956         ccs.mapSize[0] -= UI_MAPEXTRADATACONST(node).paddingRight;
01957     }
01958 
01959     /* Draw the map and markers */
01960     if (cl_3dmap->integer) {
01961         qboolean disableSolarRender = qfalse;
01964 #if 0
01965         if (ccs.zoom > cl_mapzoommax->value)
01966 #else
01967         if (ccs.zoom > 3.3)
01968 #endif
01969             disableSolarRender = qtrue;
01970 
01971         R_EnableRenderbuffer(qtrue);
01972 
01973         if (smoothRotation)
01974             MAP3D_SmoothRotate();
01975         R_Draw3DGlobe(ccs.mapPos[0], ccs.mapPos[1], ccs.mapSize[0], ccs.mapSize[1],
01976                 ccs.date.day, ccs.date.sec, ccs.angles, ccs.zoom, ccs.curCampaign->map, disableSolarRender,
01977                 cl_3dmapAmbient->value, MAP_IsNationOverlayActivated(), MAP_IsXVIOverlayActivated(),
01978                 MAP_IsRadarOverlayActivated(), r_xviTexture, r_radarTexture);
01979 
01980         MAP_DrawMapMarkers(node);
01981 
01982         R_DrawBloom();
01983         R_EnableRenderbuffer(qfalse);
01984     } else {
01985         /* the last q value for the 2d geoscape night overlay */
01986         static float lastQ = 0.0f;
01987 
01988         /* the sun is not always in the plane of the equator on earth - calculate the angle the sun is at */
01989         const float q = (ccs.date.day % DAYS_PER_YEAR + (float)(ccs.date.sec / (SECONDS_PER_HOUR * 6)) / 4) * 2 * M_PI / DAYS_PER_YEAR - M_PI;
01990         if (smoothRotation)
01991             MAP_SmoothTranslate();
01992         if (lastQ != q) {
01993             CP_CalcAndUploadDayAndNightTexture(q);
01994             lastQ = q;
01995         }
01996         R_DrawFlatGeoscape(ccs.mapPos[0], ccs.mapPos[1], ccs.mapSize[0], ccs.mapSize[1], (float) ccs.date.sec / SECONDS_PER_DAY,
01997             ccs.center[0], ccs.center[1], 0.5 / ccs.zoom, ccs.curCampaign->map, MAP_IsNationOverlayActivated(),
01998             MAP_IsXVIOverlayActivated(), MAP_IsRadarOverlayActivated(), r_dayandnightTexture, r_xviTexture, r_radarTexture);
01999         MAP_DrawMapMarkers(node);
02000     }
02001 
02002     /* display text */
02003     UI_ResetData(TEXT_STANDARD);
02004     switch (ccs.mapAction) {
02005     case MA_NEWBASE:
02006         UI_RegisterText(TEXT_STANDARD, _("Select the desired location of the new base on the map.\n"));
02007         return;
02008     case MA_NEWINSTALLATION:
02009         UI_RegisterText(TEXT_STANDARD, _("Select the desired location of the new installation on the map.\n"));
02010         return;
02011     case MA_BASEATTACK:
02012         if (ccs.selectedMission)
02013             break;
02014         UI_RegisterText(TEXT_STANDARD, _("Aliens are attacking our base at this very moment.\n"));
02015         return;
02016     case MA_INTERCEPT:
02017         if (ccs.selectedMission)
02018             break;
02019         UI_RegisterText(TEXT_STANDARD, _("Select ufo or mission on map\n"));
02020         return;
02021     case MA_UFORADAR:
02022         if (ccs.selectedMission)
02023             break;
02024         UI_RegisterText(TEXT_STANDARD, _("UFO in radar range\n"));
02025         return;
02026     case MA_NONE:
02027         break;
02028     }
02029 
02030     /* Nothing is displayed yet */
02031     if (ccs.selectedMission) {
02032         UI_RegisterText(TEXT_STANDARD, MAP_GetMissionText(textStandard, sizeof(textStandard), ccs.selectedMission));
02033     } else if (ccs.selectedAircraft) {
02034         const aircraft_t *aircraft = ccs.selectedAircraft;
02035         if (AIR_IsAircraftInBase(aircraft)) {
02036             UI_RegisterText(TEXT_STANDARD, NULL);
02037             MAP_ResetAction();
02038             return;
02039         }
02040         UI_RegisterText(TEXT_STANDARD, MAP_GetAircraftText(textStandard, sizeof(textStandard), ccs.selectedAircraft));
02041     } else if (ccs.selectedUFO) {
02042         UI_RegisterText(TEXT_STANDARD, MAP_GetUFOText(textStandard, sizeof(textStandard), ccs.selectedUFO));
02043     } else {
02044 #ifdef DEBUG
02045         if (debug_showInterest->integer) {
02046             static char t[64];
02047             Com_sprintf(t, lengthof(t), "Interest level: %i\n", ccs.overallInterest);
02048             UI_RegisterText(TEXT_STANDARD, t);
02049         } else
02050 #endif
02051         UI_RegisterText(TEXT_STANDARD, "");
02052     }
02053 }
02054 
02058 void MAP_ResetAction (void)
02059 {
02060     /* don't allow a reset when no base is set up */
02061     if (ccs.numBases)
02062         ccs.mapAction = MA_NONE;
02063 
02064     ccs.interceptAircraft = NULL;
02065     ccs.selectedMission = NULL;
02066     ccs.selectedAircraft = NULL;
02067     ccs.selectedUFO = NULL;
02068 
02069     if (!radarOverlayWasSet)
02070         MAP_DeactivateOverlay("radar");
02071 }
02072 
02076 void MAP_SelectUFO (aircraft_t* ufo)
02077 {
02078     MAP_ResetAction();
02079     ccs.selectedUFO = ufo;
02080 }
02081 
02085 void MAP_SelectAircraft (aircraft_t* aircraft)
02086 {
02087     MAP_ResetAction();
02088     ccs.selectedAircraft = aircraft;
02089 }
02090 
02094 void MAP_SelectMission (mission_t* mission)
02095 {
02096     if (!mission || mission == ccs.selectedMission)
02097         return;
02098     MAP_ResetAction();
02099     ccs.mapAction = MA_INTERCEPT;
02100     ccs.selectedMission = mission;
02101 }
02102 
02106 void MAP_NotifyMissionRemoved (const mission_t* mission)
02107 {
02108     /* Unselect the current selected mission if it's the same */
02109     if (ccs.selectedMission == mission)
02110         MAP_ResetAction();
02111 
02112     MAP_UpdateGeoscapeDock();
02113 }
02114 
02120 void MAP_NotifyUFORemoved (const aircraft_t* ufo, qboolean destroyed)
02121 {
02122     MAP_UpdateGeoscapeDock();
02123 
02124     if (!ccs.selectedUFO)
02125         return;
02126 
02127     /* Unselect the current selected ufo if its the same */
02128     if (ccs.selectedUFO == ufo)
02129         MAP_ResetAction();
02130     else if (destroyed && ccs.selectedUFO > ufo)
02131         ccs.selectedUFO--;
02132 }
02133 
02139 void MAP_NotifyAircraftRemoved (const aircraft_t* aircraft)
02140 {
02141     /* Unselect the current selected ufo if its the same */
02142     if (ccs.selectedAircraft == aircraft || ccs.interceptAircraft == aircraft)
02143         MAP_ResetAction();
02144 }
02145 
02154 nation_t* MAP_GetNation (const vec2_t pos)
02155 {
02156     int i;
02157     const byte* color = MAP_GetColor(pos, MAPTYPE_NATIONS);
02158 #ifdef PARANOID
02159     Com_DPrintf(DEBUG_CLIENT, "MAP_GetNation: color value for %.0f:%.0f is r:%i, g:%i, b: %i\n", pos[0], pos[1], color[0], color[1], color[2]);
02160 #endif
02161     for (i = 0; i < ccs.numNations; i++) {
02162         nation_t *nation = &ccs.nations[i];
02163         /* compare the first three color values with color value at pos */
02164         if (VectorCompare(nation->color, color))
02165             return nation;
02166     }
02167     Com_DPrintf(DEBUG_CLIENT, "MAP_GetNation: No nation found at %.0f:%.0f - color: %i:%i:%i\n", pos[0], pos[1], color[0], color[1], color[2]);
02168     return NULL;
02169 }
02170 
02171 
02180 const char* MAP_GetTerrainType (const byte* const color)
02181 {
02182     if (MapIsDesert(color))
02183         return "desert";
02184     else if (MapIsArctic(color))
02185         return "arctic";
02186     else if (MapIsWater(color))
02187         return "water";
02188     else if (MapIsMountain(color))
02189         return "mountain";
02190     else if (MapIsTropical(color))
02191         return "tropical";
02192     else if (MapIsCold(color))
02193         return "cold";
02194     else if (MapIsWasted(color))
02195         return "wasted";
02196     else
02197         return "grass";
02198 }
02199 
02207 static const char* MAP_GetCultureType (const byte* color)
02208 {
02209     if (MapIsWater(color))
02210         return "water";
02211     else if (MapIsEastern(color))
02212         return "eastern";
02213     else if (MapIsWestern(color))
02214         return "western";
02215     else if (MapIsOriental(color))
02216         return "oriental";
02217     else if (MapIsAfrican(color))
02218         return "african";
02219     else
02220         return "western";
02221 }
02222 
02230 static const char* MAP_GetPopulationType (const byte* color)
02231 {
02232     if (MapIsWater(color))
02233         return "water";
02234     else if (MapIsUrban(color))
02235         return "urban";
02236     else if (MapIsSuburban(color))
02237         return "suburban";
02238     else if (MapIsVillage(color))
02239         return "village";
02240     else if (MapIsRural(color))
02241         return "rural";
02242     else if (MapIsNopopulation(color))
02243         return "nopopulation";
02244     else
02245         return "nopopulation";
02246 }
02247 
02254 static inline const char* MAP_GetTerrainTypeByPos (const vec2_t pos)
02255 {
02256     const byte* color = MAP_GetColor(pos, MAPTYPE_TERRAIN);
02257     return MAP_GetTerrainType(color);
02258 }
02259 
02266 static inline const char* MAP_GetCultureTypeByPos (const vec2_t pos)
02267 {
02268     const byte* color = MAP_GetColor(pos, MAPTYPE_CULTURE);
02269     return MAP_GetCultureType(color);
02270 }
02271 
02278 static inline const char* MAP_GetPopulationTypeByPos (const vec2_t pos)
02279 {
02280     const byte* color = MAP_GetColor(pos, MAPTYPE_POPULATION);
02281     return MAP_GetPopulationType(color);
02282 }
02283 
02290 int MAP_GetCivilianNumberByPosition (const vec2_t pos)
02291 {
02292     const byte* color = MAP_GetColor(pos, MAPTYPE_POPULATION);
02293 
02294     if (MapIsWater(color))
02295         Com_Error(ERR_DROP, "MAP_GetPopulationType: Trying to get number of civilian in a position on water");
02296     else if (MapIsUrban(color))
02297         return 10;
02298     else if (MapIsSuburban(color))
02299         return 8;
02300     else if (MapIsVillage(color))
02301         return 6;
02302     else if (MapIsRural(color))
02303         return 4;
02304     else if (MapIsNopopulation(color))
02305         return 2;
02306     else
02307         return 0;
02308 }
02309 
02316 void MAP_PrintParameterStringByPos (const vec2_t pos)
02317 {
02318     const char *terrainType = MAP_GetTerrainTypeByPos(pos);
02319     const char *cultureType = MAP_GetCultureTypeByPos(pos);
02320     const char *populationType = MAP_GetPopulationTypeByPos(pos);
02321 
02322     Com_Printf ("      (Terrain: %s, Culture: %s, Population: %s)\n", terrainType, cultureType, populationType);
02323 }
02324 
02329 void MAP_CheckPositionBoundaries (float *pos)
02330 {
02331     while (pos[0] > 180.0)
02332         pos[0] -= 360.0;
02333     while (pos[0] < -180.0)
02334         pos[0] += 360.0;
02335     while (pos[1] > 90.0)
02336         pos[1] -= 180.0;
02337     while (pos[1] < -90.0)
02338         pos[1] += 180.0;
02339 }
02340 
02346 qboolean MAP_IsNight (const vec2_t pos)
02347 {
02348     float p, q, a, root, x;
02349 
02350     /* set p to hours (we don't use ccs.day here because we need a float value) */
02351     p = (float) ccs.date.sec / SECONDS_PER_DAY;
02352     /* convert current day to angle (-pi on 1st january, pi on 31 december) */
02353     q = (ccs.date.day + p) * (2 * M_PI / DAYS_PER_YEAR_AVG) - M_PI;
02354     p = (0.5 + pos[0] / 360 - p) * (2 * M_PI) - q;
02355     a = -sin(pos[1] * torad);
02356     root = sqrt(1.0 - a * a);
02357     x = sin(p) * root * sin(q) - (a * SIN_ALPHA + cos(p) * root * COS_ALPHA) * cos(q);
02358     return (x > 0);
02359 }
02360 
02374 byte *MAP_GetColor (const vec2_t pos, mapType_t type)
02375 {
02376     int x, y;
02377     int width, height;
02378     byte *mask;
02379 
02380     switch (type) {
02381     case MAPTYPE_TERRAIN:
02382         mask = terrainPic;
02383         width = terrainWidth;
02384         height = terrainHeight;
02385         break;
02386     case MAPTYPE_CULTURE:
02387         mask = culturePic;
02388         width = cultureWidth;
02389         height = cultureHeight;
02390         break;
02391     case MAPTYPE_POPULATION:
02392         mask = populationPic;
02393         width = populationWidth;
02394         height = populationHeight;
02395         break;
02396     case MAPTYPE_NATIONS:
02397         mask = nationsPic;
02398         width = nationsWidth;
02399         height = nationsHeight;
02400         break;
02401     default:
02402         Com_Error(ERR_DROP, "Unknown maptype %i\n", type);
02403     }
02404 
02406     assert(pos[0] >= -180);
02407     assert(pos[0] <= 180);
02408     assert(pos[1] >= -90);
02409     assert(pos[1] <= 90);
02410 
02411     /* get coordinates */
02412     x = (180 - pos[0]) / 360 * width;
02413     x--; /* we start from 0 */
02414     y = (90 - pos[1]) / 180 * height;
02415     y--; /* we start from 0 */
02416     if (x < 0)
02417         x = 0;
02418     if (y < 0)
02419         y = 0;
02420 
02421     /* 4 => RGBA */
02422     /* terrainWidth is the width of the image */
02423     /* this calculation returns the pixel in col x and in row y */
02424     assert(4 * (x + y * width) < width * height * 4);
02425     return mask + 4 * (x + y * width);
02426 }
02427 
02431 static const float MIN_DIST_BASE = 4.0f;
02432 
02437 base_t* MAP_PositionCloseToBase (const vec2_t pos)
02438 {
02439     int baseIdx;
02440 
02441     for (baseIdx = 0; baseIdx < MAX_BASES; baseIdx++) {
02442         base_t *base = B_GetFoundedBaseByIDX(baseIdx);
02443         if (!base)
02444             continue;
02445 
02446         if (GetDistanceOnGlobe(pos, base->pos) < MIN_DIST_BASE) {
02447             return base;
02448         }
02449     }
02450 
02451     return NULL;
02452 }
02453 
02464 qboolean MAP_PositionFitsTCPNTypes (const vec2_t pos, const linkedList_t* terrainTypes, const linkedList_t* cultureTypes, const linkedList_t* populationTypes, const linkedList_t* nations)
02465 {
02466     const char *terrainType = MAP_GetTerrainTypeByPos(pos);
02467     const char *cultureType = MAP_GetCultureTypeByPos(pos);
02468     const char *populationType = MAP_GetPopulationTypeByPos(pos);
02469 
02470     if (MapIsWater(MAP_GetColor(pos, MAPTYPE_TERRAIN)))
02471         return qfalse;
02472 
02473     if (!terrainTypes || LIST_ContainsString(terrainTypes, terrainType)) {
02474         if (!cultureTypes || LIST_ContainsString(cultureTypes, cultureType)) {
02475             if (!populationTypes || LIST_ContainsString(populationTypes, populationType)) {
02476                 const nation_t *nationAtPos = MAP_GetNation(pos);
02477                 if (!nations)
02478                     return qtrue;
02479                 if (nationAtPos && (!nations || LIST_ContainsString(nations, nationAtPos->id))) {
02480                     return qtrue;
02481                 }
02482             }
02483         }
02484     }
02485 
02486     return qfalse;
02487 }
02488 
02489 void MAP_Init (void)
02490 {
02491     /* load terrain mask */
02492     if (terrainPic) {
02493         Mem_Free(terrainPic);
02494         terrainPic = NULL;
02495     }
02496     R_LoadImage(va("pics/geoscape/%s_terrain", ccs.curCampaign->map), &terrainPic, &terrainWidth, &terrainHeight);
02497     if (!terrainPic || !terrainWidth || !terrainHeight)
02498         Com_Error(ERR_DROP, "Couldn't load map mask %s_terrain in pics/geoscape", ccs.curCampaign->map);
02499 
02500     /* load culture mask */
02501     if (culturePic) {
02502         Mem_Free(culturePic);
02503         culturePic = NULL;
02504     }
02505     R_LoadImage(va("pics/geoscape/%s_culture", ccs.curCampaign->map), &culturePic, &cultureWidth, &cultureHeight);
02506     if (!culturePic || !cultureWidth || !cultureHeight)
02507         Com_Error(ERR_DROP, "Couldn't load map mask %s_culture in pics/geoscape", ccs.curCampaign->map);
02508 
02509     /* load population mask */
02510     if (populationPic) {
02511         Mem_Free(populationPic);
02512         populationPic = NULL;
02513     }
02514     R_LoadImage(va("pics/geoscape/%s_population", ccs.curCampaign->map), &populationPic, &populationWidth, &populationHeight);
02515     if (!populationPic || !populationWidth || !populationHeight)
02516         Com_Error(ERR_DROP, "Couldn't load map mask %s_population in pics/geoscape", ccs.curCampaign->map);
02517 
02518     /* load nations mask */
02519     if (nationsPic) {
02520         Mem_Free(nationsPic);
02521         nationsPic = NULL;
02522     }
02523     R_LoadImage(va("pics/geoscape/%s_nations", ccs.curCampaign->map), &nationsPic, &nationsWidth, &nationsHeight);
02524     if (!nationsPic || !nationsWidth || !nationsHeight)
02525         Com_Error(ERR_DROP, "Couldn't load map mask %s_nations in pics/geoscape", ccs.curCampaign->map);
02526 
02527     MAP_ResetAction();
02528     MAP_UpdateGeoscapeDock();
02529 }
02530 
02534 void MAP_NotifyUFODisappear (const aircraft_t* ufo)
02535 {
02536     /* Unselect the current selected ufo if its the same */
02537     if (ccs.selectedUFO == ufo)
02538         MAP_ResetAction();
02539 
02540     MAP_UpdateGeoscapeDock();
02541 }
02542 
02546 void MAP_Zoom_f (void)
02547 {
02548     const char *cmd;
02549     const float zoomAmount = 50.0f;
02550 
02551     if (Cmd_Argc() != 2) {
02552         Com_Printf("Usage: %s <in|out>\n", Cmd_Argv(0));
02553         return;
02554     }
02555 
02556     cmd = Cmd_Argv(1);
02557     switch (cmd[0]) {
02558     case 'i':
02559         smoothFinalZoom = ccs.zoom * pow(0.995, -zoomAmount);
02560         break;
02561     case 'o':
02562         smoothFinalZoom = ccs.zoom * pow(0.995, zoomAmount);
02563         break;
02564     default:
02565         Com_Printf("MAP_Zoom_f: Invalid parameter: %s\n", cmd);
02566         return;
02567     }
02568 
02569     if (smoothFinalZoom < cl_mapzoommin->value)
02570         smoothFinalZoom = cl_mapzoommin->value;
02571     else if (smoothFinalZoom > cl_mapzoommax->value)
02572         smoothFinalZoom = cl_mapzoommax->value;
02573 
02574     if (!cl_3dmap->integer) {
02575         ccs.zoom = smoothFinalZoom;
02576         if (ccs.center[1] < 0.5 / ccs.zoom)
02577             ccs.center[1] = 0.5 / ccs.zoom;
02578         if (ccs.center[1] > 1.0 - 0.5 / ccs.zoom)
02579             ccs.center[1] = 1.0 - 0.5 / ccs.zoom;
02580     } else {
02581         VectorCopy(ccs.angles, smoothFinalGlobeAngle);
02582         smoothDeltaLength = 0;
02583         smoothRotation = qtrue;
02584         smoothDeltaZoom = fabs(smoothFinalZoom - ccs.zoom);
02585     }
02586 }
02587 
02591 void MAP_Scroll_f (void)
02592 {
02593     const char *cmd;
02594     float scrollX = 0.0f, scrollY = 0.0f;
02595     const float scrollAmount = 80.0f;
02596 
02597     if (Cmd_Argc() != 2) {
02598         Com_Printf("Usage: %s <up|down|left|right>\n", Cmd_Argv(0));
02599         return;
02600     }
02601 
02602     cmd = Cmd_Argv(1);
02603     switch (cmd[0]) {
02604     case 'l':
02605         scrollX = scrollAmount;
02606         break;
02607     case 'r':
02608         scrollX = -scrollAmount;
02609         break;
02610     case 'u':
02611         scrollY = scrollAmount;
02612         break;
02613     case 'd':
02614         scrollY = -scrollAmount;
02615         break;
02616     default:
02617         Com_Printf("MAP_Scroll_f: Invalid parameter\n");
02618         return;
02619     }
02620     if (cl_3dmap->integer) {
02621         /* case 3D geoscape */
02622         vec3_t diff;
02623 
02624         VectorCopy(ccs.angles, smoothFinalGlobeAngle);
02625 
02626         /* rotate a model */
02627         smoothFinalGlobeAngle[PITCH] += ROTATE_SPEED * (scrollX) / ccs.zoom;
02628         smoothFinalGlobeAngle[YAW] -= ROTATE_SPEED * (scrollY) / ccs.zoom;
02629 
02630         while (smoothFinalGlobeAngle[YAW] < -180.0) {
02631             smoothFinalGlobeAngle[YAW] = -180.0;
02632         }
02633         while (smoothFinalGlobeAngle[YAW] > 0.0) {
02634             smoothFinalGlobeAngle[YAW] = 0.0;
02635         }
02636 
02637         while (smoothFinalGlobeAngle[PITCH] > 180.0) {
02638             smoothFinalGlobeAngle[PITCH] -= 360.0;
02639             ccs.angles[PITCH] -= 360.0;
02640         }
02641         while (smoothFinalGlobeAngle[PITCH] < -180.0) {
02642             smoothFinalGlobeAngle[PITCH] += 360.0;
02643             ccs.angles[PITCH] += 360.0;
02644         }
02645         VectorSubtract(smoothFinalGlobeAngle, ccs.angles, diff);
02646         smoothDeltaLength = VectorLength(diff);
02647 
02648         smoothFinalZoom = ccs.zoom;
02649         smoothDeltaZoom = 0.0f;
02650         smoothRotation = qtrue;
02651     } else {
02652         int i;
02653         /* shift the map */
02654         ccs.center[0] -= (float) (scrollX) / (ccs.mapSize[0] * ccs.zoom);
02655         ccs.center[1] -= (float) (scrollY) / (ccs.mapSize[1] * ccs.zoom);
02656         for (i = 0; i < 2; i++) {
02657             while (ccs.center[i] < 0.0)
02658                 ccs.center[i] += 1.0;
02659             while (ccs.center[i] > 1.0)
02660                 ccs.center[i] -= 1.0;
02661         }
02662         if (ccs.center[1] < 0.5 / ccs.zoom)
02663             ccs.center[1] = 0.5 / ccs.zoom;
02664         if (ccs.center[1] > 1.0 - 0.5 / ccs.zoom)
02665             ccs.center[1] = 1.0 - 0.5 / ccs.zoom;
02666     }
02667 }
02668 
02673 void MAP_SetOverlay (const char *overlayID)
02674 {
02675     if (!strcmp(overlayID, "nations")) {
02676         if (MAP_IsNationOverlayActivated())
02677             cl_geoscape_overlay->integer ^= OVERLAY_NATION;
02678         else
02679             cl_geoscape_overlay->integer |= OVERLAY_NATION;
02680     }
02681 
02682     /* do nothing while the first base/installation is not build */
02683     if (ccs.numBases + ccs.numInstallations == 0)
02684         return;
02685 
02686     if (!strcmp(overlayID, "xvi")) {
02687         if (cl_geoscape_overlay->integer & OVERLAY_XVI)
02688             cl_geoscape_overlay->integer ^= OVERLAY_XVI;
02689         else
02690             cl_geoscape_overlay->integer |= OVERLAY_XVI;
02691     } else if (!strcmp(overlayID, "radar")) {
02692         if (MAP_IsRadarOverlayActivated())
02693             cl_geoscape_overlay->integer ^= OVERLAY_RADAR;
02694         else {
02695             cl_geoscape_overlay->integer |= OVERLAY_RADAR;
02696             RADAR_UpdateWholeRadarOverlay();
02697         }
02698     }
02699 }
02700 
02704 static void MAP_SetOverlay_f (void)
02705 {
02706     const char *arg;
02707 
02708     if (Cmd_Argc() != 2) {
02709         Com_Printf("Usage: %s <nations|xvi|radar>\n", Cmd_Argv(0));
02710         return;
02711     }
02712 
02713     arg = Cmd_Argv(1);
02714     MAP_SetOverlay(arg);
02715 
02716     /* save last decision player took on radar display, in order to be able to restore it later */
02717     if (!strcmp(arg, "radar"))
02718         radarOverlayWasSet = MAP_IsRadarOverlayActivated();
02719 }
02720 
02725 void MAP_DeactivateOverlay (const char *overlayID)
02726 {
02727     if (!strcmp(overlayID, "nations")) {
02728         if (MAP_IsNationOverlayActivated())
02729             MAP_SetOverlay("nations");
02730         else
02731             return;
02732     }
02733 
02734     if (!strcmp(overlayID, "xvi")) {
02735         if (MAP_IsXVIOverlayActivated())
02736             MAP_SetOverlay("xvi");
02737         else
02738             return;
02739     } else if (!strcmp(overlayID, "radar")) {
02740         if (MAP_IsRadarOverlayActivated())
02741             MAP_SetOverlay("radar");
02742         else
02743             return;
02744     }
02745 }
02746 
02750 static void MAP_DeactivateOverlay_f (void)
02751 {
02752     const char *arg;
02753 
02754     if (Cmd_Argc() != 2) {
02755         Com_Printf("Usage: %s <nations|xvi|radar>\n", Cmd_Argv(0));
02756         return;
02757     }
02758 
02759     arg = Cmd_Argv(1);
02760     MAP_DeactivateOverlay(arg);
02761 }
02762 
02763 qboolean MAP_IsRadarOverlayActivated (void)
02764 {
02765     return cl_geoscape_overlay->integer & OVERLAY_RADAR;
02766 }
02767 
02768 qboolean MAP_IsNationOverlayActivated (void)
02769 {
02770     return cl_geoscape_overlay->integer & OVERLAY_NATION;
02771 }
02772 
02773 qboolean MAP_IsXVIOverlayActivated (void)
02774 {
02775     return cl_geoscape_overlay->integer & OVERLAY_XVI;
02776 }
02777 
02781 void MAP_InitStartup (void)
02782 {
02783     int i;
02784 
02785     Cmd_AddCommand("multi_select_click", MAP_MultiSelectExecuteAction_f, NULL);
02786     Cmd_AddCommand("map_overlay", MAP_SetOverlay_f, "Set the geoscape overlay");
02787     Cmd_AddCommand("map_deactivateoverlay", MAP_DeactivateOverlay_f, "Deactivate overlay");
02788     Cmd_AddCommand("map_selectobject", MAP_SelectObject_f, "Select an object an center on it");
02789     Cmd_AddCommand("mn_mapaction_reset", MAP_ResetAction, NULL);
02790 
02791     cl_geoscape_overlay = Cvar_Get("cl_geoscape_overlay", "0", 0, "Geoscape overlays - Bitmask");
02792     cl_3dmap = Cvar_Get("cl_3dmap", "1", CVAR_ARCHIVE, "3D geoscape or flat geoscape");
02793     cl_3dmapAmbient = Cvar_Get("cl_3dmapAmbient", "0", CVAR_ARCHIVE, "3D geoscape ambient lighting factor");
02794     cl_mapzoommax = Cvar_Get("cl_mapzoommax", "6.0", CVAR_ARCHIVE, "Maximum geoscape zooming value");
02795     cl_mapzoommin = Cvar_Get("cl_mapzoommin", "1.0", CVAR_ARCHIVE, "Minimum geoscape zooming value");
02796 #ifdef DEBUG
02797     debug_showInterest = Cvar_Get("debug_showinterest", "0", CVAR_DEVELOPER, "Shows the global interest value on geoscape");
02798 #endif
02799 
02800     for (i = 0; i < GEOSCAPE_IMAGE_MAX; i++) {
02801         geoscapeImages[i] = R_FindImage(geoscapeImageNames[i], it_pic);
02802         if (geoscapeImages[i] == r_noTexture)
02803             Com_Error(ERR_DROP, "Could not find image: %s", geoscapeImageNames[i]);
02804     }
02805 }

Generated by  doxygen 1.6.2