1 /**
2 * @file
3 */
4
5 /*
6 Copyright (C) 2002-2013 UFO: Alien Invasion.
7
8 This program is free software; you can redistribute it and/or
9 modify it under the terms of the GNU General Public License
10 as published by the Free Software Foundation; either version 2
11 of the License, or (at your option) any later version.
12
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
16
17 See the GNU General Public License for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, write to the Free Software
21 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
22
23 */
24
25 #include "../ui_nodes.h"
26 #include "../ui_input.h"
27 #include "../ui_parse.h"
28 #include "../ui_behaviour.h"
29 #include "../ui_actions.h"
30 #include "../ui_render.h"
31 #include "ui_node_geoscape.h"
32 #include "../../renderer/r_framebuffer.h"
33 #include "../../renderer/r_geoscape.h"
34 #include "../../cl_shared.h"
35 #include "../../cgame/cl_game.h"
36 #include "../../input/cl_input.h"
37 #include "../../input/cl_keys.h"
38
39 #define EXTRADATA_TYPE mapExtraData_t
40 #define EXTRADATA(node) UI_EXTRADATA(node, EXTRADATA_TYPE)
41 #define EXTRADATACONST(node) UI_EXTRADATACONST(node, EXTRADATA_TYPE)
42
43 /**
44 * Status of the node
45 */
46 enum mapDragMode_t {
47 /**
48 * No interaction
49 */
50 MODE_NULL,
51 /**
52 * Mouse captured to move the 2D geoscape
53 */
54 MODE_SHIFT2DMAP,
55 /**
56 * Mouse captured to move the 3D geoscape
57 */
58 MODE_SHIFT3DMAP,
59 /**
60 * Mouse captured to zoom on the geoscape
61 */
62 MODE_ZOOMMAP
63 };
64
65 /**
66 * Old X-location of the mouse, when the node is captured.
67 * It is related to a captured node (only one at a time), that why it is global.
68 */
69 static int oldMousePosX = 0;
70
71 /**
72 * Old y-location of the mouse, when the node is captured.
73 * It is related to a captured node (only one at a time), that why it is global.
74 */
75 static int oldMousePosY = 0;
76
77 /**
78 * Status of the node.
79 * It is related to a captured node (only one at a time), that why it is global.
80 */
81 static mapDragMode_t mode = MODE_NULL;
82 static const float ROTATE_SPEED = 0.5f;
83 static const float GLOBE_ROTATE = -90.0f;
84 static const float SMOOTHING_STEP_2D = 0.02f;
85 static const float SMOOTHACCELERATION = 0.06f; /**< the acceleration to use during a smooth motion (This affects the speed of the smooth motion) */
86
87 static cvar_t* cl_3dmap; /**< 3D geoscape or flat geoscape */
88 static cvar_t* cl_3dmapAmbient;
89 static cvar_t* cl_mapzoommax;
90 static cvar_t* cl_mapzoommin;
91 static cvar_t* cl_geoscape_overlay;
92
93 // FIXME: don't make this static
94 static uiNode_t* geoscapeNode;
95
96 /** this is the mask that is used to display day/night on (2d-)geoscape */
97 image_t* r_dayandnightTexture;
98
99 image_t* r_radarTexture; /**< radar texture */
100 image_t* r_xviTexture; /**< XVI alpha mask texture */
101
102 #define DAWN 0.03
103
104 /**
105 * @brief smooth rotation of the 3D geoscape
106 * @note updates slowly values of @c angles and @c zoom so that its value goes to @c smoothFinalGlobeAngle
107 */
smoothRotate(uiNode_t * node)108 void uiGeoscapeNode::smoothRotate (uiNode_t* node)
109 {
110 vec3_t diff;
111 const float diffZoom = UI_MAPEXTRADATACONST(node).smoothFinalZoom - UI_MAPEXTRADATACONST(node).zoom;
112
113 VectorSubtract(UI_MAPEXTRADATACONST(node).smoothFinalGlobeAngle, UI_MAPEXTRADATACONST(node).angles, diff);
114
115 if (UI_MAPEXTRADATACONST(node).smoothDeltaLength > UI_MAPEXTRADATACONST(node).smoothDeltaZoom) {
116 /* when we rotate (and zoom) */
117 const float diffAngle = VectorLength(diff);
118 const float epsilon = 0.1f;
119 if (diffAngle > epsilon) {
120 float rotationSpeed;
121 /* Append the old speed to the new speed if this is the first half of a new rotation, but never exceed the max speed.
122 * This allows the globe to rotate at maximum speed when the button is held down. */
123 rotationSpeed = sin(3.05f * diffAngle / UI_MAPEXTRADATACONST(node).smoothDeltaLength) * diffAngle;
124 if (diffAngle / UI_MAPEXTRADATACONST(node).smoothDeltaLength > 0.5)
125 rotationSpeed = std::min(diffAngle, UI_MAPEXTRADATACONST(node).curRotationSpeed + rotationSpeed * 0.5f);
126
127 UI_MAPEXTRADATA(node).curRotationSpeed = rotationSpeed;
128 VectorScale(diff, SMOOTHACCELERATION / diffAngle * rotationSpeed, diff);
129 VectorAdd(UI_MAPEXTRADATACONST(node).angles, diff, UI_MAPEXTRADATA(node).angles);
130 UI_MAPEXTRADATA(node).zoom = UI_MAPEXTRADATACONST(node).zoom + SMOOTHACCELERATION * diffZoom / diffAngle * rotationSpeed;
131 return;
132 }
133 } else {
134 const float epsilonZoom = 0.01f;
135 /* when we zoom only */
136 if (fabsf(diffZoom) > epsilonZoom) {
137 float speed;
138 /* 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.
139 * This allows the globe to zoom at maximum speed when the button is held down. */
140 if (fabsf(diffZoom) / UI_MAPEXTRADATACONST(node).smoothDeltaZoom > 0.5f) {
141 const float maxSpeed = SMOOTHACCELERATION * 2.0f;
142 const float newSpeed = UI_MAPEXTRADATACONST(node).curZoomSpeed + sin(3.05 * (fabs(diffZoom) / UI_MAPEXTRADATACONST(node).smoothDeltaZoom)) * SMOOTHACCELERATION;
143 speed = std::min(maxSpeed, newSpeed);
144 } else {
145 speed = sin(3.05 * (fabs(diffZoom) / UI_MAPEXTRADATACONST(node).smoothDeltaZoom)) * SMOOTHACCELERATION * 2.0;
146 }
147 UI_MAPEXTRADATA(node).curZoomSpeed = speed;
148 UI_MAPEXTRADATA(node).zoom = UI_MAPEXTRADATACONST(node).zoom + diffZoom * speed;
149 return;
150 }
151 }
152
153 /* if we reach this point, that means that movement is over */
154 VectorCopy(UI_MAPEXTRADATACONST(node).smoothFinalGlobeAngle, UI_MAPEXTRADATA(node).angles);
155 UI_MAPEXTRADATA(node).smoothRotation = false;
156 UI_MAPEXTRADATA(node).zoom = UI_MAPEXTRADATACONST(node).smoothFinalZoom;
157 }
158
159 /**
160 * @brief smooth translation of the 2D geoscape
161 * @note updates slowly values of @c center so that its value goes to @c smoothFinal2DGeoscapeCenter
162 * @note updates slowly values of @c zoom so that its value goes to @c ZOOM_LIMIT
163 */
smoothTranslate(uiNode_t * node)164 void uiGeoscapeNode::smoothTranslate (uiNode_t* node)
165 {
166 const float dist1 = UI_MAPEXTRADATACONST(node).smoothFinal2DGeoscapeCenter[0] - UI_MAPEXTRADATACONST(node).center[0];
167 const float dist2 = UI_MAPEXTRADATACONST(node).smoothFinal2DGeoscapeCenter[1] - UI_MAPEXTRADATACONST(node).center[1];
168 const float length = sqrt(dist1 * dist1 + dist2 * dist2);
169
170 if (length < SMOOTHING_STEP_2D) {
171 UI_MAPEXTRADATA(node).center[0] = UI_MAPEXTRADATACONST(node).smoothFinal2DGeoscapeCenter[0];
172 UI_MAPEXTRADATA(node).center[1] = UI_MAPEXTRADATACONST(node).smoothFinal2DGeoscapeCenter[1];
173 UI_MAPEXTRADATA(node).zoom = UI_MAPEXTRADATACONST(node).smoothFinalZoom;
174 UI_MAPEXTRADATA(node).smoothRotation = false;
175 } else {
176 const float diffZoom = UI_MAPEXTRADATACONST(node).smoothFinalZoom - UI_MAPEXTRADATACONST(node).zoom;
177 UI_MAPEXTRADATA(node).center[0] = UI_MAPEXTRADATACONST(node).center[0] + SMOOTHING_STEP_2D * dist1 / length;
178 UI_MAPEXTRADATA(node).center[1] = UI_MAPEXTRADATACONST(node).center[1] + SMOOTHING_STEP_2D * dist2 / length;
179 UI_MAPEXTRADATA(node).zoom = UI_MAPEXTRADATACONST(node).zoom + SMOOTHING_STEP_2D * diffZoom;
180 }
181 }
182
183 /**
184 * @brief Applies alpha values to the night overlay image for 2d geoscape
185 * @param[in] node The current menuNode we have clicked on (3dmap or map)
186 * @param[in] q The angle the sun is standing against the equator on earth
187 */
calcAndUploadDayAndNightTexture(uiNode_t * node,float q)188 void uiGeoscapeNode::calcAndUploadDayAndNightTexture (uiNode_t* node, float q)
189 {
190 int x, y;
191 const float dphi = (float) 2 * M_PI / DAN_WIDTH;
192 const float da = M_PI / 2 * (HIGH_LAT - LOW_LAT) / DAN_HEIGHT;
193 const float sin_q = sin(q);
194 const float cos_q = cos(q);
195 float sin_phi[DAN_WIDTH], cos_phi[DAN_WIDTH];
196 byte* px;
197
198 for (x = 0; x < DAN_WIDTH; x++) {
199 const float phi = x * dphi - q;
200 sin_phi[x] = sin(phi);
201 cos_phi[x] = cos(phi);
202 }
203
204 /* calculate */
205 px = UI_MAPEXTRADATA(node).r_dayandnightAlpha;
206 for (y = 0; y < DAN_HEIGHT; y++) {
207 const float a = sin(M_PI / 2 * HIGH_LAT - y * da);
208 const float root = sqrt(1 - a * a);
209 for (x = 0; x < DAN_WIDTH; x++) {
210 const float pos = sin_phi[x] * root * sin_q - (a * SIN_ALPHA + cos_phi[x] * root * COS_ALPHA) * cos_q;
211
212 if (pos >= DAWN)
213 *px++ = 255;
214 else if (pos <= -DAWN)
215 *px++ = 0;
216 else
217 *px++ = (byte) (128.0 * (pos / DAWN + 1));
218 }
219 }
220
221 /* upload alpha map into the r_dayandnighttexture */
222 R_UploadAlpha(r_dayandnightTexture, UI_MAPEXTRADATA(node).r_dayandnightAlpha);
223 }
224
draw(uiNode_t * node)225 void uiGeoscapeNode::draw (uiNode_t* node)
226 {
227 vec2_t screenPos;
228
229 geoscapeNode = node;
230 UI_MAPEXTRADATA(node).flatgeoscape = cl_3dmap->integer == 0;
231 UI_MAPEXTRADATA(node).overlayMask = cl_geoscape_overlay->integer;
232 UI_MAPEXTRADATA(node).ambientLightFactor = cl_3dmapAmbient->value;
233 UI_MAPEXTRADATA(node).mapzoommin = cl_mapzoommin->value;
234 UI_MAPEXTRADATA(node).mapzoommax = cl_mapzoommax->value;
235
236 UI_GetNodeAbsPos(node, UI_MAPEXTRADATA(node).mapPos);
237 Vector2Copy(node->box.size, UI_MAPEXTRADATA(node).mapSize);
238 if (!UI_MAPEXTRADATACONST(node).flatgeoscape) {
239 /* remove the left padding */
240 UI_MAPEXTRADATA(node).mapSize[0] -= UI_MAPEXTRADATACONST(node).paddingRight;
241 }
242
243 /* Draw geoscape */
244 UI_GetNodeScreenPos(node, screenPos);
245 UI_PushClipRect(screenPos[0], screenPos[1], node->box.size[0], node->box.size[1]);
246
247 if (UI_MAPEXTRADATACONST(node).smoothRotation) {
248 if (UI_MAPEXTRADATACONST(node).flatgeoscape)
249 smoothTranslate(node);
250 else
251 smoothRotate(node);
252 }
253
254 geoscapeData_t& data = *UI_MAPEXTRADATA(node).geoscapeData;
255 data.geoscapeNode = node;
256 GAME_DrawMap(&data);
257 if (!data.active)
258 return;
259
260 const char* map = data.map;
261 date_t& date = data.date;
262
263 /* Draw the map and markers */
264 if (UI_MAPEXTRADATACONST(node).flatgeoscape) {
265 /* the last q value for the 2d geoscape night overlay */
266 static float lastQ = 0.0f;
267
268 /* the sun is not always in the plane of the equator on earth - calculate the angle the sun is at */
269 const float q = (date.day % DAYS_PER_YEAR + (float)(date.sec / (SECONDS_PER_HOUR * 6)) / 4) * 2 * M_PI / DAYS_PER_YEAR - M_PI;
270 if (lastQ != q) {
271 calcAndUploadDayAndNightTexture(node, q);
272 lastQ = q;
273 }
274 R_DrawFlatGeoscape(UI_MAPEXTRADATACONST(node).mapPos, UI_MAPEXTRADATACONST(node).mapSize, (float) date.sec / SECONDS_PER_DAY,
275 UI_MAPEXTRADATACONST(node).center[0], UI_MAPEXTRADATACONST(node).center[1], 0.5 / UI_MAPEXTRADATACONST(node).zoom, map,
276 data.nationOverlay, data.xviOverlay, data.radarOverlay, r_dayandnightTexture, r_xviTexture, r_radarTexture);
277
278 GAME_DrawMapMarkers(node);
279 } else {
280 bool disableSolarRender = false;
281 if (UI_MAPEXTRADATACONST(node).zoom > 3.3)
282 disableSolarRender = true;
283
284 R_EnableRenderbuffer(true);
285
286 R_Draw3DGlobe(UI_MAPEXTRADATACONST(node).mapPos, UI_MAPEXTRADATACONST(node).mapSize, date.day, date.sec,
287 UI_MAPEXTRADATACONST(node).angles, UI_MAPEXTRADATACONST(node).zoom, map, disableSolarRender,
288 UI_MAPEXTRADATACONST(node).ambientLightFactor, UI_MAPEXTRADATA(node).overlayMask & OVERLAY_NATION,
289 UI_MAPEXTRADATA(node).overlayMask & OVERLAY_XVI, UI_MAPEXTRADATA(node).overlayMask & OVERLAY_RADAR, r_xviTexture, r_radarTexture,
290 true);
291
292 GAME_DrawMapMarkers(node);
293
294 R_DrawBloom();
295 R_EnableRenderbuffer(false);
296 }
297
298 UI_PopClipRect();
299 }
300
onCapturedMouseMove(uiNode_t * node,int x,int y)301 void uiGeoscapeNode::onCapturedMouseMove (uiNode_t* node, int x, int y)
302 {
303 switch (mode) {
304 case MODE_SHIFT2DMAP:
305 {
306 int i;
307 const float zoom = 0.5 / UI_MAPEXTRADATACONST(node).zoom;
308 /* shift the map */
309 UI_MAPEXTRADATA(node).center[0] -= (float) (mousePosX - oldMousePosX) / (node->box.size[0] * UI_MAPEXTRADATACONST(node).zoom);
310 UI_MAPEXTRADATA(node).center[1] -= (float) (mousePosY - oldMousePosY) / (node->box.size[1] * UI_MAPEXTRADATACONST(node).zoom);
311 for (i = 0; i < 2; i++) {
312 /* clamp to min/max values */
313 while (UI_MAPEXTRADATACONST(node).center[i] < 0.0)
314 UI_MAPEXTRADATA(node).center[i] += 1.0;
315 while (UI_MAPEXTRADATACONST(node).center[i] > 1.0)
316 UI_MAPEXTRADATA(node).center[i] -= 1.0;
317 }
318 if (UI_MAPEXTRADATACONST(node).center[1] < zoom)
319 UI_MAPEXTRADATA(node).center[1] = zoom;
320 if (UI_MAPEXTRADATACONST(node).center[1] > 1.0 - zoom)
321 UI_MAPEXTRADATA(node).center[1] = 1.0 - zoom;
322 break;
323 }
324
325 case MODE_SHIFT3DMAP:
326 /* rotate a model */
327 UI_MAPEXTRADATA(node).angles[PITCH] += ROTATE_SPEED * (mousePosX - oldMousePosX) / UI_MAPEXTRADATACONST(node).zoom;
328 UI_MAPEXTRADATA(node).angles[YAW] -= ROTATE_SPEED * (mousePosY - oldMousePosY) / UI_MAPEXTRADATACONST(node).zoom;
329
330 /* clamp the UI_MAPEXTRADATACONST(node).angles */
331 while (UI_MAPEXTRADATACONST(node).angles[YAW] > 0.0)
332 UI_MAPEXTRADATA(node).angles[YAW] = 0.0;
333 while (UI_MAPEXTRADATACONST(node).angles[YAW] < -180.0)
334 UI_MAPEXTRADATA(node).angles[YAW] = -180.0;
335
336 while (UI_MAPEXTRADATACONST(node).angles[PITCH] > 180.0)
337 UI_MAPEXTRADATA(node).angles[PITCH] -= 360.0;
338 while (UI_MAPEXTRADATACONST(node).angles[PITCH] < -180.0)
339 UI_MAPEXTRADATA(node).angles[PITCH] += 360.0;
340 break;
341 case MODE_ZOOMMAP:
342 {
343 const float zoom = 0.5 / UI_MAPEXTRADATACONST(node).zoom;
344 /* zoom the map */
345 UI_MAPEXTRADATA(node).zoom *= pow(0.995, mousePosY - oldMousePosY);
346 if (UI_MAPEXTRADATACONST(node).zoom < UI_MAPEXTRADATACONST(node).mapzoommin)
347 UI_MAPEXTRADATA(node).zoom = UI_MAPEXTRADATACONST(node).mapzoommin;
348 else if (UI_MAPEXTRADATACONST(node).zoom > UI_MAPEXTRADATACONST(node).mapzoommax)
349 UI_MAPEXTRADATA(node).zoom = UI_MAPEXTRADATACONST(node).mapzoommax;
350
351 if (UI_MAPEXTRADATACONST(node).center[1] < zoom)
352 UI_MAPEXTRADATA(node).center[1] = zoom;
353 if (UI_MAPEXTRADATACONST(node).center[1] > 1.0 - zoom)
354 UI_MAPEXTRADATA(node).center[1] = 1.0 - zoom;
355 break;
356 }
357 default:
358 assert(false);
359 break;
360 }
361 oldMousePosX = x;
362 oldMousePosY = y;
363 }
364
startMouseShifting(uiNode_t * node,int x,int y)365 void uiGeoscapeNode::startMouseShifting (uiNode_t* node, int x, int y)
366 {
367 UI_SetMouseCapture(node);
368 if (UI_MAPEXTRADATACONST(node).flatgeoscape)
369 mode = MODE_SHIFT2DMAP;
370 else
371 mode = MODE_SHIFT3DMAP;
372 UI_MAPEXTRADATA(node).smoothRotation = false;
373 oldMousePosX = x;
374 oldMousePosY = y;
375 }
376
377 /**
378 * @brief Return longitude and latitude of a point of the screen for 2D geoscape
379 * @param[in] node The current menuNode we have clicked on (3dmap or map)
380 * @param[in] x X coordinate on the screen that was clicked on
381 * @param[in] y Y coordinate on the screen that was clicked on
382 * @param[out] pos vec2_t was filled with longitude and latitude
383 */
screenToMap(const uiNode_t * node,int x,int y,vec2_t pos)384 void uiGeoscapeNode::screenToMap (const uiNode_t* node, int x, int y, vec2_t pos)
385 {
386 pos[0] = (((UI_MAPEXTRADATACONST(node).mapPos[0] - x) / UI_MAPEXTRADATACONST(node).mapSize[0] + 0.5) / UI_MAPEXTRADATACONST(node).zoom
387 - (UI_MAPEXTRADATACONST(node).center[0] - 0.5)) * 360.0;
388 pos[1] = (((UI_MAPEXTRADATACONST(node).mapPos[1] - y) / UI_MAPEXTRADATACONST(node).mapSize[1] + 0.5) / UI_MAPEXTRADATACONST(node).zoom
389 - (UI_MAPEXTRADATACONST(node).center[1] - 0.5)) * 180.0;
390
391 while (pos[0] > 180.0)
392 pos[0] -= 360.0;
393 while (pos[0] < -180.0)
394 pos[0] += 360.0;
395 }
396
397 /**
398 * @brief Return longitude and latitude of a point of the screen for 3D geoscape (globe)
399 * @param[in] node The current menuNode we have clicked on (3dmap or map)
400 * @param[in] x X coordinate on the screen that was clicked on
401 * @param[in] y Y coordinate on the screen that was clicked on
402 * @param[out] pos vec2_t was filled with longitude and latitude
403 * @sa MAP_3DMapToScreen
404 */
screenTo3DMap(const uiNode_t * node,int x,int y,vec2_t pos)405 void uiGeoscapeNode::screenTo3DMap (const uiNode_t* node, int x, int y, vec2_t pos)
406 {
407 vec2_t mid;
408 vec3_t v, v1, rotationAxis;
409 float dist;
410 const float radius = GLOBE_RADIUS;
411
412 /* set mid to the coordinates of the center of the globe */
413 Vector2Set(mid, UI_MAPEXTRADATACONST(node).mapPos[0] + UI_MAPEXTRADATACONST(node).mapSize[0] / 2.0f,
414 UI_MAPEXTRADATACONST(node).mapPos[1] + UI_MAPEXTRADATACONST(node).mapSize[1] / 2.0f);
415
416 /* stop if we click outside the globe (distance is the distance of the point to the center of the globe) */
417 dist = sqrt((x - mid[0]) * (x - mid[0]) + (y - mid[1]) * (y - mid[1]));
418 if (dist > radius) {
419 Vector2Set(pos, -1.0, -1.0);
420 return;
421 }
422
423 /* calculate the coordinates in the local frame
424 * this frame is the frame of the screen.
425 * v[0] is the vertical axis of the screen
426 * v[1] is the horizontal axis of the screen
427 * v[2] is the axis perpendicular to the screen - we get its value knowing that norm of v is egal to radius
428 * (because the point is on the globe) */
429 v[0] = - (y - mid[1]);
430 v[1] = - (x - mid[0]);
431 v[2] = - sqrt(radius * radius - (x - mid[0]) * (x - mid[0]) - (y - mid[1]) * (y - mid[1]));
432 VectorNormalize(v);
433
434 /* rotate the vector to switch of reference frame
435 * note the ccs.angles[ROLL] is always 0, so there is only 2 rotations and not 3
436 * and that GLOBE_ROTATE is already included in ccs.angles[YAW]
437 * first rotation is along the horizontal axis of the screen, to put north-south axis of the earth
438 * perpendicular to the screen */
439 VectorSet(rotationAxis, 0, 1, 0);
440 RotatePointAroundVector(v1, rotationAxis, v, UI_MAPEXTRADATACONST(node).angles[YAW]);
441
442 /* second rotation is to rotate the earth around its north-south axis
443 * so that Greenwich meridian is along the vertical axis of the screen */
444 VectorSet(rotationAxis, 0, 0, 1);
445 RotatePointAroundVector(v, rotationAxis, v1, UI_MAPEXTRADATACONST(node).angles[PITCH]);
446
447 /* we therefore got in v the coordinates of the point in the static frame of the earth
448 * that we can convert in polar coordinates to get its latitude and longitude */
449 VecToPolar(v, pos);
450 }
451
onLeftClick(uiNode_t * node,int x,int y)452 void uiGeoscapeNode::onLeftClick (uiNode_t* node, int x, int y)
453 {
454 if (mode != MODE_NULL)
455 return;
456
457 vec2_t pos;
458
459 /* get map position */
460 if (!UI_MAPEXTRADATACONST(node).flatgeoscape)
461 screenTo3DMap(node, x, y, pos);
462 else
463 screenToMap(node, x, y, pos);
464
465 GAME_MapClick(node, x, y, pos);
466 }
467
onStartDragging(uiNode_t * node,int startX,int startY,int x,int y,int button)468 bool uiGeoscapeNode::onStartDragging (uiNode_t* node, int startX, int startY, int x, int y, int button)
469 {
470 switch (button) {
471 case K_MOUSE1:
472 case K_MOUSE3:
473 startMouseShifting(node, startX, startY);
474 return true;
475 case K_MOUSE2:
476 UI_SetMouseCapture(node);
477 mode = MODE_ZOOMMAP;
478 oldMousePosX = startX;
479 oldMousePosY = startY;
480 return true;
481 }
482 return false;
483 }
484
onMouseUp(uiNode_t * node,int x,int y,int button)485 void uiGeoscapeNode::onMouseUp (uiNode_t* node, int x, int y, int button)
486 {
487 if (mode != MODE_NULL) {
488 UI_MouseRelease();
489 mode = MODE_NULL;
490 }
491 }
492
493 /**
494 * @brief Called when the node have lost the captured node
495 * We clean cached data
496 */
onCapturedMouseLost(uiNode_t * node)497 void uiGeoscapeNode::onCapturedMouseLost (uiNode_t* node)
498 {
499 mode = MODE_NULL;
500 }
501
502 /**
503 * Zoom on the node
504 * @todo it should use an int param for smooth zoom
505 */
zoom(uiNode_t * node,bool out)506 void uiGeoscapeNode::zoom (uiNode_t* node, bool out)
507 {
508 UI_MAPEXTRADATA(node).zoom *= pow(0.995, (out ? 10: -10));
509 if (UI_MAPEXTRADATACONST(node).zoom < UI_MAPEXTRADATACONST(node).mapzoommin)
510 UI_MAPEXTRADATA(node).zoom = UI_MAPEXTRADATACONST(node).mapzoommin;
511 else if (UI_MAPEXTRADATACONST(node).zoom > UI_MAPEXTRADATACONST(node).mapzoommax)
512 UI_MAPEXTRADATA(node).zoom = UI_MAPEXTRADATACONST(node).mapzoommax;
513
514 if (UI_MAPEXTRADATACONST(node).flatgeoscape) {
515 if (UI_MAPEXTRADATACONST(node).center[1] < 0.5 / UI_MAPEXTRADATACONST(node).zoom)
516 UI_MAPEXTRADATA(node).center[1] = 0.5 / UI_MAPEXTRADATACONST(node).zoom;
517 if (UI_MAPEXTRADATACONST(node).center[1] > 1.0 - 0.5 / UI_MAPEXTRADATACONST(node).zoom)
518 UI_MAPEXTRADATA(node).center[1] = 1.0 - 0.5 / UI_MAPEXTRADATACONST(node).zoom;
519 }
520 UI_MAPEXTRADATA(node).smoothRotation = false;
521 }
522
onScroll(uiNode_t * node,int deltaX,int deltaY)523 bool uiGeoscapeNode::onScroll (uiNode_t* node, int deltaX, int deltaY)
524 {
525 bool down = deltaY > 0;
526 if (deltaY == 0)
527 return false;
528 zoom(node, down);
529 return true;
530 }
531
532 /**
533 * @brief Called before loading. Used to set default attribute values
534 */
onLoading(uiNode_t * node)535 void uiGeoscapeNode::onLoading (uiNode_t* node)
536 {
537 Vector4Set(node->color, 1, 1, 1, 1);
538
539 OBJZERO(EXTRADATA(node));
540 EXTRADATA(node).angles[YAW] = GLOBE_ROTATE;
541 EXTRADATA(node).center[0] = EXTRADATA(node).center[1] = 0.5;
542 EXTRADATA(node).zoom = 1.0;
543 Vector2Set(EXTRADATA(node).smoothFinal2DGeoscapeCenter, 0.5, 0.5);
544 VectorSet(EXTRADATA(node).smoothFinalGlobeAngle, 0, GLOBE_ROTATE, 0);
545
546 /* @todo: allocate this on a per node basis - and remove the global variable geoscapeData */
547 EXTRADATA(node).geoscapeData = &geoscapeData;
548 /* EXTRADATA(node).geoscapeData = Mem_AllocType(geoscapeData_t); */
549
550 /** this is the data that is used with r_dayandnightTexture */
551 EXTRADATA(node).r_dayandnightAlpha = Mem_AllocTypeN(byte, DAN_WIDTH * DAN_HEIGHT);
552
553 r_dayandnightTexture = R_LoadImageData("***r_dayandnighttexture***", nullptr, DAN_WIDTH, DAN_HEIGHT, it_effect);
554 r_radarTexture = R_LoadImageData("***r_radarTexture***", nullptr, RADAR_WIDTH, RADAR_HEIGHT, it_effect);
555 r_xviTexture = R_LoadImageData("***r_xvitexture***", nullptr, XVI_WIDTH, XVI_HEIGHT, it_effect);
556 }
557
UI_GeoscapeNodeZoomIn(uiNode_t * node,const uiCallContext_t * context)558 static void UI_GeoscapeNodeZoomIn (uiNode_t* node, const uiCallContext_t* context)
559 {
560 uiGeoscapeNode* m = static_cast<uiGeoscapeNode*>(node->behaviour->manager.get());
561 m->zoom(node, false);
562 }
563
UI_GeoscapeNodeZoomOut(uiNode_t * node,const uiCallContext_t * context)564 static void UI_GeoscapeNodeZoomOut (uiNode_t* node, const uiCallContext_t* context)
565 {
566 uiGeoscapeNode* m = static_cast<uiGeoscapeNode*>(node->behaviour->manager.get());
567 m->zoom(node, true);
568 }
569
570 /**
571 * @brief Command binding for map zooming
572 * @todo convert into node method
573 */
UI_GeoscapeNodeZoom_f(void)574 static void UI_GeoscapeNodeZoom_f (void)
575 {
576 const char* cmd;
577 const float zoomAmount = 50.0f;
578
579 if (Cmd_Argc() != 2) {
580 Com_Printf("Usage: %s <in|out>\n", Cmd_Argv(0));
581 return;
582 }
583
584 cmd = Cmd_Argv(1);
585 uiNode_t* node = geoscapeNode;
586 if (!node)
587 return;
588
589 switch (cmd[0]) {
590 case 'i':
591 UI_MAPEXTRADATA(node).smoothFinalZoom = UI_MAPEXTRADATACONST(node).zoom * powf(0.995, -zoomAmount);
592 break;
593 case 'o':
594 UI_MAPEXTRADATA(node).smoothFinalZoom = UI_MAPEXTRADATACONST(node).zoom * powf(0.995, zoomAmount);
595 break;
596 default:
597 Com_Printf("UI_GeoscapeNodeZoom_f: Invalid parameter: %s\n", cmd);
598 return;
599 }
600
601 if (UI_MAPEXTRADATACONST(node).smoothFinalZoom < UI_MAPEXTRADATACONST(node).mapzoommin)
602 UI_MAPEXTRADATA(node).smoothFinalZoom = UI_MAPEXTRADATACONST(node).mapzoommin;
603 else if (UI_MAPEXTRADATACONST(node).smoothFinalZoom > UI_MAPEXTRADATACONST(node).mapzoommax)
604 UI_MAPEXTRADATA(node).smoothFinalZoom = UI_MAPEXTRADATACONST(node).mapzoommax;
605
606 if (UI_MAPEXTRADATACONST(node).flatgeoscape) {
607 UI_MAPEXTRADATA(node).zoom = UI_MAPEXTRADATACONST(node).smoothFinalZoom;
608 if (UI_MAPEXTRADATACONST(node).center[1] < 0.5 / UI_MAPEXTRADATACONST(node).zoom)
609 UI_MAPEXTRADATA(node).center[1] = 0.5 / UI_MAPEXTRADATACONST(node).zoom;
610 if (UI_MAPEXTRADATACONST(node).center[1] > 1.0 - 0.5 / UI_MAPEXTRADATACONST(node).zoom)
611 UI_MAPEXTRADATA(node).center[1] = 1.0 - 0.5 / UI_MAPEXTRADATACONST(node).zoom;
612 } else {
613 VectorCopy(UI_MAPEXTRADATACONST(node).angles, UI_MAPEXTRADATA(node).smoothFinalGlobeAngle);
614 UI_MAPEXTRADATA(node).smoothDeltaLength = 0;
615 UI_MAPEXTRADATA(node).smoothRotation = true;
616 UI_MAPEXTRADATA(node).smoothDeltaZoom = fabs(UI_MAPEXTRADATACONST(node).smoothFinalZoom - UI_MAPEXTRADATACONST(node).zoom);
617 }
618 }
619
620 /**
621 * @brief Command binding for map scrolling
622 * @todo convert into node method
623 */
UI_GeoscapeNodeScroll_f(void)624 static void UI_GeoscapeNodeScroll_f (void)
625 {
626 const char* cmd;
627 float scrollX = 0.0f, scrollY = 0.0f;
628 const float scrollAmount = 80.0f;
629
630 if (Cmd_Argc() != 2) {
631 Com_Printf("Usage: %s <up|down|left|right>\n", Cmd_Argv(0));
632 return;
633 }
634
635 cmd = Cmd_Argv(1);
636
637 uiNode_t* node = geoscapeNode;
638 if (!node)
639 return;
640
641 switch (cmd[0]) {
642 case 'l':
643 scrollX = scrollAmount;
644 break;
645 case 'r':
646 scrollX = -scrollAmount;
647 break;
648 case 'u':
649 scrollY = scrollAmount;
650 break;
651 case 'd':
652 scrollY = -scrollAmount;
653 break;
654 default:
655 Com_Printf("UI_GeoscapeNodeScroll_f: Invalid parameter\n");
656 return;
657 }
658
659 if (!UI_MAPEXTRADATACONST(node).flatgeoscape) {
660 /* case 3D geoscape */
661 vec3_t diff;
662
663 VectorCopy(UI_MAPEXTRADATACONST(node).angles, UI_MAPEXTRADATA(node).smoothFinalGlobeAngle);
664
665 /* rotate a model */
666 UI_MAPEXTRADATA(node).smoothFinalGlobeAngle[PITCH] += ROTATE_SPEED * (scrollX) / UI_MAPEXTRADATACONST(node).zoom;
667 UI_MAPEXTRADATA(node).smoothFinalGlobeAngle[YAW] -= ROTATE_SPEED * (scrollY) / UI_MAPEXTRADATACONST(node).zoom;
668
669 while (UI_MAPEXTRADATACONST(node).smoothFinalGlobeAngle[YAW] < -180.0) {
670 UI_MAPEXTRADATA(node).smoothFinalGlobeAngle[YAW] = -180.0;
671 }
672 while (UI_MAPEXTRADATACONST(node).smoothFinalGlobeAngle[YAW] > 0.0) {
673 UI_MAPEXTRADATA(node).smoothFinalGlobeAngle[YAW] = 0.0;
674 }
675
676 while (UI_MAPEXTRADATACONST(node).smoothFinalGlobeAngle[PITCH] > 180.0) {
677 UI_MAPEXTRADATA(node).smoothFinalGlobeAngle[PITCH] -= 360.0;
678 UI_MAPEXTRADATA(node).angles[PITCH] -= 360.0;
679 }
680 while (UI_MAPEXTRADATACONST(node).smoothFinalGlobeAngle[PITCH] < -180.0) {
681 UI_MAPEXTRADATA(node).smoothFinalGlobeAngle[PITCH] += 360.0;
682 UI_MAPEXTRADATA(node).angles[PITCH] += 360.0;
683 }
684 VectorSubtract(UI_MAPEXTRADATACONST(node).smoothFinalGlobeAngle, UI_MAPEXTRADATACONST(node).angles, diff);
685 UI_MAPEXTRADATA(node).smoothDeltaLength = VectorLength(diff);
686
687 UI_MAPEXTRADATA(node).smoothFinalZoom = UI_MAPEXTRADATACONST(node).zoom;
688 UI_MAPEXTRADATA(node).smoothDeltaZoom = 0.0f;
689 UI_MAPEXTRADATA(node).smoothRotation = true;
690 } else {
691 int i;
692 /* shift the map */
693 UI_MAPEXTRADATA(node).center[0] -= (float) (scrollX) / (UI_MAPEXTRADATACONST(node).mapSize[0] * UI_MAPEXTRADATACONST(node).zoom);
694 UI_MAPEXTRADATA(node).center[1] -= (float) (scrollY) / (UI_MAPEXTRADATACONST(node).mapSize[1] * UI_MAPEXTRADATACONST(node).zoom);
695 for (i = 0; i < 2; i++) {
696 while (UI_MAPEXTRADATACONST(node).center[i] < 0.0)
697 UI_MAPEXTRADATA(node).center[i] += 1.0;
698 while (UI_MAPEXTRADATACONST(node).center[i] > 1.0)
699 UI_MAPEXTRADATA(node).center[i] -= 1.0;
700 }
701 if (UI_MAPEXTRADATACONST(node).center[1] < 0.5 / UI_MAPEXTRADATACONST(node).zoom)
702 UI_MAPEXTRADATA(node).center[1] = 0.5 / UI_MAPEXTRADATACONST(node).zoom;
703 if (UI_MAPEXTRADATACONST(node).center[1] > 1.0 - 0.5 / UI_MAPEXTRADATACONST(node).zoom)
704 UI_MAPEXTRADATA(node).center[1] = 1.0 - 0.5 / UI_MAPEXTRADATACONST(node).zoom;
705 }
706 }
707
UI_RegisterGeoscapeNode(uiBehaviour_t * behaviour)708 void UI_RegisterGeoscapeNode (uiBehaviour_t* behaviour)
709 {
710 behaviour->name = "geoscape";
711 behaviour->manager = UINodePtr(new uiGeoscapeNode());
712 behaviour->extraDataSize = sizeof(EXTRADATA_TYPE);
713
714 /* Use a right padding. */
715 UI_RegisterExtradataNodeProperty(behaviour, "padding-right", V_FLOAT, EXTRADATA_TYPE, paddingRight);
716 /* Call it to zoom out of the map */
717 UI_RegisterNodeMethod(behaviour, "zoomin", UI_GeoscapeNodeZoomIn);
718 /* Call it to zoom into the map */
719 UI_RegisterNodeMethod(behaviour, "zoomout", UI_GeoscapeNodeZoomOut);
720
721 Cmd_AddCommand("map_zoom", UI_GeoscapeNodeZoom_f);
722 Cmd_AddCommand("map_scroll", UI_GeoscapeNodeScroll_f);
723
724 cl_3dmap = Cvar_Get("cl_3dmap", "1", CVAR_ARCHIVE, "3D geoscape or flat geoscape");
725 cl_3dmapAmbient = Cvar_Get("cl_3dmapAmbient", "0", CVAR_ARCHIVE, "3D geoscape ambient lighting factor");
726 cl_mapzoommax = Cvar_Get("cl_mapzoommax", "6.0", CVAR_ARCHIVE, "Maximum geoscape zooming value");
727 cl_mapzoommin = Cvar_Get("cl_mapzoommin", "1.0", CVAR_ARCHIVE, "Minimum geoscape zooming value");
728 cl_geoscape_overlay = Cvar_Get("cl_geoscape_overlay", "0", 0, "Geoscape overlays - Bitmask");
729 }
730