1 /** \file glHelp.cc
2    Some OpenGL utility algorithms.
3 */
4 /*
5    Copyright (C) 2000-2005  Mathias Broxvall
6                             Yannick Perret
7 
8    This program is free software; you can redistribute it and/or modify
9    it under the terms of the GNU General Public License as published by
10    the Free Software Foundation; either version 2 of the License, or
11    (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.  See the
16    GNU General Public License for more details.
17 
18    You should have received a copy of the GNU General Public License
19    along with this program; if not, write to the Free Software
20    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21 */
22 
23 #include "glHelp.h"
24 
25 #include "font.h"
26 #include "game.h"
27 #include "map.h"
28 #include "settings.h"
29 #include "sparkle2d.h"
30 
31 #include <SDL2/SDL_image.h>
32 #include <SDL2/SDL_ttf.h>
33 #include <cstdlib>
34 #include <map>
35 
36 float fps = 50.0;
37 int screenWidth = 640, screenHeight = 480;
38 ViewParameters activeView;
39 UniformLocations uniformLocations;
40 
41 static GLuint shaderWaterDay = 0;
42 static GLuint shaderWaterNight = 0;
43 static GLuint shaderTileDay = 0;
44 static GLuint shaderTileNight = 0;
45 static GLuint shaderTileShadow = 0;
46 static GLuint shaderLine = 0;
47 static GLuint shaderUI = 0;
48 static GLuint shaderObjectDay = 0;
49 static GLuint shaderObjectNight = 0;
50 static GLuint shaderObjectShadow = 0;
51 static GLuint shaderReflection = 0;
52 GLuint textureBlank = 0;
53 GLuint textureGlitter = 0;
54 GLuint textureMousePointer = 0;
55 GLuint textureDizzy = 0;
56 GLuint textureWings = 0;
57 GLuint textureTrack = 0;
58 
59 TTF_Font *ingameFont;
60 extern struct timespec displayStartTime;
61 extern struct timespec lastDisplayStartTime;
62 
63 const GLfloat menuColorSelected[4] = {0.86f, 0.86f, 0.86f, 1.f};
64 const GLfloat menuColor[4] = {0.86f, 0.86f, 0.25f, 1.f};
65 
66 const double sin6[6] = {0.0, 0.8660254037844386,  0.8660254037844386,
67                         0.0, -0.8660254037844386, -0.8660254037844386};
68 const double cos6[6] = {1.0, 0.5, -0.5, -1.0, -0.5, 0.5};
69 const double sin10[10] = {
70     0.0, 0.5877852522924731,  0.9510565162951535,  0.9510565162951535,  0.5877852522924731,
71     0.0, -0.5877852522924731, -0.9510565162951535, -0.9510565162951535, -0.5877852522924731};
72 const double cos10[10] = {
73     1.0,  0.8090169943749475,  0.30901699437494745,  -0.30901699437494745, -0.8090169943749475,
74     -1.0, -0.8090169943749475, -0.30901699437494745, 0.30901699437494745,  0.8090169943749475};
75 const double sin12[12] = {0.0, 0.5,  0.8660254037844386,  1.0,  0.8660254037844386,  0.5,
76                           0.0, -0.5, -0.8660254037844386, -1.0, -0.8660254037844386, -0.5};
77 const double cos12[12] = {1.0,  0.8660254037844386,  0.5,  0.0, -0.5, -0.8660254037844386,
78                           -1.0, -0.8660254037844386, -0.5, 0.0, 0.5,  0.8660254037844386};
79 const double sin14[14] = {0.0,
80                           0.4338837391175581,
81                           0.7818314824680298,
82                           0.9749279121818236,
83                           0.9749279121818236,
84                           0.7818314824680298,
85                           0.4338837391175581,
86                           0.0,
87                           -0.4338837391175581,
88                           -0.7818314824680298,
89                           -0.9749279121818236,
90                           -0.9749279121818236,
91                           -0.7818314824680298,
92                           -0.4338837391175581};
93 const double cos14[14] = {1.0,
94                           0.9009688679024191,
95                           0.6234898018587336,
96                           0.22252093395631445,
97                           -0.22252093395631445,
98                           -0.6234898018587336,
99                           -0.9009688679024191,
100                           -1.0,
101                           -0.9009688679024191,
102                           -0.6234898018587336,
103                           -0.22252093395631445,
104                           0.22252093395631445,
105                           0.6234898018587336,
106                           0.9009688679024191};
107 
108 #define GLHELP_MAX_TEXTURES 256
109 GLuint textures[GLHELP_MAX_TEXTURES] = {0};  // added init. to 0 (no texture)
110 char *textureNames[GLHELP_MAX_TEXTURES] = {NULL};
111 int numTextures;
112 
113 #define MAX_BALL_DETAIL 32
114 GLfloat *sphere_points[MAX_BALL_DETAIL];
115 GLfloat *sphere_texcos[MAX_BALL_DETAIL];
116 ushort *sphere_idxs[MAX_BALL_DETAIL];
117 
118 static std::map<int, TTF_Font *> menuFontLookup;
119 
120 Sparkle2D *sparkle2D = NULL;
121 
122 struct StringInfo {
123   TTF_Font *font;
124   char string[256];
125 };
operator <(const struct StringInfo & a,const struct StringInfo & b)126 static bool operator<(const struct StringInfo &a, const struct StringInfo &b) {
127   if (a.font != b.font) return a.font < b.font;
128   return strcmp(a.string, b.string) < 0;
129 }
130 
131 struct StringCache {
132   GLuint texture;
133   GLfloat texcoord[4];
134   int w, h;
135   long tick;
136 };
137 
138 static std::map<StringInfo, StringCache> strcache;
139 
drawStringToSurface(struct StringInfo & inf,bool outlined)140 static SDL_Surface *drawStringToSurface(struct StringInfo &inf, bool outlined) {
141   SDL_Color white = {255, 255, 255, 255};
142   SDL_Color black = {0, 0, 0, 0};
143   if (outlined) {
144     TTF_SetFontOutline(inf.font, 0);
145 
146     SDL_Surface *inner = TTF_RenderUTF8_Blended(inf.font, inf.string, white);
147     if (!inner) {
148       warning("Failed to render string outline '%s'", inf.string);
149       return NULL;
150     }
151     TTF_SetFontOutline(inf.font, 2);
152 
153     SDL_Surface *outline = TTF_RenderUTF8_Blended(inf.font, inf.string, black);
154     if (!outline) {
155       warning("Failed to render string inside '%s'", inf.string);
156       return NULL;
157     }
158     SDL_Rect rect = {2, 2, inner->w, inner->h};
159     SDL_SetSurfaceBlendMode(inner, SDL_BLENDMODE_BLEND);
160     SDL_BlitSurface(inner, NULL, outline, &rect);
161     SDL_FreeSurface(inner);
162     return outline;
163   } else {
164     SDL_Surface *outline = TTF_RenderUTF8_Blended(inf.font, inf.string, white);
165     if (!outline) {
166       warning("Failed to render string '%s'", inf.string);
167       return NULL;
168     }
169     return outline;
170   }
171 }
172 
draw2DString(TTF_Font * font,const char * string,int x,int y,float red,float green,float blue,float alpha,bool outlined,int align,int maxwidth)173 int draw2DString(TTF_Font *font, const char *string, int x, int y, float red, float green,
174                  float blue, float alpha, bool outlined, int align, int maxwidth) {
175   struct StringInfo inf;
176   inf.font = font;
177   memset(inf.string, 0, 256);
178   strncpy(inf.string, string, 255);
179   inf.string[255] = '\0';
180   if (strcache.count(inf) <= 0) {
181     struct StringCache newentry;
182     newentry.tick = displayFrameNumber;
183     SDL_Surface *surf = drawStringToSurface(inf, outlined);
184     if (!surf) { return 0; }
185     newentry.texture = LoadTexture(surf, newentry.texcoord);
186     newentry.w = surf->w;
187     newentry.h = surf->h;
188     SDL_FreeSurface(surf);
189     strcache[inf] = newentry;
190   }
191 
192   struct StringCache &cached = strcache[inf];
193   cached.tick = displayFrameNumber;
194 
195   GLfloat shrink = (maxwidth > 0 && maxwidth < cached.w) ? (maxwidth / (GLfloat)cached.w) : 1.;
196   draw2DRectangle(x - shrink * align * cached.w / 2, y - shrink * cached.h / 2,
197                   shrink * cached.w, shrink * cached.h, cached.texcoord[0], cached.texcoord[1],
198                   cached.texcoord[2], cached.texcoord[3], red, green, blue, alpha,
199                   cached.texture);
200   return maxwidth > 0 ? std::min(cached.w, maxwidth) : cached.w;
201 }
202 
update2DStringCache(bool force_wipe)203 void update2DStringCache(bool force_wipe) {
204   int erased;
205   do {
206     erased = false;
207     // TODO: better asymptotics
208     for (std::map<StringInfo, StringCache>::iterator i = strcache.begin(); i != strcache.end();
209          ++i) {
210       if (i->second.tick < displayFrameNumber - 100 || force_wipe) {
211         glDeleteTextures(1, &i->second.texture);
212         strcache.erase(i);
213         erased = true;
214         break;
215       }
216     }
217   } while (erased);
218 }
219 
menuFontForSize(int sz)220 TTF_Font *menuFontForSize(int sz) {
221   if (!menuFontLookup.count(sz)) {
222     char str[256];
223     snprintf(str, sizeof(str), "%s/fonts/%s", effectiveShareDir, "FreeSerifBoldItalic.ttf");
224     menuFontLookup[sz] = TTF_OpenFont(str, 2 * sz);  // barbatri
225     if (!menuFontLookup[sz]) { error("failed to load font %s", str); }
226   }
227   return menuFontLookup[sz];
228 }
229 
230 double mousePointerPhase = 0.0;
231 
tickMouse(Real td)232 void tickMouse(Real td) {
233   int mouseX, mouseY;
234   static int oldMouseX = 0, oldMouseY = 0;
235   SDL_GetMouseState(&mouseX, &mouseY);
236   float mouseSpeedX = (mouseX - oldMouseX) / td;
237   float mouseSpeedY = (mouseY - oldMouseY) / td;
238   float mouseSpeedLen = std::sqrt(mouseSpeedX * mouseSpeedX + mouseSpeedY * mouseSpeedY);
239   if (mouseSpeedLen > 25.f) {
240     mouseSpeedX /= 25.f / mouseSpeedLen;
241     mouseSpeedY /= 25.f / mouseSpeedLen;
242     mouseSpeedLen = 25.f;
243   }
244   static Real last_sparkle = 0.0;
245 
246   oldMouseX = mouseX;
247   oldMouseY = mouseY;
248 
249   mousePointerPhase += td;
250   sparkle2D->tick(td);
251   last_sparkle += td * (1.0 + mouseSpeedLen * 0.1);
252   while (last_sparkle > 0.0) {
253     last_sparkle -= 0.05;
254     float pos[2] = {(float)(mouseX + 10.0f * (frandom() - 0.5f)),
255                     (float)(mouseY + 10.0f * (frandom() - 0.5f))};
256     float speed[2] = {(float)(mouseSpeedX / 2.f + 100.0f * (frandom() - 0.5f)),
257                       (float)(mouseSpeedY / 2.f - 120.0f * frandom())};
258     float color[4] = {(float)(0.8f + 0.2f * frandom()), (float)(0.8f + 0.2f * frandom()),
259                       (float)(0.5f + 0.2f * frandom()), (float)(0.9f - 0.35f * frandom())};
260     sparkle2D->add(pos, speed, (float)(1.5 + 0.5 * (frandom() - 0.5)),
261                    (float)(11 + 3. * (frandom() - 0.5)), color);
262   }
263 }
264 
draw2DRectangle(GLfloat x,GLfloat y,GLfloat w,GLfloat h,GLfloat tx,GLfloat ty,GLfloat tw,GLfloat th,GLfloat r,GLfloat g,GLfloat b,GLfloat a,GLuint tex)265 void draw2DRectangle(GLfloat x, GLfloat y, GLfloat w, GLfloat h, GLfloat tx, GLfloat ty,
266                      GLfloat tw, GLfloat th, GLfloat r, GLfloat g, GLfloat b, GLfloat a,
267                      GLuint tex) {
268   GLfloat corners[4][2] = {{x, y}, {x, y + h}, {x + w, y}, {x + w, y + h}};
269   GLfloat texture[4][2] = {{tx, ty}, {tx, ty + th}, {tx + tw, ty}, {tx + tw, ty + th}};
270   GLfloat colors[4][4] = {{r, g, b, a}, {r, g, b, a}, {r, g, b, a}, {r, g, b, a}};
271   draw2DQuad(corners, texture, colors, tex);
272 }
273 
draw2DQuad(const GLfloat ver[4][2],const GLfloat txc[4][2],const GLfloat col[4][4],GLuint tex)274 void draw2DQuad(const GLfloat ver[4][2], const GLfloat txc[4][2], const GLfloat col[4][4],
275                 GLuint tex) {
276   Require2DMode();
277   if (tex == 0) { tex = textureBlank; }
278 
279   const GLfloat data[32] = {
280       ver[0][0], ver[0][1], col[0][0], col[0][1], col[0][2], col[0][3], txc[0][0], txc[0][1],
281       ver[1][0], ver[1][1], col[1][0], col[1][1], col[1][2], col[1][3], txc[1][0], txc[1][1],
282       ver[2][0], ver[2][1], col[2][0], col[2][1], col[2][2], col[2][3], txc[2][0], txc[2][1],
283       ver[3][0], ver[3][1], col[3][0], col[3][1], col[3][2], col[3][3], txc[3][0], txc[3][1],
284   };
285 
286   static GLuint idxs = (GLuint)-1;
287   static GLuint buf = (GLuint)-1;
288   static GLuint vao = (GLuint)-1;
289   if (idxs == (GLuint)-1) {
290     glGenBuffers(1, &idxs);
291     glGenBuffers(1, &buf);
292     glGenVertexArrays(1, &vao);
293 
294     const ushort idxdata[6] = {0, 1, 2, 1, 2, 3};
295 
296     glBindVertexArray(vao);
297     glBindBuffer(GL_ARRAY_BUFFER, buf);
298     glBufferData(GL_ARRAY_BUFFER, 32 * sizeof(GLfloat), NULL, GL_STREAM_DRAW);
299     glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, idxs);
300     glBufferData(GL_ELEMENT_ARRAY_BUFFER, 6 * sizeof(ushort), idxdata, GL_STATIC_DRAW);
301     configureUIAttributes();
302   } else {
303     glBindVertexArray(vao);
304   }
305 
306   glBindBuffer(GL_ARRAY_BUFFER, buf);
307   glBufferSubData(GL_ARRAY_BUFFER, 0, 32 * sizeof(GLfloat), data);
308 
309   glBindTexture(GL_TEXTURE_2D, tex);
310   glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, (void *)0);
311 }
312 
drawMousePointer()313 void drawMousePointer() {
314   int mouseX, mouseY;
315   sparkle2D->draw();
316   SDL_GetMouseState(&mouseX, &mouseY);
317   drawMouse(mouseX, mouseY, 64, 64);
318 }
319 
drawMouse(int x,int y,int w,int h)320 void drawMouse(int x, int y, int w, int h) {
321   GLfloat r1 = 1.0 + 0.1 * std::cos(mousePointerPhase * 1.8);
322   GLfloat r2 = 1.0 + 0.1 * std::cos(mousePointerPhase * 1.9);
323   GLfloat dx = 0.707f * w * r1 * std::sin(mousePointerPhase * 0.35);
324   GLfloat dy = 0.707f * h * r2 * std::cos(mousePointerPhase * 0.35);
325 
326   GLfloat colors[4][4] = {
327       {1., 1., 1., 1.}, {1., 1., 1., 1.}, {1., 1., 1., 1.}, {1., 1., 1., 1.}};
328   GLfloat texco[4][2] = {{0., 0.}, {0., 1.}, {1., 0.}, {1., 1.}};
329 
330   GLfloat vco[4][2] = {{x - dx, y - dy}, {x - dy, y + dx}, {x + dy, y - dx}, {x + dx, y + dy}};
331   draw2DQuad(vco, texco, colors, textureMousePointer);
332 }
333 
packObjectVertex(void * dest,GLfloat x,GLfloat y,GLfloat z,GLfloat tx,GLfloat ty,const Color & color,const GLfloat normal[3])334 size_t packObjectVertex(void *dest, GLfloat x, GLfloat y, GLfloat z, GLfloat tx, GLfloat ty,
335                         const Color &color, const GLfloat normal[3]) {
336   uint32_t *aout = (uint32_t *)dest;
337   GLfloat *fout = (GLfloat *)dest;
338   fout[0] = x;
339   fout[1] = y;
340   fout[2] = z;
341   aout[3] = (((uint32_t)(color.v[1])) << 16) + (uint32_t)(color.v[0]);
342   aout[4] = (((uint32_t)(color.v[3])) << 16) + (uint32_t)(color.v[2]);
343   aout[5] = (((uint32_t)(65535.f * 0.5f * ty)) << 16) + (uint32_t)(65535.f * 0.5f * tx);
344   aout[6] = packNormal(normal);
345   return 8 * sizeof(GLfloat);
346 }
configureObjectAttributes()347 void configureObjectAttributes() {
348   glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (void *)0);
349   glVertexAttribPointer(1, 4, GL_UNSIGNED_SHORT, GL_TRUE, 8 * sizeof(GLfloat),
350                         (void *)(3 * sizeof(GLfloat)));
351   glVertexAttribPointer(2, 2, GL_UNSIGNED_SHORT, GL_TRUE, 8 * sizeof(GLfloat),
352                         (void *)(5 * sizeof(GLfloat)));
353   glVertexAttribPointer(3, 4, GL_UNSIGNED_INT_2_10_10_10_REV, GL_TRUE, 8 * sizeof(GLfloat),
354                         (void *)(6 * sizeof(GLfloat)));
355   glEnableVertexAttribArray(0);
356   glEnableVertexAttribArray(1);
357   glEnableVertexAttribArray(2);
358   glEnableVertexAttribArray(3);
359 }
configureUIAttributes()360 void configureUIAttributes() {
361   // To be used by `shaderUI`
362   glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (void *)0);
363   glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat),
364                         (void *)(2 * sizeof(GLfloat)));
365   glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat),
366                         (void *)(6 * sizeof(GLfloat)));
367   glEnableVertexAttribArray(0);
368   glEnableVertexAttribArray(1);
369   glEnableVertexAttribArray(2);
370 }
371 
372 static GLuint lastProgram = 0;
373 
markViewChanged()374 void markViewChanged() { lastProgram = 0; }
375 
setActiveProgramAndUniforms(Shader_Type type)376 void setActiveProgramAndUniforms(Shader_Type type) {
377   GLuint shader = 0;
378   switch (type) {
379   case Shader_Line:
380     shader = shaderLine;
381     break;
382   case Shader_Object:
383     if (activeView.calculating_shadows) {
384       shader = shaderObjectShadow;
385     } else {
386       if (activeView.day_mode) {
387         shader = shaderObjectDay;
388       } else {
389         shader = shaderObjectNight;
390       }
391     }
392     break;
393   case Shader_Tile:
394     if (activeView.calculating_shadows) {
395       shader = shaderTileShadow;
396     } else {
397       if (activeView.day_mode) {
398         shader = shaderTileDay;
399       } else {
400         shader = shaderTileNight;
401       }
402     }
403     break;
404   case Shader_Water:
405     if (activeView.day_mode) {
406       shader = shaderWaterDay;
407     } else {
408       shader = shaderWaterNight;
409     }
410     break;
411   case Shader_Reflection:
412     shader = shaderReflection;
413     break;
414   }
415 
416   if (shader == lastProgram) return;
417   glUseProgram(shader);
418   lastProgram = shader;
419 
420   /* Set universal uniforms */
421   Matrix4d mvp;
422   matrixMult(activeView.modelview, activeView.projection, mvp);
423   GLfloat lmvp[16], lmodel[16];
424   for (int i = 0; i < 4; i++) {
425     for (int j = 0; j < 4; j++) {
426       lmvp[4 * i + j] = mvp[i][j];
427       lmodel[4 * i + j] = activeView.modelview[i][j];
428     }
429   }
430   glUniformMatrix4fv(glGetUniformLocation(shader, "mvp_matrix"), 1, GL_FALSE, (GLfloat *)lmvp);
431   glUniformMatrix4fv(glGetUniformLocation(shader, "model_matrix"), 1, GL_FALSE,
432                      (GLfloat *)lmodel);
433 
434   switch (type) {
435   case Shader_Tile:
436     uniformLocations.render_stage = glGetUniformLocation(shader, "render_stage");
437     uniformLocations.arrtex = glGetUniformLocation(shader, "arrtex");
438     uniformLocations.gameTime = glGetUniformLocation(shader, "gameTime");
439     break;
440   case Shader_Water:
441     uniformLocations.wtex = glGetUniformLocation(shader, "wtex");
442     uniformLocations.gameTime = glGetUniformLocation(shader, "gameTime");
443     break;
444   case Shader_Object:
445     uniformLocations.render_stage = glGetUniformLocation(shader, "render_stage");
446     uniformLocations.specular_color = glGetUniformLocation(shader, "specular");
447     uniformLocations.sharpness = glGetUniformLocation(shader, "sharpness");
448     uniformLocations.use_lighting = glGetUniformLocation(shader, "use_lighting");
449     uniformLocations.ignore_shadow = glGetUniformLocation(shader, "ignore_shadow");
450     break;
451   case Shader_Reflection:
452     uniformLocations.refl_color = glGetUniformLocation(shader, "refl_color");
453     uniformLocations.tex = glGetUniformLocation(shader, "tex");
454     break;
455   case Shader_Line:
456     uniformLocations.line_color = glGetUniformLocation(shader, "line_color");
457     break;
458   }
459   if (!activeView.calculating_shadows) {
460     /* Fog applies to all display shaders */
461     glUniform1i(glGetUniformLocation(shader, "fog_active"), activeView.fog_enabled);
462     glUniform3f(glGetUniformLocation(shader, "fog_color"), activeView.fog_color[0],
463                 activeView.fog_color[1], activeView.fog_color[2]);
464     glUniform1f(glGetUniformLocation(shader, "fog_start"), activeView.fog_start);
465     glUniform1f(glGetUniformLocation(shader, "fog_end"), activeView.fog_end);
466 
467     if (type == Shader_Object || type == Shader_Water || type == Shader_Tile) {
468       glUniform3f(glGetUniformLocation(shader, "light_ambient"), activeView.light_ambient[0],
469                   activeView.light_ambient[1], activeView.light_ambient[2]);
470       glUniform3f(glGetUniformLocation(shader, "light_diffuse"), activeView.light_diffuse[0],
471                   activeView.light_diffuse[1], activeView.light_diffuse[2]);
472       if (type == Shader_Object) {
473         glUniform3f(glGetUniformLocation(shader, "light_specular"),
474                     activeView.light_specular[0], activeView.light_specular[1],
475                     activeView.light_specular[2]);
476       }
477 
478       if (activeView.day_mode) {
479         glUniform1i(glGetUniformLocation(shader, "shadow_cascade0"), 2);
480         glActiveTexture(GL_TEXTURE2);
481         glBindTexture(GL_TEXTURE_2D, activeView.cascadeTexture[0]);
482         glUniform1i(glGetUniformLocation(shader, "shadow_cascade1"), 3);
483         glActiveTexture(GL_TEXTURE3);
484         glBindTexture(GL_TEXTURE_2D, activeView.cascadeTexture[1]);
485         glUniform1i(glGetUniformLocation(shader, "shadow_cascade2"), 4);
486         glActiveTexture(GL_TEXTURE4);
487         glBindTexture(GL_TEXTURE_2D, activeView.cascadeTexture[2]);
488 
489         const int N = 3;
490         GLfloat cscmvp[N * 16], cscmodel[N * 16];
491         for (int i = 0; i < N; i++) {
492           Matrix4d cmvp;
493           matrixMult(activeView.cascade_model[i], activeView.cascade_proj[i], cmvp);
494           for (int j = 0; j < 4; j++) {
495             for (int k = 0; k < 4; k++) {
496               cscmvp[16 * i + 4 * j + k] = cmvp[j][k];
497               cscmodel[16 * i + 4 * j + k] = activeView.cascade_model[i][j][k];
498             }
499           }
500         }
501         glUniformMatrix4fv(glGetUniformLocation(shader, "cascade_mvp"), 3, GL_FALSE,
502                            (GLfloat *)cscmvp);
503         glUniformMatrix4fv(glGetUniformLocation(shader, "cascade_model"), 3, GL_FALSE,
504                            (GLfloat *)cscmodel);
505 
506         glUniform3f(glGetUniformLocation(shader, "sun_direction"), activeView.sun_direction[0],
507                     activeView.sun_direction[1], activeView.sun_direction[2]);
508       } else {
509         glUniform1i(glGetUniformLocation(shader, "shadow_map"), 1);
510         glActiveTexture(GL_TEXTURE1);
511         glBindTexture(GL_TEXTURE_CUBE_MAP, activeView.shadowMapTexture);
512 
513         glUniform3f(glGetUniformLocation(shader, "light_position"),
514                     activeView.light_position[0], activeView.light_position[1],
515                     activeView.light_position[2]);
516       }
517     }
518     if (type != Shader_Line) { glActiveTexture(GL_TEXTURE0 + 0); }
519   }
520 }
setObjectUniforms(Color c,float sharpness,Object_Lighting lighting)521 void setObjectUniforms(Color c, float sharpness, Object_Lighting lighting) {
522   if (lastProgram == shaderObjectDay || lastProgram == shaderObjectNight) {
523     glUniformC(uniformLocations.specular_color, c);
524     glUniform1f(uniformLocations.sharpness, sharpness);
525     glUniform1i(uniformLocations.ignore_shadow, lighting == Lighting_Regular);
526     glUniform1i(uniformLocations.use_lighting, lighting != Lighting_None);
527   }
528 }
529 
countObjectSpherePoints(int * ntriangles,int * nvertices,int detail)530 void countObjectSpherePoints(int *ntriangles, int *nvertices, int detail) {
531   if (detail < 1) {
532     warning("Sphere detail level must be > 1");
533     *ntriangles = 0.;
534     *nvertices = 0.;
535     return;
536   }
537 
538   int radial_count = 4 * detail;
539   int nrows = 2 * detail + 1;
540   *nvertices = (radial_count + 1) * (nrows - 2) + 2;
541   *ntriangles = 2 * radial_count * (nrows - 2);
542 }
543 
constructSphereData(int detail)544 static void constructSphereData(int detail) {
545   int radial_count = 4 * detail;
546   int nrows = 2 * detail + 1;
547   int nvertices = (radial_count + 1) * (nrows - 2) + 2;
548   int ntriangles = 2 * radial_count * (nrows - 2);
549 
550   GLfloat *pts = new GLfloat[3 * nvertices];
551   GLfloat *txs = new GLfloat[2 * nvertices];
552   ushort *idxs = new ushort[3 * ntriangles];
553 
554   // Construct vertices
555   int m = 0;
556   for (int z = 1; z < nrows - 1; z++) {
557     GLfloat theta = z * M_PI / (nrows - 1);
558     for (int t = 0; t <= radial_count; t++) {
559       GLfloat phi = 2 * t * M_PI / radial_count;
560       // Stagger every second row
561       if (z % 2 == 1) phi += M_PI / radial_count;
562       txs[2 * m + 0] = phi / (2 * M_PI);
563       txs[2 * m + 1] = theta / M_PI;
564       pts[3 * m + 0] = std::sin(theta) * std::cos(phi);
565       pts[3 * m + 1] = std::sin(theta) * std::sin(phi);
566       pts[3 * m + 2] = std::cos(theta);
567       m++;
568     }
569   }
570   pts[3 * m + 0] = 0;
571   pts[3 * m + 1] = 0;
572   pts[3 * m + 2] = 1.;
573   pts[3 * m + 3] = 0;
574   pts[3 * m + 4] = 0;
575   pts[3 * m + 5] = -1.;
576   txs[2 * m + 0] = 0.5;
577   txs[2 * m + 1] = 0.0;
578   txs[2 * m + 2] = 0.5;
579   txs[2 * m + 3] = 1.0;
580 
581   // Triangulate end caps
582   for (int i = 0; i < radial_count; i++) {
583     idxs[6 * i + 0] = i;
584     idxs[6 * i + 1] = i + 1;
585     idxs[6 * i + 2] = (radial_count + 1) * (nrows - 2);
586     idxs[6 * i + 3] = (radial_count + 1) * (nrows - 3) + i + 1;
587     idxs[6 * i + 4] = (radial_count + 1) * (nrows - 3) + i;
588     idxs[6 * i + 5] = (radial_count + 1) * (nrows - 2) + 1;
589   }
590   // Triangulate body
591   ushort *base = &idxs[2 * 3 * radial_count];
592   for (int z = 1; z < nrows - 2; z++) {
593     for (int i = 0; i < radial_count; i++) {
594       base[6 * i + 0] = (z - 1) * (radial_count + 1) + i + 1;
595       base[6 * i + 1] = (z - 1) * (radial_count + 1) + i;
596       base[6 * i + 2] = z * (radial_count + 1) + i + 1;
597       base[6 * i + 3] = (z - 1) * (radial_count + 1) + i;
598       base[6 * i + 4] = z * (radial_count + 1) + i;
599       base[6 * i + 5] = z * (radial_count + 1) + i + 1;
600     }
601     base = &base[6 * radial_count];
602   }
603 
604   sphere_points[detail] = pts;
605   sphere_texcos[detail] = txs;
606   sphere_idxs[detail] = idxs;
607 }
608 
placeObjectSphere(void * data,ushort * idxs,ushort first_index,GLfloat const position[3],Matrix3d rotation,GLfloat radius,int detail,const Color & color)609 void placeObjectSphere(void *data, ushort *idxs, ushort first_index, GLfloat const position[3],
610                        Matrix3d rotation, GLfloat radius, int detail, const Color &color) {
611   if (detail < 1) {
612     warning("Sphere detail level must be > 1. Drawing nothing.");
613     return;
614   }
615   if (!sphere_points[detail]) { constructSphereData(detail); }
616 
617   int radial_count = 4 * detail;
618   int nrows = 2 * detail + 1;
619   int nvertices = (radial_count + 1) * (nrows - 2) + 2;
620   int ntriangles = 2 * radial_count * (nrows - 2);
621   for (int i = 0; i < 3 * ntriangles; i++) { idxs[i] = first_index + sphere_idxs[detail][i]; }
622 
623   /* cast to float early */
624   GLfloat rot[3][3];
625   for (int i = 0; i < 3; i++)
626     for (int j = 0; j < 3; j++) rot[i][j] = rotation[i][j];
627 
628   /* Copy and transform vertices */
629   char *pos = (char *)data;
630   GLfloat *pts = sphere_points[detail];
631   GLfloat *txs = sphere_texcos[detail];
632   for (int i = 0; i < nvertices; i++) {
633     GLfloat loc[3] = {pts[3 * i], pts[3 * i + 1], pts[3 * i + 2]};
634     GLfloat txc[2] = {txs[2 * i], txs[2 * i + 1]};
635 
636     GLfloat off[3] = {0.f, 0.f, 0.f};
637     for (int j = 0; j < 3; j++)
638       for (int k = 0; k < 3; k++) off[j] += rot[j][k] * loc[k];
639     pos += packObjectVertex(pos, position[0] + radius * off[0], position[1] + radius * off[1],
640                             position[2] + radius * off[2], txc[0], txc[1], color, off);
641   }
642 }
643 
perspectiveMatrix(GLdouble fovy_deg,GLdouble aspect,GLdouble zNear,GLdouble zFar,Matrix4d out)644 void perspectiveMatrix(GLdouble fovy_deg, GLdouble aspect, GLdouble zNear, GLdouble zFar,
645                        Matrix4d out) {
646   GLdouble f = 1. / std::tan(fovy_deg * M_PI / 360.0);
647   Matrix4d mat = {{f / aspect, 0., 0., 0.},
648                   {0., f, 0., 0.},
649                   {0., 0., (zFar + zNear) / (zNear - zFar), -1.},
650                   {0., 0., 2 * zFar * zNear / (zNear - zFar), 0.}};
651   assign(mat, out);
652 }
653 
lookAtMatrix(GLdouble eyeX,GLdouble eyeY,GLdouble eyeZ,GLdouble centerX,GLdouble centerY,GLdouble centerZ,GLdouble upX,GLdouble upY,GLdouble upZ,Matrix4d out)654 void lookAtMatrix(GLdouble eyeX, GLdouble eyeY, GLdouble eyeZ, GLdouble centerX,
655                   GLdouble centerY, GLdouble centerZ, GLdouble upX, GLdouble upY, GLdouble upZ,
656                   Matrix4d out) {
657   Coord3d eye(eyeX, eyeY, eyeZ);
658   Coord3d center(centerX, centerY, centerZ);
659   Coord3d up(upX, upY, upZ);
660   Coord3d f = center - eye;
661   f = f / length(f);
662   up = up / length(up);
663   Coord3d s = crossProduct(f, up);
664   s = s / length(s);
665   Coord3d u = crossProduct(s, f);
666 
667   Matrix4d mat = {{s[0], u[0], -f[0], 0.},
668                   {s[1], u[1], -f[1], 0.},
669                   {s[2], u[2], -f[2], 0.},
670                   {0., 0., 0., 1.}};
671   Matrix4d trans = {
672       {1., 0., 0., 0.}, {0., 1., 0., 0.}, {0., 0., 1., 0.}, {-eye[0], -eye[1], -eye[2], 1.}};
673   matrixMult(trans, mat, out);
674 }
675 
renderDummyShadowMap()676 void renderDummyShadowMap() {
677   GLenum dirs[6] = {GL_TEXTURE_CUBE_MAP_POSITIVE_X, GL_TEXTURE_CUBE_MAP_NEGATIVE_X,
678                     GL_TEXTURE_CUBE_MAP_POSITIVE_Y, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
679                     GL_TEXTURE_CUBE_MAP_POSITIVE_Z, GL_TEXTURE_CUBE_MAP_NEGATIVE_Z};
680   GLfloat buf[1] = {1.0f};
681   activeView.shadowMapTexsize = 1;
682   glBindTexture(GL_TEXTURE_CUBE_MAP, activeView.shadowMapTexture);
683   for (int face = 0; face < 6; face++) {
684     glTexImage2D(dirs[face], 0, GL_DEPTH_COMPONENT, 1, 1, 0, GL_DEPTH_COMPONENT, GL_FLOAT,
685                  buf);
686   }
687 }
688 
renderDummyShadowCascade()689 void renderDummyShadowCascade() {
690   GLfloat buf[3] = {1.0f};
691   activeView.cascadeTexsize = 1;
692   for (int i = 0; i < 3; i++) {
693     glBindTexture(GL_TEXTURE_2D, activeView.cascadeTexture[i]);
694     glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, 1, 1, 0, GL_DEPTH_COMPONENT, GL_FLOAT,
695                  buf);
696   }
697 }
698 
renderShadowMap(const Coord3d & focus,Map * mp,Game * gm)699 void renderShadowMap(const Coord3d &focus, Map *mp, Game *gm) {
700   Matrix4d origMV, origProj;
701   assign(activeView.modelview, origMV);
702   assign(activeView.projection, origProj);
703 
704   activeView.calculating_shadows = true;
705   static int ltexsize = 0;
706   if (activeView.shadowMapTexsize <= 1 || ltexsize != Settings::settings->shadowTexsize) {
707     ltexsize = Settings::settings->shadowTexsize;
708     /* order doesn't matter */
709     GLenum dirs[6] = {GL_TEXTURE_CUBE_MAP_POSITIVE_X, GL_TEXTURE_CUBE_MAP_NEGATIVE_X,
710                       GL_TEXTURE_CUBE_MAP_POSITIVE_Y, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
711                       GL_TEXTURE_CUBE_MAP_POSITIVE_Z, GL_TEXTURE_CUBE_MAP_NEGATIVE_Z};
712 
713     GLint maxSize;
714     glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxSize);
715     GLint reqSize = 1 << Settings::settings->shadowTexsize;
716     activeView.shadowMapTexsize = std::min(maxSize, reqSize);
717     for (uint face = 0; face < 6; face++) {
718       glTexImage2D(dirs[face], 0, GL_DEPTH_COMPONENT, activeView.shadowMapTexsize,
719                    activeView.shadowMapTexsize, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);
720     }
721   }
722 
723   GLdouble norv[6][3] = {{1, 0, 0}, {-1, 0, 0}, {0, 1, 0}, {0, -1, 0}, {0, 0, 1}, {0, 0, -1}};
724   GLdouble upv[6][3] = {{0, -1, 0}, {0, -1, 0}, {0, 0, 1}, {0, 0, -1}, {0, -1, 0}, {0, -1, 0}};
725   /* order doesn't matter */
726   GLenum dirs[6] = {GL_TEXTURE_CUBE_MAP_POSITIVE_X, GL_TEXTURE_CUBE_MAP_NEGATIVE_X,
727                     GL_TEXTURE_CUBE_MAP_POSITIVE_Y, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
728                     GL_TEXTURE_CUBE_MAP_POSITIVE_Z, GL_TEXTURE_CUBE_MAP_NEGATIVE_Z};
729 
730   perspectiveMatrix(90., 1, 0.1, 1000., activeView.projection);
731   glEnable(GL_CULL_FACE);
732   glEnable(GL_DEPTH_TEST);
733   glDepthFunc(GL_LEQUAL);
734   glViewport(0, 0, activeView.shadowMapTexsize, activeView.shadowMapTexsize);
735 
736   GLuint cubeFBOs[6];
737   glGenFramebuffers(6, cubeFBOs);
738   for (int i = 0; i < 6; i++) {
739     /* issue: only fbo 1 is set? */
740     glBindFramebuffer(GL_DRAW_FRAMEBUFFER, cubeFBOs[i]);
741     glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, dirs[i],
742                            activeView.shadowMapTexture, 0);
743     GLenum result = glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER);
744     if (GL_FRAMEBUFFER_COMPLETE != result) {
745       warning("Framebuffer is not complete. #%d w/err %x", i, result);
746     }
747   }
748 
749   glClearColor(0.5f, 0.5f, 0.5f, 0.5f);
750   // set Persp to square + angle
751   for (int loop = 0; loop < 6; ++loop) {
752     glBindFramebuffer(GL_DRAW_FRAMEBUFFER, cubeFBOs[loop]);
753     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
754     lookAtMatrix(activeView.light_position[0], activeView.light_position[1],
755                  activeView.light_position[2], activeView.light_position[0] + norv[loop][0],
756                  activeView.light_position[1] + norv[loop][1],
757                  activeView.light_position[2] + norv[loop][2], upv[loop][0], upv[loop][1],
758                  upv[loop][2], activeView.modelview);
759     markViewChanged();
760     // Render (todo: 50% alpha clip)
761     if (mp) mp->draw(0, focus, gm ? gm->gameTime : 0.);
762     if (gm) gm->draw();
763   }
764 
765   /* back to default (to screen) frame buffer */
766   glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
767   glDeleteFramebuffers(6, cubeFBOs);
768 
769   activeView.calculating_shadows = false;
770   assign(origMV, activeView.modelview);
771   assign(origProj, activeView.projection);
772   markViewChanged();
773 }
774 
renderShadowCascade(const Coord3d & focus,Map * mp,Game * gm)775 void renderShadowCascade(const Coord3d &focus, Map *mp, Game *gm) {
776   Matrix4d origMV, origProj;
777   assign(activeView.modelview, origMV);
778   assign(activeView.projection, origProj);
779 
780   const int N = 3;
781 
782   GLfloat proj_half_angle = 40 / 2 * M_PI / 180;
783   GLfloat aspect = (GLdouble)screenWidth / (GLdouble)std::max(screenHeight, 1);
784 
785   /* shadow step scale by factor of 4 */
786   GLfloat dts[N + 1] = {0.f, 12.5f, 50.f, 200.f};
787   /* Construct ortho projection matrix clipped near at 0, far at 1000. */
788   Matrix4d ortho = {{1, 0, 0, 0}, {0, 1, 0, 0}, {0, 0, -2. / 100., -1}, {0, 0, 0, 1}};
789   assign(ortho, activeView.projection);
790 
791   /* Construct light model matrices */
792   Matrix4d mvmt;
793   for (int i = 0; i < 4; i++)
794     for (int j = 0; j < 4; j++) mvmt[i][j] = origMV[j][i];
795   /* extract rotation operation and camera center */
796   Matrix3d mvm3t;
797   for (int k = 0; k < 3; k++)
798     for (int j = 0; j < 3; j++) mvm3t[j][k] = mvmt[k][j];
799   Coord3d cc(mvmt[0][3], mvmt[1][3], mvmt[2][3]);
800   Coord3d camera = -useMatrix(mvm3t, cc);
801 
802   Matrix4d light_align;
803   lookAtMatrix(0, 0, 0, activeView.sun_direction[0], activeView.sun_direction[1],
804                activeView.sun_direction[2], 0, 0, 1, light_align);
805   Matrix3d light_align3;
806   for (int k = 0; k < 3; k++)
807     for (int j = 0; j < 3; j++) light_align3[j][k] = light_align[k][j];
808 
809   for (int i = 0; i < N; i++) {
810     double bounds[3][2] = {{1e99, -1e99}, {1e99, -1e99}, {1e99, -1e99}};
811 
812     /* Estimate orthogonal projection matrix onto frustum */
813     for (int j = 0; j < 2; j++) {
814       double eyepts[3] = {std::tan(proj_half_angle) * dts[i + j],
815                           aspect * std::tan(proj_half_angle) * dts[i + j], dts[i + j]};
816       for (int k = 0; k < 4; k++) {
817         eyepts[k % 2] *= -1;
818         /* convert to world space */
819         Coord3d resultA = useMatrix(mvm3t, Coord3d(eyepts)) + camera;
820 
821         /* reorient along light space. NOTE: this breaks total offset, but we don't care about
822          * it...*/
823         Coord3d result = useMatrix(light_align3, resultA);
824 
825         for (int m = 0; m < 3; m++) {
826           bounds[m][0] = std::min(bounds[m][0], result[m]);
827           bounds[m][1] = std::max(bounds[m][1], result[m]);
828         }
829       }
830     }
831     double dx = bounds[0][1] - bounds[0][0], dy = bounds[1][1] - bounds[1][0];  //,
832     double rx = std::max(dx, dy);
833     double ry = std::max(dx, dy);
834     Coord3d midpoint(0, 0, -0.5 * (dts[i] + dts[i + 1]));
835     Coord3d mmpt = useMatrix(mvm3t, midpoint) + camera;
836 
837     double s = 50; /* rel. the 100 of range */
838     Coord3d sundir(activeView.sun_direction[0], activeView.sun_direction[1],
839                    activeView.sun_direction[2]);
840     Coord3d light_camera = mmpt - s * sundir;
841     lookAtMatrix(light_camera[0], light_camera[1], light_camera[2], mmpt[0], mmpt[1], mmpt[2],
842                  0, 0, 1, activeView.cascade_model[i]);
843 
844     /* like glOrtho; -L=R=rx, -B=T=ry, N=0,F=1000 */
845     const double F = 1000.;
846     Matrix4d orthoMtx = {
847         {1 / rx, 0, 0, 0}, {0, 1 / ry, 0, 0}, {0, 0, -2. / F, 0}, {0, 0, 0, 1}};
848     assign(orthoMtx, activeView.cascade_proj[i]);
849   }
850 
851   /* Render cascade map using the given matrices */
852   static int ltexsize = 0;
853   if (activeView.cascadeTexsize <= 1 || ltexsize != Settings::settings->shadowTexsize) {
854     ltexsize = Settings::settings->shadowTexsize;
855     /* order doesn't matter */
856     GLint maxSize;
857     glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxSize);
858     GLint reqSize = 1 << Settings::settings->shadowTexsize;
859     activeView.cascadeTexsize = std::min(maxSize, reqSize);
860   }
861   for (int i = 0; i < N; i++) {
862     glBindTexture(GL_TEXTURE_2D, activeView.cascadeTexture[i]);
863     glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, activeView.cascadeTexsize,
864                  activeView.cascadeTexsize, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);
865   }
866 
867   glEnable(GL_CULL_FACE);
868   glEnable(GL_DEPTH_TEST);
869   glDepthFunc(GL_LEQUAL);
870   glViewport(0, 0, activeView.cascadeTexsize, activeView.cascadeTexsize);
871   activeView.calculating_shadows = true;
872 
873   GLuint cascadeFBOs[N];
874   glGenFramebuffers(N, cascadeFBOs);
875   for (int i = 0; i < N; i++) {
876     glBindFramebuffer(GL_DRAW_FRAMEBUFFER, cascadeFBOs[i]);
877     glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D,
878                            activeView.cascadeTexture[i], 0);
879     GLenum result = glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER);
880     if (GL_FRAMEBUFFER_COMPLETE != result) {
881       warning("Framebuffer is not complete. #%d w/err %x", i, result);
882     }
883   }
884 
885   glClearColor(0.5f, 0.5f, 0.5f, 0.5f);
886   // set Persp to square + angle
887   for (int loop = 0; loop < N; ++loop) {
888     glBindFramebuffer(GL_DRAW_FRAMEBUFFER, cascadeFBOs[loop]);
889     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
890     assign(activeView.cascade_model[loop], activeView.modelview);
891     assign(activeView.cascade_proj[loop], activeView.projection);
892     markViewChanged();
893     if (mp) mp->draw(0, focus, gm ? gm->gameTime : 0.);
894     if (gm) gm->draw();
895   }
896 
897   /* back to default (to screen) frame buffer */
898   glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
899   glDeleteFramebuffers(N, cascadeFBOs);
900 
901   assign(origMV, activeView.modelview);
902   assign(origProj, activeView.projection);
903   activeView.calculating_shadows = false;
904   markViewChanged();
905 }
906 
907 /* generates a snapshot of the screen */
createSnapshot()908 int createSnapshot() {
909   static int snap_number = 0;
910   char name[1024];
911   int again = 9999;
912 
913   /* find the name for the image */
914   do {
915     snprintf(name, 1023, "./snapshot_%04d.png", snap_number++);
916     FILE *f;
917     if ((f = fopen(name, "r")) == NULL) {
918       break;
919     } else {
920       fclose(f);
921       again--;
922     }
923   } while (again > 0);
924 
925   /* Check against symlinks */
926   if (pathIsLink(name)) {
927     warning("file %s is a symlink.", name);
928     return 0;
929   }
930 
931   /* allocate buffer */
932   unsigned char *buffer = new unsigned char[screenWidth * screenHeight * 4];
933   unsigned char *linebuffer = new unsigned char[screenWidth * 4];
934   Uint32 mask[4];
935 #if SDL_BYTEORDER == SDL_LIL_ENDIAN /* OpenGL RGBA masks */
936   mask[0] = 0x000000FF;
937   mask[1] = 0x0000FF00;
938   mask[2] = 0x00FF0000;
939   mask[3] = 0xFF000000;
940 #else
941   mask[0] = 0xFF000000;
942   mask[1] = 0x00FF0000;
943   mask[2] = 0x0000FF00;
944   mask[3] = 0x000000FF;
945 #endif
946 
947   /* get the screen */
948   glReadPixels(0, 0, screenWidth, screenHeight, GL_RGBA, GL_UNSIGNED_BYTE, (GLvoid *)buffer);
949   /* Flip pixels vertically */
950   for (int i = 0; i < screenHeight / 2; i++) {
951     memcpy(linebuffer, &buffer[i * screenWidth * 4], screenWidth * 4);
952     memcpy(&buffer[i * screenWidth * 4], &buffer[(screenHeight - 1 - i) * screenWidth * 4],
953            screenWidth * 4);
954     memcpy(&buffer[(screenHeight - 1 - i) * screenWidth * 4], linebuffer, screenWidth * 4);
955   }
956   /* Construct surface and save */
957   SDL_Surface *surf =
958       SDL_CreateRGBSurfaceFrom(buffer, screenWidth, screenHeight, 32, screenWidth * 4, mask[0],
959                                mask[1], mask[2], mask[3]);
960   if (IMG_SavePNG(surf, name)) { warning("Failed to save screenshot to %s", name); }
961   SDL_FreeSurface(surf);
962   /* Cleanup */
963   delete[] buffer;
964   delete[] linebuffer;
965   return 1;
966 }
967 
968 /* Displays a centered semi-transparent sign with two text rows */
message(char * A,char * B)969 void message(char *A, char *B) {
970   int size = 16;
971   int w1, w2, h1, h2, w;
972   w1 = getTextWidth(A, size);
973   w2 = getTextWidth(B, size);
974   h1 = 32;
975   h2 = 32;
976 
977   w = std::max(w1, w2) + 20;
978 
979   int x1 = screenWidth / 2 - w / 2, x2 = screenWidth / 2 + w / 2;
980   int y1 = screenHeight / 2 - h1 - 5, y2 = screenHeight / 2 + h2 + 5;
981   Enter2DMode();
982   draw2DRectangle(x1, y1, x2 - x1, y2 - y1, 0., 0., 1., 1., 0.2, 0.5, 0.2, 0.5);
983 
984   drawCenterSimpleText(A, screenWidth / 2, screenHeight / 2 - size, size, 0.5, 1.0, 0.2, 1.0);
985   drawCenterSimpleText(B, screenWidth / 2, screenHeight / 2 + 14, size, 0.5, 1.0, 0.2, 1.0);
986 
987   Leave2DMode();
988 }
989 
multiMessage(int nlines,const char * left[],const char * right[])990 void multiMessage(int nlines, const char *left[], const char *right[]) {
991   int total_height, width, h_now;
992   int size = 16;
993 
994   total_height = 0;
995   for (int i = 0; i < nlines; i++) { total_height += size * 2; }
996   width = 600;
997 
998   int x1 = screenWidth / 2 - width / 2 - 5, x2 = screenWidth / 2 + width / 2 + 5;
999   int y1 = screenHeight / 2 - total_height / 2 - 5,
1000       y2 = screenHeight / 2 + total_height / 2 + 30;
1001 
1002   Enter2DMode();
1003   draw2DRectangle(x1, y1, x2 - x1, y2 - y1, 0., 0., 1., 1., 0.2, 0.5, 0.2, 0.5);
1004 
1005   h_now = -size;
1006   for (int i = 0; i < nlines; i++) {
1007     h_now += 2 * size;
1008     if (left[i]) {
1009       drawSimpleText(left[i], screenWidth / 2 - width / 2,
1010                      screenHeight / 2 - total_height / 2 + h_now, size, 0.5, 1.0, 0.2, 1.0);
1011     }
1012     if (right[i]) {
1013       drawRightSimpleText(right[i], screenWidth / 2 + width / 2,
1014                           screenHeight / 2 - total_height / 2 + h_now, size, 0.5, 1.0, 0.2,
1015                           1.0);
1016     }
1017   }
1018   Leave2DMode();
1019 }
1020 
1021 /** Loads a texture from file and returns a reference to it.
1022     It is safe to load the same texture multiple times since the results are cached
1023 */
loadTexture(const char * name)1024 int loadTexture(const char *name) {
1025   /* Check in cache if texture already loaded */
1026   for (int i = 0; i < numTextures; i++)
1027     if (strcmp(textureNames[i], name) == 0) return i;
1028 
1029   if (numTextures >= GLHELP_MAX_TEXTURES) {
1030     warning("Warning: max. number of textures reached (%d). Texture '%s' not loaded.",
1031             GLHELP_MAX_TEXTURES, name);
1032     return 0;
1033   }
1034 
1035   char str[256];
1036   snprintf(str, sizeof(str), "%s/images/%s", effectiveShareDir, name);
1037   SDL_Surface *surface = IMG_Load(str);
1038   if (!surface) {
1039     warning("Failed to load texture %s", str);
1040     // Override texture name... (to avoid carrying on outdated tex names)
1041     textureNames[numTextures] = strdup(textureNames[0]);
1042     textures[numTextures++] =
1043         textures[0];  // just assume we managed to load this, better than nothing
1044     return -1;
1045   } else {
1046     textureNames[numTextures] = strdup(name);
1047     GLfloat texCoord[4];
1048     textures[numTextures] = LoadTexture(surface, texCoord);
1049     numTextures++;
1050     SDL_FreeSurface(surface);
1051   }
1052   return (numTextures - 1);  // ok
1053 }
1054 
filetobuf(const char * filename)1055 char *filetobuf(const char *filename) {
1056   FILE *fptr = fopen(filename, "rb");
1057   if (!fptr) return NULL;
1058   fseek(fptr, 0, SEEK_END);
1059   long length = ftell(fptr);
1060   char *buf = (char *)malloc(length + 1);
1061   fseek(fptr, 0, SEEK_SET);
1062   fread(buf, length, 1, fptr);
1063   fclose(fptr);
1064   buf[length] = 0;
1065   return buf;
1066 }
1067 
loadShaderPart(const char * name,GLuint shader_type)1068 static GLuint loadShaderPart(const char *name, GLuint shader_type) {
1069   char path[256];
1070   const char *tdesc = NULL;
1071   if (shader_type == GL_VERTEX_SHADER) {
1072     tdesc = "Vertex";
1073   } else if (shader_type == GL_FRAGMENT_SHADER) {
1074     tdesc = "Fragment";
1075   }
1076 
1077   snprintf(path, 256, "%s/shaders/%s", effectiveShareDir, name);
1078   GLchar *source = filetobuf(path);
1079   if (source == NULL) { error("%s shader %s could not be read", tdesc, path); }
1080   GLuint shader = glCreateShader(shader_type);
1081   if (shader == 0) { error("Failed to even create shader object"); }
1082   glShaderSource(shader, 1, (const GLchar **)&source, 0);
1083   free(source);
1084 
1085   glCompileShader(shader);
1086   int isCompiled = 0;
1087   glGetShaderiv(shader, GL_COMPILE_STATUS, &isCompiled);
1088   if (isCompiled == 0) {
1089     int maxLength = 0;
1090     glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &maxLength);
1091     char *vertexInfoLog = (char *)malloc(maxLength);
1092     glGetShaderInfoLog(shader, maxLength, &maxLength, vertexInfoLog);
1093     warning("%s shader %s error (len %d): %s", tdesc, name, maxLength, vertexInfoLog);
1094     glDeleteShader(shader);
1095     free(vertexInfoLog);
1096     return 0;
1097   }
1098   return shader;
1099 }
1100 
linkShader(GLuint vertexshader,GLuint fragmentshader,const char * descr)1101 static GLuint linkShader(GLuint vertexshader, GLuint fragmentshader, const char *descr) {
1102   GLuint shaderprogram = glCreateProgram();
1103   glAttachShader(shaderprogram, vertexshader);
1104   glAttachShader(shaderprogram, fragmentshader);
1105   /* Shaders use attribs 1..k for some k */
1106   glBindAttribLocation(shaderprogram, 0, "in_Position");
1107   glBindAttribLocation(shaderprogram, 1, "in_Color");
1108   glBindAttribLocation(shaderprogram, 2, "in_Texcoord");
1109   glBindAttribLocation(shaderprogram, 3, "in_Normal");
1110   glBindAttribLocation(shaderprogram, 4, "in_Velocity");
1111   glLinkProgram(shaderprogram);
1112   int is_linked;
1113   glGetProgramiv(shaderprogram, GL_LINK_STATUS, (int *)&is_linked);
1114   glDeleteShader(vertexshader);
1115   glDeleteShader(fragmentshader);
1116   if (is_linked == 0) {
1117     int maxLength = 0;
1118     glGetProgramiv(shaderprogram, GL_INFO_LOG_LENGTH, &maxLength);
1119     char *shaderProgramInfoLog = (char *)malloc(maxLength);
1120     glGetProgramInfoLog(shaderprogram, maxLength, &maxLength, shaderProgramInfoLog);
1121     warning("Program (%s) link error (len %d): %s", descr, maxLength, shaderProgramInfoLog);
1122     free(shaderProgramInfoLog);
1123     return 0;
1124   }
1125   return shaderprogram;
1126 }
1127 
glHelpInit()1128 void glHelpInit() {
1129   warnForGLerrors("preGLinit");
1130 
1131   TTF_Init();
1132   char str[256];
1133   snprintf(str, sizeof(str), "%s/fonts/%s", effectiveShareDir, "menuFont.ttf");
1134   ingameFont = TTF_OpenFont(str, 30);
1135   if (!ingameFont) { error("failed to load font %s", str); }
1136 
1137   /* Note: all textures must be powers of 2 since we ignore texcoords */
1138   loadTexture("ice.png");
1139   loadTexture("acid.png");
1140   loadTexture("sand.png");
1141   textureTrack = textures[loadTexture("track.png")];
1142   loadTexture("texture.png");
1143   loadTexture("texture2.png");
1144   loadTexture("texture3.png");
1145   loadTexture("texture4.png");
1146   textureWings = textures[loadTexture("wings.png")];
1147   textureMousePointer = textures[loadTexture("mousePointer.png")];
1148   textureGlitter = textures[loadTexture("glitter.png")];
1149   textureDizzy = textures[loadTexture("dizzy.png")];
1150   textureBlank = textures[loadTexture("blank.png")];
1151 
1152   sparkle2D = new Sparkle2D();
1153 
1154   // Errors handled in loadShaderPart and linkShader
1155   GLuint vBasic = loadShaderPart("basic.vert", GL_VERTEX_SHADER);
1156   GLuint vLine = loadShaderPart("line.vert", GL_VERTEX_SHADER);
1157   GLuint vWater = loadShaderPart("water.vert", GL_VERTEX_SHADER);
1158   GLuint vUI = loadShaderPart("ui.vert", GL_VERTEX_SHADER);
1159   GLuint vObject = loadShaderPart("object.vert", GL_VERTEX_SHADER);
1160   GLuint vReflection = loadShaderPart("reflection.vert", GL_VERTEX_SHADER);
1161 
1162   GLuint fBasicDay = loadShaderPart("basic_day.frag", GL_FRAGMENT_SHADER);
1163   GLuint fBasicNight = loadShaderPart("basic_night.frag", GL_FRAGMENT_SHADER);
1164   GLuint fBasicShadow = loadShaderPart("basic_shadow.frag", GL_FRAGMENT_SHADER);
1165   GLuint fLine = loadShaderPart("line.frag", GL_FRAGMENT_SHADER);
1166   GLuint fWaterDay = loadShaderPart("water_day.frag", GL_FRAGMENT_SHADER);
1167   GLuint fWaterNight = loadShaderPart("water_night.frag", GL_FRAGMENT_SHADER);
1168   GLuint fUI = loadShaderPart("ui.frag", GL_FRAGMENT_SHADER);
1169   GLuint fObjectDay = loadShaderPart("object_day.frag", GL_FRAGMENT_SHADER);
1170   GLuint fObjectNight = loadShaderPart("object_night.frag", GL_FRAGMENT_SHADER);
1171   GLuint fObjectShadow = loadShaderPart("object_shadow.frag", GL_FRAGMENT_SHADER);
1172   GLuint fReflection = loadShaderPart("reflection.frag", GL_FRAGMENT_SHADER);
1173 
1174   shaderTileDay = linkShader(vBasic, fBasicDay, "BasicDay");
1175   shaderTileNight = linkShader(vBasic, fBasicNight, "BasicNight");
1176   shaderTileShadow = linkShader(vBasic, fBasicShadow, "BasicShadow");
1177   shaderLine = linkShader(vLine, fLine, "Line");
1178   shaderWaterDay = linkShader(vWater, fWaterDay, "WaterDay");
1179   shaderWaterNight = linkShader(vWater, fWaterNight, "WaterNight");
1180   shaderUI = linkShader(vUI, fUI, "UI");
1181   shaderObjectDay = linkShader(vObject, fObjectDay, "ObjectDay");
1182   shaderObjectNight = linkShader(vObject, fObjectNight, "ObjectNight");
1183   shaderObjectShadow = linkShader(vObject, fObjectShadow, "ObjectShadow");
1184   shaderReflection = linkShader(vReflection, fReflection, "Reflection");
1185 
1186   glDeleteShader(vBasic);
1187   glDeleteShader(vLine);
1188   glDeleteShader(vWater);
1189   glDeleteShader(vUI);
1190   glDeleteShader(vObject);
1191   glDeleteShader(vReflection);
1192 
1193   glDeleteShader(fBasicDay);
1194   glDeleteShader(fBasicNight);
1195   glDeleteShader(fBasicShadow);
1196   glDeleteShader(fLine);
1197   glDeleteShader(fWaterDay);
1198   glDeleteShader(fWaterNight);
1199   glDeleteShader(fUI);
1200   glDeleteShader(fObjectDay);
1201   glDeleteShader(fObjectNight);
1202   glDeleteShader(fObjectShadow);
1203   glDeleteShader(fReflection);
1204 
1205   // Load uniform locations
1206   uniformLocations.ui_screen_width = glGetUniformLocation(shaderUI, "screen_width");
1207   uniformLocations.ui_screen_height = glGetUniformLocation(shaderUI, "screen_height");
1208   uniformLocations.ui_tex = glGetUniformLocation(shaderUI, "tex");
1209 
1210   // Wipe ball cache
1211   for (int i = 0; i < MAX_BALL_DETAIL; i++) {
1212     sphere_points[i] = NULL;
1213     sphere_texcos[i] = NULL;
1214     sphere_idxs[i] = NULL;
1215   }
1216 
1217   // Setup view state
1218   activeView.fog_enabled = 0;
1219   activeView.fog_color[0] = 1.f;
1220   activeView.fog_color[1] = 1.f;
1221   activeView.fog_color[2] = 1.f;
1222   activeView.fog_start = 10.f;
1223   activeView.fog_end = 20.f;
1224 
1225   activeView.light_position[0] = 0.;
1226   activeView.light_position[1] = 0.;
1227   activeView.light_position[2] = 10.;
1228   activeView.light_ambient[0] = 0.2f;
1229   activeView.light_ambient[1] = 0.2f;
1230   activeView.light_ambient[2] = 0.2f;
1231   activeView.light_diffuse[0] = 1.f;
1232   activeView.light_diffuse[1] = 1.f;
1233   activeView.light_diffuse[2] = 1.f;
1234   activeView.light_specular[0] = 0.5f;
1235   activeView.light_specular[1] = 0.5f;
1236   activeView.light_specular[2] = 0.5f;
1237   activeView.sun_direction[0] = 6 / 11.;
1238   activeView.sun_direction[1] = 34 / 121.;
1239   activeView.sun_direction[2] = -93 / 121.;
1240 
1241   activeView.calculating_shadows = false;
1242   activeView.show_flag_state = false;
1243 
1244   glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS);
1245   glGenTextures(1, &activeView.shadowMapTexture);
1246   glBindTexture(GL_TEXTURE_CUBE_MAP, activeView.shadowMapTexture);
1247   glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_BASE_LEVEL, 0);
1248   glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAX_LEVEL, 0);
1249   glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
1250   glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
1251   glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
1252   glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
1253   glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
1254 
1255   /* We don't use an array for the cascade texture because it reqs.
1256    * more shaders/etc */
1257   activeView.cascadeTexture[0] = 0;
1258   activeView.cascadeTexture[1] = 0;
1259   activeView.cascadeTexture[2] = 0;
1260 
1261   glGenTextures(3, activeView.cascadeTexture);
1262   for (int i = 0; i < 3; i++) {
1263     glBindTexture(GL_TEXTURE_2D, activeView.cascadeTexture[i]);
1264 
1265     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);
1266     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
1267     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
1268     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
1269   }
1270 
1271   activeView.day_mode = true;
1272 
1273   renderDummyShadowCascade();
1274   renderDummyShadowMap();
1275   warnForGLerrors("postGLinit");
1276 }
glHelpCleanup()1277 void glHelpCleanup() {
1278   if (shaderTileDay) glDeleteProgram(shaderTileDay);
1279   if (shaderTileNight) glDeleteProgram(shaderTileNight);
1280   if (shaderTileShadow) glDeleteProgram(shaderTileShadow);
1281   if (shaderLine) glDeleteProgram(shaderLine);
1282   if (shaderWaterDay) glDeleteProgram(shaderWaterDay);
1283   if (shaderWaterNight) glDeleteProgram(shaderWaterNight);
1284   if (shaderUI) glDeleteProgram(shaderUI);
1285   if (shaderObjectDay) glDeleteProgram(shaderObjectDay);
1286   if (shaderObjectNight) glDeleteProgram(shaderObjectNight);
1287   if (shaderObjectShadow) glDeleteProgram(shaderObjectShadow);
1288   if (shaderReflection) glDeleteProgram(shaderReflection);
1289   shaderLine = 0;
1290   shaderTileDay = 0;
1291   shaderTileNight = 0;
1292   shaderTileShadow = 0;
1293   shaderWaterDay = 0;
1294   shaderWaterNight = 0;
1295   shaderUI = 0;
1296   shaderObjectDay = 0;
1297   shaderObjectNight = 0;
1298   shaderObjectShadow = 0;
1299 
1300   if (sparkle2D) delete sparkle2D;
1301 
1302   for (int i = 0; i < MAX_BALL_DETAIL; i++) {
1303     if (sphere_points[i]) delete[] sphere_points[i];
1304     if (sphere_texcos[i]) delete[] sphere_texcos[i];
1305     if (sphere_idxs[i]) delete[] sphere_idxs[i];
1306   }
1307 
1308   /* Invalidate all strings so they get cleaned up */
1309   update2DStringCache(true);
1310 
1311   for (std::map<int, TTF_Font *>::iterator i = menuFontLookup.begin();
1312        i != menuFontLookup.end(); ++i) {
1313     TTF_CloseFont(i->second);
1314   }
1315   menuFontLookup.clear();
1316   TTF_CloseFont(ingameFont);
1317   ingameFont = 0;
1318 
1319   for (int i = 0; i < numTextures; i++) { glDeleteTextures(1, &textures[i]); }
1320 }
1321 
warnForGLerrors(const char * where_am_i)1322 void warnForGLerrors(const char *where_am_i) {
1323   GLenum err;
1324   while ((err = glGetError()) != GL_NO_ERROR) {
1325     const char *type = NULL;
1326     switch (err) {
1327     default:
1328       break;
1329     case GL_INVALID_ENUM:
1330       type = "Invalid enum";
1331       break;
1332     case GL_INVALID_VALUE:
1333       type = "Invalid value";
1334       break;
1335     case GL_INVALID_OPERATION:
1336       type = "Invalid operation";
1337       break;
1338     }
1339 
1340     warning("GL error %x (%s) at location: %s", err, type, where_am_i);
1341   }
1342 }
1343 
1344 /* Calculates and displays current framerate */
displayFrameRate()1345 void displayFrameRate() {
1346   /* Frame rate is given for the previous period. */
1347   double td = getTimeDifference(lastDisplayStartTime, displayStartTime) / timeDilationFactor;
1348   if (td > 1.0) {
1349     fps = 1.0;
1350   } else if (td <= 1e-4) {
1351     static int hasWarned = 0;
1352     if (!hasWarned) {
1353       warning("Warning: too fast framerate (%f)", td);
1354       hasWarned = 1;
1355     }
1356   } else {
1357     fps = fps * 0.95 + 0.05 / td;
1358   }
1359 
1360   if (Settings::settings->showFPS > 0) {
1361     char slow[256];
1362     char fast[256];
1363     if (Settings::settings->showFPS == 1) {
1364       snprintf(slow, sizeof(slow), "%s ", _("Framerate:"));
1365       if (fps > 0) {
1366         snprintf(fast, sizeof(fast), "%.1f", fps);
1367       } else {
1368         snprintf(fast, sizeof(fast), _("unknown"));
1369       }
1370     } else {
1371       snprintf(slow, sizeof(slow), "%s ", _("ms/Frame:"));
1372       snprintf(fast, sizeof(fast), "%.1f", 1e3 * td);
1373     }
1374     TTF_Font *active = menuFontForSize(10);
1375     int w1 = draw2DString(active, slow, 15, screenHeight - 15, menuColor[0], menuColor[1],
1376                           menuColor[2], menuColor[3], true, 0, 0);
1377     draw2DString(active, fast, 15 + w1, screenHeight - 15, menuColor[0], menuColor[1],
1378                  menuColor[2], menuColor[3], true, 0, 0);
1379   }
1380 }
1381 
1382 /*********************/
1383 /* Matrix operations */
1384 /*********************/
1385 
debugMatrix(Matrix4d m)1386 void debugMatrix(Matrix4d m) {
1387   warning("%8.5f \t%8.5f \t%8.5f \t%8.5f", m[0][0], m[0][1], m[0][2], m[0][3]);
1388   warning("%8.5f \t%8.5f \t%8.5f \t%8.5f", m[1][0], m[1][1], m[1][2], m[1][3]);
1389   warning("%8.5f \t%8.5f \t%8.5f \t%8.5f", m[2][0], m[2][1], m[2][2], m[2][3]);
1390   warning("%8.5f \t%8.5f \t%8.5f \t%8.5f", m[3][0], m[3][1], m[3][2], m[3][3]);
1391 }
1392 
1393 /* C <- A * B */
matrixMult(const Matrix4d A,const Matrix4d B,Matrix4d C)1394 void matrixMult(const Matrix4d A, const Matrix4d B, Matrix4d C) {
1395   for (int i = 0; i < 4; i++)
1396     for (int j = 0; j < 4; j++) {
1397       C[i][j] = 0.0;
1398       for (int k = 0; k < 4; k++) C[i][j] += A[i][k] * B[k][j];
1399     }
1400 }
1401 
1402 /* C <- A(B) */
useMatrix(Matrix4d A,const Coord3d & B)1403 Coord3d useMatrix(Matrix4d A, const Coord3d &B) {
1404   Coord3d C;
1405   for (int i = 0; i < 3; i++) {
1406     C[i] = A[i][3];
1407     for (int k = 0; k < 3; k++) C[i] += A[i][k] * B[k];
1408   }
1409   double h = A[3][3];
1410   for (int k = 0; k < 3; k++) h += A[3][k];
1411   for (int k = 0; k < 3; k++) C[k] /= h;
1412   return C;
1413 }
1414 
1415 /* C <- A(B) */
useMatrix(Matrix3d A,const Coord3d & B)1416 Coord3d useMatrix(Matrix3d A, const Coord3d &B) {
1417   Coord3d C;
1418   for (int i = 0; i < 3; i++) {
1419     for (int k = 0; k < 3; k++) C[i] += A[i][k] * B[k];
1420   }
1421   return C;
1422 }
1423 
1424 /* C <- A */
assign(const Matrix4d A,Matrix4d C)1425 void assign(const Matrix4d A, Matrix4d C) {
1426   for (int i = 0; i < 4; i++)
1427     for (int j = 0; j < 4; j++) C[i][j] = A[i][j];
1428 }
1429 
1430 /* C <- A */
identityMatrix(Matrix4d m)1431 void identityMatrix(Matrix4d m) {
1432   for (int i = 0; i < 4; i++)
1433     for (int j = 0; j < 4; j++) m[i][j] = i == j ? 1.0 : 0.0;
1434 }
1435 
rotateX(double v,Matrix4d m)1436 void rotateX(double v, Matrix4d m) {
1437   double cv = std::cos(v), sv = std::sin(v);
1438   for (int i = 0; i < 4; i++) {
1439     double r1 = m[i][1], r2 = m[i][2];
1440     m[i][1] = cv * r1 - sv * r2;
1441     m[i][2] = sv * r1 + cv * r2;
1442   }
1443 }
rotateY(double v,Matrix4d m)1444 void rotateY(double v, Matrix4d m) {
1445   double cv = std::cos(v), sv = std::sin(v);
1446   for (int i = 0; i < 4; i++) {
1447     double r0 = m[i][0], r2 = m[i][2];
1448     m[i][0] = cv * r0 - sv * r2;
1449     m[i][2] = sv * r0 + cv * r2;
1450   }
1451 }
rotateZ(double v,Matrix4d m)1452 void rotateZ(double v, Matrix4d m) {
1453   double cv = std::cos(v), sv = std::sin(v);
1454   for (int i = 0; i < 4; i++) {
1455     double r0 = m[i][0], r1 = m[i][1];
1456     m[i][0] = cv * r0 - sv * r1;
1457     m[i][1] = sv * r0 + cv * r1;
1458   }
1459 }
testBboxClip(double x1,double x2,double y1,double y2,double z1,double z2,const Matrix4d mvp)1460 bool testBboxClip(double x1, double x2, double y1, double y2, double z1, double z2,
1461                   const Matrix4d mvp) {
1462   // Construct axis-aligned bounding box in window space
1463   double vxl = 1e99, vyl = 1e99, vzl = 1e99;
1464   double vxh = -1e99, vyh = -1e99, vzh = -1e99;
1465   for (int i = 0; i < 8; i++) {
1466     double w[3] = {0., 0., 0.};
1467     double p[3] = {(i & 4) ? x1 : x2, (i & 2) ? y1 : y2, (i & 1) ? z1 : z2};
1468     // Apply perspective transform with MVP matrix
1469     for (int k = 0; k < 3; k++)
1470       w[k] += mvp[0][k] * p[0] + mvp[1][k] * p[1] + mvp[2][k] * p[2] + mvp[3][k];
1471     double h = mvp[0][3] * p[0] + mvp[1][3] * p[1] + mvp[2][3] * p[2] + mvp[3][3];
1472     for (int k = 0; k < 3; k++) w[k] /= h;
1473 
1474     vxl = std::min(vxl, w[0]);
1475     vyl = std::min(vyl, w[1]);
1476     vzl = std::min(vzl, w[2]);
1477 
1478     vxh = std::max(vxh, w[0]);
1479     vyh = std::max(vyh, w[1]);
1480     vzh = std::max(vzh, w[2]);
1481   }
1482   // Window frustum is [-1,1]x[-1,1]x[0,1]
1483   if (vxl > 1 || vxh < -1) return false;
1484   if (vyl > 1 || vyh < -1) return false;
1485   if (vzl > 1 || vzh < 0) return false;
1486   return true;
1487 }
1488 
1489 static bool is_2d_mode = false;
1490 
Require2DMode()1491 void Require2DMode() {
1492   if (!is_2d_mode) { warning("Function `Require2DMode` should only be called in 2D mode"); }
1493 }
1494 
Enter2DMode()1495 void Enter2DMode() {
1496   if (is_2d_mode) { return; }
1497 
1498   glDisable(GL_DEPTH_TEST);
1499   glDisable(GL_CULL_FACE);
1500 
1501   glEnable(GL_BLEND);
1502   glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1503 
1504   // Load program and set up uniforms. VAO will by bound at point of use
1505   glUseProgram(shaderUI);
1506   glUniform1f(uniformLocations.ui_screen_width, (GLfloat)screenWidth);
1507   glUniform1f(uniformLocations.ui_screen_height, (GLfloat)screenHeight);
1508   glUniform1i(uniformLocations.ui_tex, 0);
1509 
1510   glActiveTexture(GL_TEXTURE0);
1511   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);
1512   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
1513   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
1514   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
1515 
1516   is_2d_mode = true;
1517 }
1518 
Leave2DMode()1519 void Leave2DMode() {
1520   if (!is_2d_mode) {
1521     warning("Function `Leave2DMode` should only be called in 2D mode");
1522     return;
1523   }
1524 
1525   is_2d_mode = false;
1526 }
1527 
powerOfTwo(int input)1528 int powerOfTwo(int input) {
1529   int value = 1;
1530 
1531   while (value < input) { value <<= 1; }
1532   return value;
1533 }
1534 
LoadTexture(SDL_Surface * surface,GLfloat * texcoord)1535 GLuint LoadTexture(SDL_Surface *surface, GLfloat *texcoord) {
1536   /* Use the surface width and height expanded to powers of 2 */
1537   int w = powerOfTwo(surface->w);
1538   int h = powerOfTwo(surface->h);
1539 
1540   texcoord[0] = 0.0f;                    /* Min X */
1541   texcoord[1] = 0.0f;                    /* Min Y */
1542   texcoord[2] = (GLfloat)surface->w / w; /* Max X */
1543   texcoord[3] = (GLfloat)surface->h / h; /* Max Y */
1544 
1545   /* Rescale image if needed? */
1546   GLint maxSize;
1547   glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxSize);
1548   double scale = 1.0;
1549   if (w > maxSize || h > maxSize) scale = std::min(maxSize / (double)w, maxSize / (double)h);
1550 
1551   Uint32 mask[4];
1552 #if SDL_BYTEORDER == SDL_LIL_ENDIAN /* OpenGL RGBA masks */
1553   mask[0] = 0x000000FF;
1554   mask[1] = 0x0000FF00;
1555   mask[2] = 0x00FF0000;
1556   mask[3] = 0xFF000000;
1557 #else
1558   mask[0] = 0xFF000000;
1559   mask[1] = 0x00FF0000;
1560   mask[2] = 0x0000FF00;
1561   mask[3] = 0x000000FF;
1562 #endif
1563 
1564   SDL_Surface *image = SDL_CreateRGBSurface(SDL_SWSURFACE, (int)(w * scale), (int)(h * scale),
1565                                             32, mask[0], mask[1], mask[2], mask[3]);
1566 
1567   if (image == NULL) { return 0; }
1568 
1569   /* Copy the surface into the GL texture image */
1570   SDL_Rect area;
1571   area.x = 0;
1572   area.y = 0;
1573   area.w = (int)(surface->w * scale);
1574   area.h = (int)(surface->h * scale);
1575 
1576   SDL_SetSurfaceBlendMode(surface, SDL_BLENDMODE_NONE);
1577   if (scale == 1.0)
1578     // Easy, no scaling needed
1579     SDL_BlitSurface(surface, NULL, image, &area);
1580   else {
1581     SDL_Rect sarea;
1582     sarea.x = 0;
1583     sarea.y = 0;
1584     sarea.h = surface->h;
1585     sarea.w = surface->w;
1586     SDL_BlitScaled(surface, &sarea, image, &area);
1587   }
1588 
1589   /* Create an OpenGL texture for the image */
1590   GLuint texture = 0;
1591   glGenTextures(1, &texture);
1592   glBindTexture(GL_TEXTURE_2D, texture);
1593   glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (int)(w * scale), (int)(h * scale), 0, GL_RGBA,
1594                GL_UNSIGNED_BYTE, image->pixels);
1595   SDL_FreeSurface(image); /* No longer needed */
1596 
1597   /* Set current sampler with these just in case */
1598   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);
1599   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
1600   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
1601   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
1602 
1603   return texture;
1604 }
1605 
loadImage(const char * imagename)1606 SDL_Surface *loadImage(const char *imagename) {
1607   char path[512];
1608   snprintf(path, 511, "%s/images/%s", effectiveShareDir, imagename);
1609   path[511] = '\0';
1610 
1611   SDL_Surface *img = IMG_Load(path);
1612   if (!img) error("Failed to load image '%s' because: %s", path, IMG_GetError());
1613   return img;
1614 }
1615