Change program icons and make dark grey background
[mercenary-reloaded.git] / src / libopengl / opengl.c
1 /* OpenGL rendering
2  *
3  * (C) 2018 by Andreas Eversberg <jolly@eversberg.eu>
4  * All Rights Reserved
5  *
6  * This program is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 #include <stdio.h>
21 #include <stdint.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <math.h>
25 #include <errno.h>
26 #include "../libsdl/print.h"
27 #include "opengl.h"
28 #define GL3_PROTOTYPES 1
29 #include <GL/glew.h>
30
31 #define MAX_OSD 2
32 static uint8_t *image_rgb = NULL;
33 static uint8_t *osd_rgba[MAX_OSD] = { NULL, NULL };
34 static GLuint image_name;
35 static GLuint osd_name[MAX_OSD];
36 static int image_width, image_height;
37 static int osd_width[MAX_OSD], osd_height[MAX_OSD];
38 static int texture_size;
39 static int osd_size[MAX_OSD];
40 static int flip_y;
41
42 /* alloc and init an image texture with size that is greater than the power of two */
43 int init_opengl_image(int _image_width, int _image_height)
44 {
45         int rc;
46
47         image_width = _image_width;
48         image_height = _image_height;
49
50         /* generate texture */
51         for (texture_size = 1; texture_size <= image_width || texture_size <= image_height; texture_size *= 2)
52                 ;
53
54         image_rgb = calloc(texture_size * texture_size, 3);
55         if (!image_rgb) {
56                 print_error("Failed to allocate texture\n");
57                 rc = -ENOMEM;
58                 goto error;
59         }
60         glShadeModel(GL_FLAT);
61         glPixelStorei(GL_UNPACK_ALIGNMENT, 1); /* bytes */
62         glGenTextures(1, &image_name);
63         glBindTexture(GL_TEXTURE_2D, image_name);
64         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
65         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
66         glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, texture_size, texture_size, 0, GL_RGB, GL_UNSIGNED_BYTE, image_rgb);
67
68
69         return 0;
70
71 error:
72         exit_opengl();
73         return rc;
74 }
75
76 /* alloc and init an osd texture with size that is greater than the power of two */
77 int init_opengl_osd(int num, int _osd_width, int _osd_height)
78 {
79         int rc;
80
81         if (num < 0 || num >= MAX_OSD) {
82                 print_error("given OSD number out of range");
83                 rc = -ENOMEM;
84                 goto error;
85         }
86
87         osd_width[num] = _osd_width;
88         osd_height[num] = _osd_height;
89
90         /* generate texture */
91         for (osd_size[num] = 1; osd_size[num] <= osd_width[num] || osd_size[num] <= osd_height[num]; osd_size[num] *= 2)
92                 ;
93         osd_rgba[num] = calloc(osd_size[num] * osd_size[num], 4);
94         if (!osd_rgba[num]) {
95                 print_error("Failed to allocate texture\n");
96                 rc = -ENOMEM;
97                 goto error;
98         }
99         glPixelStorei(GL_UNPACK_ALIGNMENT, 1); /* bytes */
100         glGenTextures(1, &osd_name[num]);
101         glBindTexture(GL_TEXTURE_2D, osd_name[num]);
102         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
103         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
104         glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, osd_size[num], osd_size[num], 0, GL_RGBA, GL_UNSIGNED_BYTE, osd_rgba[num]);
105
106         return 0;
107
108 error:
109         exit_opengl();
110         return rc;
111 }
112
113 void opengl_clear(int _flip_y)
114 {
115         flip_y = _flip_y;
116
117         /* clear screen */
118         glClearColor(0.05, 0.05, 0.05, 1.0);
119         glClear(GL_COLOR_BUFFER_BIT);
120 }
121
122 /* set viewport for improved rendering */
123 void opengl_viewport(int view_width, int view_height, int split, int benson_at_line, double fov, double benson_size)
124 {
125         int view_x = 0, view_y = 0;
126         double factor_height = 1.0;
127         double factor_width = 1.0;
128
129         /* center view, or put it in the top half of the window */
130         if (split == 1) {
131                 view_y = view_height / 2;
132                 view_height = view_height / 2;
133         } else if (split == 2) {
134                 view_height = view_height / 2;
135         }
136
137         /* avoid division by zero, if one dimension is too small */
138         if (view_width < 1 || view_height < 1)
139                 return;
140
141         /* expand width or height, if apect of image_width:image_height does not match the view_width:view_height */
142         if (view_height * image_width > view_width * image_height)
143                 factor_height = (double)(view_height * image_width) / (double)(view_width * image_height);
144         else if (view_height * image_width < view_width * image_height)
145                 factor_width = (double)(view_width * image_height) / (double)(view_height * image_width);
146
147         /* avoid views that are too small */
148         if (view_width < 1 || view_height < 1)
149                 return;
150
151         /* projection matrix */
152         glViewport((GLsizei)view_x, (GLsizei)view_y, (GLsizei)view_width, (GLsizei)view_height);
153         glMatrixMode(GL_PROJECTION);
154         glLoadIdentity();
155
156         /* calculate field-of-view */
157         double slope = tan(fov / 360 * M_PI);
158         /* make frustum to center the view in the game view above benson */
159         double left = -slope;
160         double right = slope;
161         double benson_start_at_position = ((double)image_height - (double)benson_at_line) * benson_size;
162         double top = slope * ((double)image_height - benson_start_at_position) / (double)image_width;
163         double bottom = -slope * ((double)image_height * 2.0 - ((double)image_height - benson_start_at_position)) / (double)image_width;
164         glFrustum(left * factor_width, right * factor_width, bottom * factor_height, top * factor_height, 1.0, 5000000000.0);
165
166         glMatrixMode(GL_MODELVIEW);
167 }
168
169 /* render image or only benson */
170 void opengl_blit_image(uint8_t *rgb, int filter, int benson_at_line, int render_benson_only, double fov, double monitor_distance, double benson_size, int benson_case)
171 {
172         double texture_left = 0;
173         double texture_right = ((double)image_width) / (double)texture_size;
174         double texture_top = ((double)benson_at_line) / (double)texture_size;
175         double texture_bottom = ((double)image_height) / (double)texture_size;
176         double image_start_at_position = ((double)image_height - (double)benson_at_line) * benson_size;
177         double image_top = -((double)image_height - image_start_at_position) / (double)image_width;
178         double image_bottom = -((double)image_height * 2.0 - ((double)image_height - image_start_at_position)) / (double)image_width;
179
180         /* calculate field-of-view */
181         double slope = tan(fov / 360 * M_PI) * monitor_distance;
182
183         /* render image */
184
185         if (benson_case) {
186                 glEnable(GL_CULL_FACE);
187                 glFrontFace(GL_CW);
188                 glCullFace(GL_BACK);
189         }
190
191         glEnable(GL_TEXTURE_2D);
192         glBindTexture(GL_TEXTURE_2D, image_name);
193         glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);  /* no modulation with color */
194         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, (filter) ? GL_LINEAR : GL_NEAREST);
195         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, (filter) ? GL_LINEAR : GL_NEAREST);
196         if (render_benson_only) {
197                 /* render benson only, and copy top line of benson to one line above, so the texture filter will not take false data above benson */
198                 rgb += image_width * (benson_at_line - 1) * 3;
199                 memcpy(rgb, rgb + image_width * 3, image_width * 3);
200                 glTexSubImage2D(GL_TEXTURE_2D, 0, 0, benson_at_line - 1, image_width, image_height - benson_at_line - 1, GL_RGB, GL_UNSIGNED_BYTE, rgb);
201         } else {
202                 image_top = -image_top;
203                 texture_top = 0.0;
204                 glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, image_width, image_height, GL_RGB, GL_UNSIGNED_BYTE, rgb);
205         }
206         glBegin(GL_QUADS);
207         glTexCoord2f(texture_left, texture_bottom);
208         glVertex3f(-benson_size * slope, image_bottom * slope, -monitor_distance);
209         glTexCoord2f(texture_right, texture_bottom);
210         glVertex3f(benson_size * slope, image_bottom * slope, -monitor_distance);
211         glTexCoord2f(texture_right, texture_top);
212         glVertex3f(benson_size * slope, image_top * slope, -monitor_distance);
213         glTexCoord2f(texture_left, texture_top);
214         glVertex3f(-benson_size * slope, image_top * slope, -monitor_distance);
215         glEnd();
216         glDisable(GL_TEXTURE_2D);
217         /* render benson case */
218         if (benson_case) {
219                 double left, right, bottom, top, near, far;
220                 left = -benson_size * slope;
221                 right = benson_size * slope;
222                 bottom = image_bottom * slope;
223                 top = image_top * slope;
224                 near = -monitor_distance;
225                 far = near - benson_size * slope / 3.0;
226                 glBegin(GL_QUADS);
227                 /* back */
228                 glColor4f(0.3, 0.3, 0.3, 1.0);
229                 glVertex3f(left, top, far);
230                 glVertex3f(right, top, far);
231                 glVertex3f(right, bottom, far);
232                 glVertex3f(left, bottom, far);
233                 /* top */
234                 glColor4f(0.2, 0.2, 0.2, 1.0);
235                 glVertex3f(left, top, near);
236                 glVertex3f(right, top, near);
237                 glVertex3f(right, top, far);
238                 glVertex3f(left, top, far);
239                 /* bottom */
240                 glColor4f(0.05, 0.05, 0.05, 1.0);
241                 glVertex3f(left, bottom, far);
242                 glVertex3f(right, bottom, far);
243                 glVertex3f(right, bottom, near);
244                 glVertex3f(left, bottom, near);
245                 /* left */
246                 glColor4f(0.1, 0.1, 0.1, 1.0);
247                 glVertex3f(left, bottom, far);
248                 glVertex3f(left, bottom, near);
249                 glVertex3f(left, top, near);
250                 glVertex3f(left, top, far);
251                 /* right */
252                 glVertex3f(right, top, far);
253                 glVertex3f(right, top, near);
254                 glVertex3f(right, bottom, near);
255                 glVertex3f(right, bottom, far);
256                 glEnd();
257         }
258
259         if (benson_case) {
260                 glDisable(GL_CULL_FACE);
261         }
262 }
263
264 /* render osd texture */
265 void opengl_blit_osd(int num, uint8_t *rgba, int filter, int benson_at_line, double fov, double monitor_distance, double benson_size, double scale_x, double scale_y, double offset_x, double offset_y)
266 {
267         double texture_left = 0.0;
268         double texture_right = (double)osd_width[num] / (double)osd_size[num];
269         double texture_top = 0.0;
270         double texture_bottom = (double)osd_height[num] / (double)osd_size[num];
271         double benson_start_at_position;
272         double osd_left = 0.0;
273         double osd_right = 1.0;
274         double osd_top = 0.0;
275         double osd_bottom = 1.0;
276         double slope, range, center;
277         if (fov) {
278                 osd_left = -1.0;
279                 osd_right = 1.0;
280                 benson_start_at_position = ((double)image_height - (double)benson_at_line) * benson_size;
281                 osd_top = ((double)image_height - benson_start_at_position) / (double)image_width;
282                 osd_bottom = -((double)image_height * 2.0 - ((double)image_height - benson_start_at_position)) / (double)image_width;
283                 /* calculate field-of-view */
284                 slope = tan(fov / 360 * M_PI) * monitor_distance;
285         }
286         range = (osd_right - osd_left) / 2.0;
287         center = (osd_right + osd_left) / 2.0;
288         osd_left = (osd_left - center) * scale_x + center + range * offset_x;
289         osd_right = (osd_right - center) * scale_x + center + range * offset_x;
290         range = (osd_bottom - osd_top) / 2.0;
291         center = (osd_bottom + osd_top) / 2.0;
292         osd_top = (osd_top - center) * scale_y + center + range * offset_y;
293         osd_bottom = (osd_bottom - center) * scale_y + center + range * offset_y;
294
295         glEnable(GL_BLEND);
296         glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
297         glEnable(GL_TEXTURE_2D);
298         glBindTexture(GL_TEXTURE_2D, osd_name[num]);
299         glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);  /* no modulation with color */
300         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, (filter) ? GL_LINEAR : GL_NEAREST);
301         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, (filter) ? GL_LINEAR : GL_NEAREST);
302         glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, osd_width[num], osd_height[num], GL_RGBA, GL_UNSIGNED_BYTE, rgba);
303         glBegin(GL_QUADS);
304         if (fov) {
305                 /* perspective viewport */
306                 glTexCoord2f(texture_left, texture_bottom);
307                 glVertex3f(osd_left * slope, osd_bottom * slope, -monitor_distance);
308                 glTexCoord2f(texture_right, texture_bottom);
309                 glVertex3f(osd_right * slope, osd_bottom * slope, -monitor_distance);
310                 glTexCoord2f(texture_right, texture_top);
311                 glVertex3f(osd_right * slope, osd_top * slope, -monitor_distance);
312                 glTexCoord2f(texture_left, texture_top);
313                 glVertex3f(osd_left * slope, osd_top * slope, -monitor_distance);
314         } else {
315                 /* orthogonal viewport */
316                 glTexCoord2f(texture_left, texture_top);
317                 glVertex3f(osd_left, osd_top, 0.0);
318                 glTexCoord2f(texture_right, texture_top);
319                 glVertex3f(osd_right, osd_top, 0.0);
320                 glTexCoord2f(texture_right, texture_bottom);
321                 glVertex3f(osd_right, osd_bottom, 0.0);
322                 glTexCoord2f(texture_left, texture_bottom);
323                 glVertex3f(osd_left, osd_bottom, 0.0);
324         }
325         glEnd();
326         glDisable(GL_TEXTURE_2D);
327         glDisable(GL_BLEND);
328 }
329
330 /* set color and opacity */
331 void opengl_render_color(double r, double g, double b, double a)
332 {
333         if (a < 1.0) {
334                 glEnable(GL_BLEND);
335                 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
336                 glColor4d(r, g, b, a);
337         } else {
338                 glDisable(GL_BLEND);
339                 glColor3d(r, g, b);
340         }
341 }
342
343 /* render polygon */
344 void opengl_render_polygon(double *x, double *y, double *z, int count, int cull_face)
345 {
346         int i;
347
348         if (cull_face) {
349                 glEnable(GL_CULL_FACE);
350                 glFrontFace((flip_y) ? GL_CCW : GL_CW);
351                 glCullFace(GL_BACK);
352         }
353         glBegin(GL_POLYGON);
354         for (i = 0; i < count; i++)
355                 glVertex3d(x[i], y[i], -z[i]);
356         glEnd();
357         if (cull_face)
358                 glDisable(GL_CULL_FACE);
359 }
360
361 /* render polygon, but make sure any size of it is visible */
362 void opengl_render_polygon_and_line(double *x, double *y, double *z, int count)
363 {
364         int i;
365
366         glBegin(GL_POLYGON);
367         for (i = 0; i < count; i++)
368                 glVertex3d(x[i], y[i], -z[i]);
369         glEnd();
370         glBegin(GL_LINE_LOOP);
371         for (i = 0; i < count; i++)
372                 glVertex3d(x[i], y[i], -z[i]);
373         glEnd();
374 }
375
376 /* render line */
377 void opengl_render_line(double x1, double y1, double z1, double x2, double y2, double z2, double size)
378 {
379         if (size == 0.0) {
380                 glBegin(GL_LINES);
381                 glVertex3f(x1, y1, -z1);
382                 glVertex3f(x2, y2, -z2);
383                 glEnd();
384                 return;
385         }
386 }
387
388 /* render point */
389 void opengl_render_point(double x, double y, double z, double size)
390 {
391         if (size == 0.0) {
392                 glBegin(GL_POINTS);
393                 glVertex3f(x, y, -z);
394                 glEnd();
395                 return;
396         }
397 }
398
399 /* free image texture */
400 void exit_opengl(void)
401 {
402         int i;
403
404         if (image_rgb) {
405                 free(image_rgb);
406                 image_rgb = NULL;
407         }
408         for (i = 0; i < MAX_OSD; i++) {
409                 if (osd_rgba[i]) {
410                         free(osd_rgba[i]);
411                         osd_rgba[i] = NULL;
412                 }
413         }
414 }
415