OVR: Render sights and interstellar sun at correct distance
[mercenary-reloaded.git] / src / libsdl / 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 "print.h"
27 #include "opengl.h"
28 #define GL3_PROTOTYPES 1
29 #include <GL/glew.h>
30
31 #define MAX_OSD 2
32 #define MONITOR_DISTANCE 35.0
33 static uint8_t *image_rgb = NULL;
34 static uint8_t *osd_rgba[MAX_OSD] = { NULL, NULL };
35 static GLuint image_name;
36 static GLuint osd_name[MAX_OSD];
37 static int screen_width, screen_height;
38 static int image_width, image_height;
39 static int osd_width[MAX_OSD], osd_height[MAX_OSD];
40 static int texture_size;
41 static int osd_size[MAX_OSD];
42 static int flip_y;
43
44 /* alloc and init an image texture with size that is greater than the power of two */
45 int init_opengl_image(int _image_width, int _image_height)
46 {
47         int rc;
48
49         image_width = _image_width;
50         image_height = _image_height;
51
52         /* generate texture */
53         for (texture_size = 1; texture_size <= image_width || texture_size <= image_height; texture_size *= 2)
54                 ;
55
56         image_rgb = calloc(texture_size * texture_size, 3);
57         if (!image_rgb) {
58                 print_error("Failed to allocate texture\n");
59                 rc = -ENOMEM;
60                 goto error;
61         }
62         glShadeModel(GL_FLAT);
63         glPixelStorei(GL_UNPACK_ALIGNMENT, 1); /* bytes */
64         glGenTextures(1, &image_name);
65         glBindTexture(GL_TEXTURE_2D, image_name);
66         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
67         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
68         glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, texture_size, texture_size, 0, GL_RGB, GL_UNSIGNED_BYTE, image_rgb);
69
70
71         return 0;
72
73 error:
74         exit_opengl();
75         return rc;
76 }
77
78 /* alloc and init an osd texture with size that is greater than the power of two */
79 int init_opengl_osd(int num, int _osd_width, int _osd_height)
80 {
81         int rc;
82
83         if (num < 0 || num >= MAX_OSD) {
84                 print_error("given OSD number out of range");
85                 rc = -ENOMEM;
86                 goto error;
87         }
88
89         osd_width[num] = _osd_width;
90         osd_height[num] = _osd_height;
91
92         /* generate texture */
93         for (osd_size[num] = 1; osd_size[num] <= osd_width[num] || osd_size[num] <= osd_height[num]; osd_size[num] *= 2)
94                 ;
95         osd_rgba[num] = calloc(osd_size[num] * osd_size[num], 4);
96         if (!osd_rgba[num]) {
97                 print_error("Failed to allocate texture\n");
98                 rc = -ENOMEM;
99                 goto error;
100         }
101         glPixelStorei(GL_UNPACK_ALIGNMENT, 1); /* bytes */
102         glGenTextures(1, &osd_name[num]);
103         glBindTexture(GL_TEXTURE_2D, osd_name[num]);
104         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
105         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
106         glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, osd_size[num], osd_size[num], 0, GL_RGBA, GL_UNSIGNED_BYTE, osd_rgba[num]);
107
108         return 0;
109
110 error:
111         exit_opengl();
112         return rc;
113 }
114
115 /* set clip planes so that the image portion of the image texture is centered and pixels are rectengular */
116 void resize_opengl(int _screen_width, int _screen_height)
117 {
118         screen_width = _screen_width;
119         screen_height = _screen_height;
120 }
121
122 void opengl_clear(int _flip_y)
123 {
124         flip_y = _flip_y;
125
126         /* clear screen */
127         glClearColor(0.0, 0.0, 0.0, 1.0);
128         glClear(GL_COLOR_BUFFER_BIT);
129 }
130
131 /* set viewport for improved rendering */
132 void opengl_viewport(int split, int benson_at_line, double fov, double benson_size)
133 {
134         int view_x, view_y;
135         int view_width, view_height;
136         int new_width, new_height;
137
138         /* center view, or put it in the top half of the window */
139         if (split == 1) {
140                 view_x = 0;
141                 view_y = screen_height / 2;
142                 view_width = screen_width;
143                 view_height = screen_height / 2;
144         } else if (split == 2) {
145                 view_x = 0;
146                 view_y = 0;
147                 view_width = screen_width;
148                 view_height = screen_height / 2;
149         } else {
150                 view_x = 0;
151                 view_y = 0;
152                 view_width = screen_width;
153                 view_height = screen_height;
154         }
155
156         /* avoid division by zero, if one dimension is too small */
157         if (view_width < 1 || view_height < 1)
158                 return;
159
160         /* calculate a viewport that has apect of image_width:image_height */
161         if (view_height * image_width > view_width * image_height) {
162                 new_height = view_width * image_height / image_width;
163                 view_y = view_y + view_height / 2 - new_height / 2;
164                 view_height = new_height;
165         } else if (view_height * image_width < view_width * image_height) {
166                 new_width = view_height * image_width / image_height;
167                 view_x = view_x + view_width / 2 - new_width / 2;
168                 view_width = new_width;
169         }
170
171         /* avoid views that are too small */
172         if (view_width < 1 || view_height < 1)
173                 return;
174
175         /* viewport and projection matrix */
176         glViewport((GLsizei)view_x, (GLsizei)view_y, (GLsizei)view_width, (GLsizei)view_height);
177         glMatrixMode(GL_PROJECTION);
178         glLoadIdentity();
179
180         /* calculate field-of-view */
181         double slope = tan(fov / 360 * M_PI);
182         /* make frustum to center the view in the game view above benson */
183         double left = -slope;
184         double right = slope;
185         double benson_start_at_position = ((double)image_height - (double)benson_at_line) * benson_size;
186         double top = slope * ((double)image_height - benson_start_at_position) / (double)image_width;
187         double bottom = -slope * ((double)image_height * 2.0 - ((double)image_height - benson_start_at_position)) / (double)image_width;
188         glFrustum(left, right, bottom, top, 1.0, 5000000000.0);
189
190         glMatrixMode(GL_MODELVIEW);
191 }
192
193 /* render image or only benson */
194 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)
195 {
196         double texture_left = 0;
197         double texture_right = ((double)image_width) / (double)texture_size;
198         double texture_top = ((double)benson_at_line) / (double)texture_size;
199         double texture_bottom = ((double)image_height) / (double)texture_size;
200         double image_start_at_position = ((double)image_height - (double)benson_at_line) * benson_size;
201         double image_top = -((double)image_height - image_start_at_position) / (double)image_width;
202         double image_bottom = -((double)image_height * 2.0 - ((double)image_height - image_start_at_position)) / (double)image_width;
203
204         /* calculate field-of-view */
205         double slope = tan(fov / 360 * M_PI) * monitor_distance;
206
207         /* render image */
208         glEnable(GL_TEXTURE_2D);
209         glBindTexture(GL_TEXTURE_2D, image_name);
210         glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);  /* no modulation with color */
211         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, (filter) ? GL_LINEAR : GL_NEAREST);
212         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, (filter) ? GL_LINEAR : GL_NEAREST);
213         if (render_benson_only) {
214                 /* render benson only, and copy top line of benson to one line above, so the texture filter will not take false data above benson */
215                 rgb += image_width * (benson_at_line - 1) * 3;
216                 memcpy(rgb, rgb + image_width * 3, image_width * 3);
217                 glTexSubImage2D(GL_TEXTURE_2D, 0, 0, benson_at_line - 1, image_width, image_height - benson_at_line - 1, GL_RGB, GL_UNSIGNED_BYTE, rgb);
218         } else {
219                 image_top = -image_top;
220                 texture_top = 0.0;
221                 glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, image_width, image_height, GL_RGB, GL_UNSIGNED_BYTE, rgb);
222         }
223         glBegin(GL_QUADS);
224         glTexCoord2f(texture_left, texture_bottom);
225         glVertex3f(-benson_size * slope, image_bottom * slope, -monitor_distance);
226         glTexCoord2f(texture_right, texture_bottom);
227         glVertex3f(benson_size * slope, image_bottom * slope, -monitor_distance);
228         glTexCoord2f(texture_right, texture_top);
229         glVertex3f(benson_size * slope, image_top * slope, -monitor_distance);
230         glTexCoord2f(texture_left, texture_top);
231         glVertex3f(-benson_size * slope, image_top * slope, -monitor_distance);
232         glEnd();
233         glDisable(GL_TEXTURE_2D);
234 }
235
236 /* render osd texture */
237 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)
238 {
239         double texture_left = 0.0;
240         double texture_right = (double)osd_width[num] / (double)osd_size[num];
241         double texture_top = 0.0;
242         double texture_bottom = (double)osd_height[num] / (double)osd_size[num];
243         double benson_start_at_position;
244         double osd_left = 0.0;
245         double osd_right = 1.0;
246         double osd_top = 0.0;
247         double osd_bottom = 1.0;
248         double slope, range, center;
249         if (fov) {
250                 osd_left = -1.0;
251                 osd_right = 1.0;
252                 benson_start_at_position = ((double)image_height - (double)benson_at_line) * benson_size;
253                 osd_top = ((double)image_height - benson_start_at_position) / (double)image_width;
254                 osd_bottom = -((double)image_height * 2.0 - ((double)image_height - benson_start_at_position)) / (double)image_width;
255                 /* calculate field-of-view */
256                 slope = tan(fov / 360 * M_PI) * monitor_distance;
257         }
258         range = (osd_right - osd_left) / 2.0;
259         center = (osd_right + osd_left) / 2.0;
260         osd_left = (osd_left - center) * scale_x + center + range * offset_x;
261         osd_right = (osd_right - center) * scale_x + center + range * offset_x;
262         range = (osd_bottom - osd_top) / 2.0;
263         center = (osd_bottom + osd_top) / 2.0;
264         osd_top = (osd_top - center) * scale_y + center + range * offset_y;
265         osd_bottom = (osd_bottom - center) * scale_y + center + range * offset_y;
266
267         glEnable(GL_BLEND);
268         glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
269         glEnable(GL_TEXTURE_2D);
270         glBindTexture(GL_TEXTURE_2D, osd_name[num]);
271         glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);  /* no modulation with color */
272         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, (filter) ? GL_LINEAR : GL_NEAREST);
273         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, (filter) ? GL_LINEAR : GL_NEAREST);
274         glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, osd_width[num], osd_height[num], GL_RGBA, GL_UNSIGNED_BYTE, rgba);
275         glBegin(GL_QUADS);
276         if (fov) {
277                 /* perspective viewport */
278                 glTexCoord2f(texture_left, texture_bottom);
279                 glVertex3f(osd_left * slope, osd_bottom * slope, -monitor_distance);
280                 glTexCoord2f(texture_right, texture_bottom);
281                 glVertex3f(osd_right * slope, osd_bottom * slope, -monitor_distance);
282                 glTexCoord2f(texture_right, texture_top);
283                 glVertex3f(osd_right * slope, osd_top * slope, -monitor_distance);
284                 glTexCoord2f(texture_left, texture_top);
285                 glVertex3f(osd_left * slope, osd_top * slope, -monitor_distance);
286         } else {
287                 /* orthogonal viewport */
288                 glTexCoord2f(texture_left, texture_top);
289                 glVertex3f(osd_left, osd_top, 0.0);
290                 glTexCoord2f(texture_right, texture_top);
291                 glVertex3f(osd_right, osd_top, 0.0);
292                 glTexCoord2f(texture_right, texture_bottom);
293                 glVertex3f(osd_right, osd_bottom, 0.0);
294                 glTexCoord2f(texture_left, texture_bottom);
295                 glVertex3f(osd_left, osd_bottom, 0.0);
296         }
297         glEnd();
298         glDisable(GL_TEXTURE_2D);
299         glDisable(GL_BLEND);
300 }
301
302 /* set color and opacity */
303 void opengl_render_color(double r, double g, double b, double a)
304 {
305         if (a < 1.0) {
306                 glEnable(GL_BLEND);
307                 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
308                 glColor4d(r, g, b, a);
309         } else {
310                 glDisable(GL_BLEND);
311                 glColor3d(r, g, b);
312         }
313 }
314
315 /* render polygon */
316 void opengl_render_polygon(double *x, double *y, double *z, int count, int cull_face)
317 {
318         int i;
319
320         if (cull_face) {
321                 glEnable(GL_CULL_FACE);
322                 glFrontFace((flip_y) ? GL_CCW : GL_CW);
323                 glCullFace(GL_BACK);
324         }
325         glBegin(GL_POLYGON);
326         for (i = 0; i < count; i++)
327                 glVertex3d(x[i], y[i], -z[i]);
328         glEnd();
329         if (cull_face)
330                 glDisable(GL_CULL_FACE);
331 }
332
333 /* render polygon, but make sure any size of it is visible */
334 void opengl_render_polygon_and_line(double *x, double *y, double *z, int count)
335 {
336         int i;
337
338         glBegin(GL_POLYGON);
339         for (i = 0; i < count; i++)
340                 glVertex3d(x[i], y[i], -z[i]);
341         glEnd();
342         glBegin(GL_LINE_LOOP);
343         for (i = 0; i < count; i++)
344                 glVertex3d(x[i], y[i], -z[i]);
345         glEnd();
346 }
347
348 /* render line */
349 void opengl_render_line(double x1, double y1, double z1, double x2, double y2, double z2, double size)
350 {
351         if (size == 0.0) {
352                 glBegin(GL_LINES);
353                 glVertex3f(x1, y1, -z1);
354                 glVertex3f(x2, y2, -z2);
355                 glEnd();
356                 return;
357         }
358 }
359
360 /* render point */
361 void opengl_render_point(double x, double y, double z, double size)
362 {
363         if (size == 0.0) {
364                 glBegin(GL_POINTS);
365                 glVertex3f(x, y, -z);
366                 glEnd();
367                 return;
368         }
369 }
370
371 /* free image texture */
372 void exit_opengl(void)
373 {
374         int i;
375
376         if (image_rgb) {
377                 free(image_rgb);
378                 image_rgb = NULL;
379         }
380         for (i = 0; i < MAX_OSD; i++) {
381                 if (osd_rgba[i]) {
382                         free(osd_rgba[i]);
383                         osd_rgba[i] = NULL;
384                 }
385         }
386 }
387