1 /*
2 Copyright 2012 Jared Krinke.
3 
4 Permission is hereby granted, free of charge, to any person obtaining a copy
5 of this software and associated documentation files (the "Software"), to deal
6 in the Software without restriction, including without limitation the rights
7 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 copies of the Software, and to permit persons to whom the Software is
9 furnished to do so, subject to the following conditions:
10 
11 The above copyright notice and this permission notice shall be included in
12 all copies or substantial portions of the Software.
13 
14 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 THE SOFTWARE.
21 */
22 
23 #include <math.h>
24 #include <SDL.h>
25 #if (defined _MSC_VER)
26 #include <windows.h>
27 #endif
28 #include "GL/gl.h"
29 #include "GL/glu.h"
30 
31 #include "r_state.h"
32 #include "r_assert.h"
33 #include "r_log.h"
34 #include "r_video.h"
35 #include "r_log.h"
36 #include "r_script.h"
37 #include "r_layer.h"
38 #include "r_color.h"
39 #include "r_entity.h"
40 #include "r_image_cache.h"
41 #include "r_element.h"
42 #include "r_layer.h"
43 #include "r_layer_stack.h"
44 #include "r_string_buffer.h"
45 #include "r_mesh.h"
46 #include "r_collision_detector.h"
47 #include "r_capture.h"
48 
49 /* Height of the entire view (i.e. the max y coordinate is R_VIDEO_HEIGHT / 2 since the origin is in the center) */
50 #define R_VIDEO_HEIGHT            (480.0)
51 
52 typedef struct {
53     unsigned int width;
54     unsigned int height;
55 } r_video_mode_t;
56 
57 const r_video_mode_t r_video_default_modes[] = {
58     { 320, 240 },
59     { 640, 480 },
60     { 800, 600 },
61     { 1024, 768 },
62     { 1280, 1024 },
63     { 1600, 1200 }
64 };
65 
66 typedef enum {
67     R_VIDEO_PROPERTY_PIXEL_WIDTH    = 0x00000001,
68     R_VIDEO_PROPERTY_PIXEL_HEIGHT   = 0x00000002,
69     R_VIDEO_PROPERTY_FULLSCREEN     = 0x00000004,
70     R_VIDEO_PROPERTY_NONE           = 0x00000000
71 } r_video_property_t;
72 
r_glenum_to_status(GLenum gl)73 r_status_t r_glenum_to_status(GLenum gl)
74 {
75     return (gl == GL_NO_ERROR) ? R_SUCCESS : (R_F_BIT | R_FACILITY_VIDEO_GL | gl);
76 }
77 
r_video_set_mode(r_state_t * rs,unsigned int width,unsigned int height,r_boolean_t fullscreen)78 r_status_t r_video_set_mode(r_state_t *rs, unsigned int width, unsigned int height, r_boolean_t fullscreen)
79 {
80     r_status_t status = (rs != NULL) ? R_SUCCESS : R_F_INVALID_POINTER;
81     R_ASSERT(R_SUCCEEDED(status));
82 
83     if (R_SUCCEEDED(status))
84     {
85         status = (SDL_SetVideoMode((int)width, (int)height, 0, SDL_OPENGL | (fullscreen ? SDL_FULLSCREEN : 0)) != NULL) ? R_SUCCESS : R_FAILURE;
86 
87         if (R_SUCCEEDED(status))
88         {
89             /* Grab input if using a fullscreen mode */
90             SDL_GrabMode grab_mode = fullscreen ? SDL_GRAB_ON : SDL_GRAB_OFF;
91             GLint max_texture_size = 512;
92 
93             if (grab_mode != SDL_WM_GrabInput(SDL_GRAB_QUERY))
94             {
95                 SDL_WM_GrabInput(grab_mode);
96             }
97 
98             /* Don't show the cursor */
99             if (SDL_DISABLE != SDL_ShowCursor(SDL_QUERY))
100             {
101                 SDL_ShowCursor(SDL_DISABLE);
102             }
103 
104             rs->video_width = width;
105             rs->video_height = height;
106 
107             /* Get OpenGL implementation parameters */
108             /* Check to see if OpenGL 1.2 is supported */
109             {
110                 /* Kind of surprising that string parsing is required to get the version... */
111                 const char *version_string_original = (const char*)glGetString(GL_VERSION);
112                 char version_string[4];
113                 double version = 0;
114 
115                 /* Assume "Major.Minor" is 3 characters--ignore release and other text */
116                 memcpy(version_string, version_string_original, 3 * sizeof(char));
117                 version_string[3] = '\0';
118                 version = atof(version_string);
119 
120                 if (version > 1.2)
121                 {
122                     rs->video_full_featured = R_TRUE;
123                 }
124             }
125 
126             /* Query for maximum texture size */
127             glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max_texture_size);
128             rs->max_texture_size = (max_texture_size >= 64) ? max_texture_size : 512;
129 
130             /* Assume minimum size is 8 since there isn't good documentation */
131             rs->min_texture_size = 8;
132 
133             /* Set up pixel-to-coordinate transformation */
134             r_transform2d_init(&rs->pixels_to_coordinates);
135             r_transform2d_translate(&rs->pixels_to_coordinates, (r_real_t)(-rs->video_width) / 2, (r_real_t)(-rs->video_height) / 2);
136             r_transform2d_scale(&rs->pixels_to_coordinates, (r_real_t)(R_VIDEO_HEIGHT / rs->video_height), (r_real_t)(-R_VIDEO_HEIGHT / rs->video_height));
137 
138             /* Initialize OpenGL */
139             /* TODO: determine which OpenGL setup commands are actually needed */
140             glViewport(0, 0, rs->video_width, rs->video_height);
141             glMatrixMode(GL_PROJECTION);
142             glLoadIdentity();
143             gluPerspective(45, ((r_real_t)rs->video_width)/((r_real_t)rs->video_height), 0.000001, 1000000000.0);
144             glMatrixMode(GL_MODELVIEW);
145             glLoadIdentity();
146             glEnable(GL_TEXTURE_2D);
147 
148             glClearColor(0, 0, 0, 1);
149             glClearDepth(1.0);
150 
151             glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
152             glEnable(GL_BLEND);
153             glEnable(GL_COLOR_MATERIAL);
154 
155             /* Clear the screen */
156             glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
157             SDL_GL_SwapBuffers();
158             glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
159             SDL_GL_SwapBuffers();
160 
161             status = r_glenum_to_status(glGetError());
162         }
163         else
164         {
165             r_log_error(rs, SDL_GetError());
166         }
167     }
168 
169     if (R_SUCCEEDED(status))
170     {
171         rs->video_mode_set = R_TRUE;
172 
173         if (R_SUCCEEDED(status))
174         {
175             /* Changing the video mode may reset the OpenGL context, so all images must be reloaded */
176             status = r_image_cache_reload(rs);
177         }
178     }
179 
180     return status;
181 }
182 
r_video_start(r_state_t * rs,const char * application_name,const char * default_font_path)183 r_status_t r_video_start(r_state_t *rs, const char *application_name, const char *default_font_path)
184 {
185     r_status_t status = (rs != NULL && rs->script_state != NULL && default_font_path != NULL) ? R_SUCCESS : R_F_INVALID_POINTER;
186     R_ASSERT(R_SUCCEEDED(status));
187 
188     if (R_SUCCEEDED(status))
189     {
190         /* Initialize video subsystem */
191         status = (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_JOYSTICK) == 0) ? R_SUCCESS : R_FAILURE;
192 
193         if (R_SUCCEEDED(status))
194         {
195             /* TODO: find a way in SDL to sync to vertical refresh rate */
196             /* Use double-buffering */
197             SDL_WM_SetCaption(application_name, application_name);
198             status = (SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1) == 0) ? R_SUCCESS : R_FAILURE;
199 
200             if (R_SUCCEEDED(status))
201             {
202                 /* Set up image cache */
203                 rs->default_font_path = default_font_path;
204                 status = r_image_cache_start(rs);
205             }
206             else
207             {
208                 r_log_error_format(rs, "Could not enable double-buffering: %s", SDL_GetError());
209             }
210         }
211         else
212         {
213             r_log_error_format(rs, "Could not initialize SDL video and timer subsystems: %s", SDL_GetError());
214         }
215     }
216 
217     return status;
218 }
219 
r_video_end(r_state_t * rs)220 void r_video_end(r_state_t *rs)
221 {
222     r_status_t status = (rs != NULL) ? R_SUCCESS : R_F_INVALID_POINTER;
223     R_ASSERT(R_SUCCEEDED(status));
224 
225     if (R_SUCCEEDED(status))
226     {
227         r_image_cache_stop(rs);
228         SDL_WM_GrabInput(SDL_GRAB_OFF);
229         SDL_Quit();
230     }
231 }
232 
r_video_push_mode_table(lua_State * ls,unsigned int width,unsigned int height)233 R_INLINE void r_video_push_mode_table(lua_State *ls, unsigned int width, unsigned int height)
234 {
235     lua_newtable(ls);
236 
237     lua_pushliteral(ls, "width");
238     lua_pushnumber(ls, (lua_Number)width);
239     lua_rawset(ls, -3);
240 
241     lua_pushliteral(ls, "height");
242     lua_pushnumber(ls, (lua_Number)height);
243     lua_rawset(ls, -3);
244 }
245 
l_Video_setTitle(lua_State * ls)246 static int l_Video_setTitle(lua_State *ls)
247 {
248     r_state_t *rs = r_script_get_r_state(ls);
249     const r_script_argument_t expected_arguments[] = {
250         { LUA_TSTRING,  0 }
251     };
252 
253     r_status_t status = r_script_verify_arguments(rs, R_ARRAY_SIZE(expected_arguments), expected_arguments);
254 
255     if (R_SUCCEEDED(status))
256     {
257         const char *title = lua_tostring(ls, 1);
258 
259         SDL_WM_SetCaption(title, title);
260     }
261 
262     lua_pop(ls, lua_gettop(ls));
263 
264     return 0;
265 }
266 
l_Video_getModes(lua_State * ls)267 static int l_Video_getModes(lua_State *ls)
268 {
269     r_state_t *rs = r_script_get_r_state(ls);
270     int result_count = 0;
271     r_status_t status = r_script_verify_arguments(rs, 0, NULL);
272 
273     if (R_SUCCEEDED(status))
274     {
275         SDL_Rect **modes = SDL_ListModes(NULL, SDL_OPENGL | SDL_FULLSCREEN);
276 
277         status = (modes != 0) ? R_SUCCESS : R_FAILURE;
278 
279         if (R_SUCCEEDED(status))
280         {
281             if ((void*)modes != ((void*)(-1)))
282             {
283                 int i;
284 
285                 for (i = 0; modes[i] != NULL && R_SUCCEEDED(status); ++i)
286                 {
287                     r_video_push_mode_table(ls, (unsigned int)modes[i]->w, (unsigned int)modes[i]->h);
288                     ++result_count;
289                 }
290             }
291             else
292             {
293                 int i;
294 
295                 for (i = 0; i < R_ARRAY_SIZE(r_video_default_modes) && R_SUCCEEDED(status); ++i)
296                 {
297                     r_video_push_mode_table(ls, r_video_default_modes[i].width, r_video_default_modes[i].height);
298                     ++result_count;
299                 }
300             }
301         }
302     }
303 
304     lua_pop(ls, lua_gettop(ls) - result_count);
305 
306     return result_count;
307 }
308 
l_Video_getProperty(lua_State * ls,r_video_property_t properties)309 static int l_Video_getProperty(lua_State *ls, r_video_property_t properties)
310 {
311     r_state_t *rs = r_script_get_r_state(ls);
312     int result_count = 0;
313     r_status_t status = r_script_verify_arguments(rs, 0, NULL);
314 
315     if (R_SUCCEEDED(status))
316     {
317         SDL_Surface *surface = SDL_GetVideoSurface();
318 
319         status = (surface != NULL) ? R_SUCCESS : R_VIDEO_FAILURE;
320 
321         if (R_SUCCEEDED(status))
322         {
323             /* Return the requested properties */
324             if ((properties & R_VIDEO_PROPERTY_PIXEL_WIDTH) != 0)
325             {
326                 lua_pushnumber(ls, (lua_Number)surface->w);
327                 ++result_count;
328             }
329 
330             if ((properties & R_VIDEO_PROPERTY_PIXEL_HEIGHT) != 0)
331             {
332                 lua_pushnumber(ls, (lua_Number)surface->h);
333                 ++result_count;
334             }
335 
336             if ((properties & R_VIDEO_PROPERTY_FULLSCREEN) != 0)
337             {
338                 lua_pushboolean(ls, (surface->flags & SDL_FULLSCREEN) ? R_TRUE : R_FALSE);
339                 ++result_count;
340             }
341         }
342     }
343 
344     lua_pop(ls, lua_gettop(ls) - result_count);
345 
346     return result_count;
347 }
348 
l_Video_getPixelWidth(lua_State * ls)349 static int l_Video_getPixelWidth(lua_State *ls)
350 {
351     return l_Video_getProperty(ls, R_VIDEO_PROPERTY_PIXEL_WIDTH);
352 }
353 
l_Video_getPixelHeight(lua_State * ls)354 static int l_Video_getPixelHeight(lua_State *ls)
355 {
356     return l_Video_getProperty(ls, R_VIDEO_PROPERTY_PIXEL_HEIGHT);
357 }
358 
l_Video_getFullscreen(lua_State * ls)359 static int l_Video_getFullscreen(lua_State *ls)
360 {
361     return l_Video_getProperty(ls, R_VIDEO_PROPERTY_FULLSCREEN);
362 }
363 
l_Video_setMode(lua_State * ls)364 static int l_Video_setMode(lua_State *ls)
365 {
366     r_state_t *rs = r_script_get_r_state(ls);
367     int argument_count = lua_gettop(ls);
368     r_status_t status = R_SUCCESS;
369 
370     /* Check for video mode table (which defines "width" and "height" as fields) */
371     if (argument_count == 1)
372     {
373         /* A table with width and height is expected */
374         status = (lua_type(ls, 1) == LUA_TTABLE) ? R_SUCCESS : RS_F_INCORRECT_TYPE;
375 
376         if (R_SUCCEEDED(status))
377         {
378             lua_getfield(ls, 1, "width");
379             lua_getfield(ls, 1, "height");
380             lua_remove(ls, 1);
381         }
382     }
383 
384     /* Add fullscreen specification, if omitted */
385     if (R_SUCCEEDED(status))
386     {
387         if (argument_count >= 1 && argument_count <= 2)
388         {
389             /* Fullscreen was not specified, so add it */
390             SDL_Surface *surface = SDL_GetVideoSurface();
391 
392             if (surface != NULL)
393             {
394                 lua_pushboolean(ls, (surface->flags & SDL_FULLSCREEN) ? R_TRUE : R_FALSE);
395             }
396             else
397             {
398                 /* Video mode may not have been set, default to windowed */
399                 lua_pushboolean(ls, R_FALSE);
400             }
401         }
402     }
403 
404     /* Check arguments */
405     if (R_SUCCEEDED(status))
406     {
407         const r_script_argument_t expected_arguments[] = {
408             { LUA_TNUMBER,  0 },
409             { LUA_TNUMBER,  0 },
410             { LUA_TBOOLEAN, 0 }
411         };
412 
413         status = r_script_verify_arguments(rs, R_ARRAY_SIZE(expected_arguments), expected_arguments);
414     }
415 
416     /* Actually set the video mode */
417     if (R_SUCCEEDED(status))
418     {
419         unsigned int width = (unsigned int)lua_tonumber(ls, 1);
420         unsigned int height = (unsigned int)lua_tonumber(ls, 2);
421         r_boolean_t fullscreen = lua_toboolean(ls, 3) ? R_TRUE : R_FALSE;
422 
423         status = r_video_set_mode(rs, width, height, fullscreen);
424     }
425 
426     lua_pop(ls, lua_gettop(ls));
427 
428     return 0;
429 }
430 
r_video_setup(r_state_t * rs)431 r_status_t r_video_setup(r_state_t *rs)
432 {
433     r_status_t status = (rs != NULL && rs->script_state != NULL) ? R_SUCCESS : R_F_INVALID_POINTER;
434     R_ASSERT(R_SUCCEEDED(status));
435 
436     if (R_SUCCEEDED(status))
437     {
438         r_script_node_t video_nodes[] = {
439             { "getPixelWidth",  R_SCRIPT_NODE_TYPE_FUNCTION, NULL, l_Video_getPixelWidth },
440             { "getPixelHeight", R_SCRIPT_NODE_TYPE_FUNCTION, NULL, l_Video_getPixelHeight },
441             { "getFullscreen",  R_SCRIPT_NODE_TYPE_FUNCTION, NULL, l_Video_getFullscreen },
442             { "getModes",       R_SCRIPT_NODE_TYPE_FUNCTION, NULL, l_Video_getModes },
443             { "setMode",        R_SCRIPT_NODE_TYPE_FUNCTION, NULL, l_Video_setMode },
444             { "setTitle",       R_SCRIPT_NODE_TYPE_FUNCTION, NULL, l_Video_setTitle },
445             { NULL }
446         };
447 
448         r_script_node_root_t roots[] = {
449             { LUA_GLOBALSINDEX, NULL, { "Video", R_SCRIPT_NODE_TYPE_TABLE, video_nodes } },
450             { 0, NULL, { NULL, R_SCRIPT_NODE_TYPE_MAX, NULL, NULL } }
451         };
452 
453         status = r_script_register_nodes(rs, roots);
454     }
455 
456     return status;
457 }
458 
r_video_color_get_current_color(r_color_t * color_base)459 R_INLINE void r_video_color_get_current_color(r_color_t *color_base)
460 {
461     GLfloat values[4];
462 
463     glGetFloatv(GL_CURRENT_COLOR, values);
464 
465     color_base->red      = values[0];
466     color_base->green    = values[1];
467     color_base->blue     = values[2];
468     color_base->opacity  = values[3];
469 }
470 
r_video_color_blend(const r_color_t * color_base,const r_color_t * color)471 R_INLINE void r_video_color_blend(const r_color_t *color_base, const r_color_t *color)
472 {
473     glColor4f(color_base->red * color->red, color_base->green * color->green, color_base->blue * color->blue, color_base->opacity * color->opacity);
474 }
475 
r_video_color_unblend(r_color_t * color_base)476 static void r_video_color_unblend(r_color_t *color_base)
477 {
478     glColor4f(color_base->red, color_base->green, color_base->blue, color_base->opacity);
479 }
480 
r_video_draw_image_internal(r_state_t * rs,r_image_t * image,r_boolean_t region,r_real_t u1,r_real_t v1,r_real_t u2,r_real_t v2)481 static r_status_t r_video_draw_image_internal(r_state_t *rs, r_image_t *image, r_boolean_t region, r_real_t u1, r_real_t v1, r_real_t u2, r_real_t v2)
482 {
483     /* TODO: Cache the results of these calculations somewhere */
484     switch (image->storage_type)
485     {
486     case R_IMAGE_STORAGE_NATIVE:
487         /* Draw a single rectangle using the (single) texture */
488         glBindTexture(GL_TEXTURE_2D, (GLuint)(image->storage.native.id));
489 
490         glBegin(GL_POLYGON);
491         glTexCoord2f(u1, v1);
492         glVertex3f(-0.5f, 0.5f, 0.0f);
493 
494         glTexCoord2f(u1, v2);
495         glVertex3f(-0.5f, -0.5f, 0.0f);
496 
497         glTexCoord2f(u2, v2);
498         glVertex3f(0.5f, -0.5f, 0.0f);
499 
500         glTexCoord2f(u2, v1);
501         glVertex3f(0.5f, 0.5f, 0.0f);
502         glEnd();
503         break;
504 
505     case R_IMAGE_STORAGE_COMPOSITE:
506         {
507             /* Draw using one rectangle per element */
508             const unsigned int columns = image->storage.composite.columns;
509             const unsigned int rows = image->storage.composite.rows;
510             unsigned int i, j;
511 
512             /* Position of the current element */
513             r_real_t x1, y1;
514 
515             /* Inclusive lower bound */
516             unsigned int i1 = 0;
517             unsigned int j1 = 0;
518 
519             /* Exclusive upper bound */
520             unsigned int i2 = columns;
521             unsigned int j2 = rows;
522 
523             /* If this is an image region, calculate the set of elements that need to be rendered */
524             if (region)
525             {
526                 const unsigned int total_width = image->storage.composite.width;
527                 const unsigned int total_height = image->storage.composite.height;
528                 const unsigned int element_width = image->storage.composite.elements[0].width;
529                 const unsigned int element_height = image->storage.composite.elements[0].height;
530 
531                 i1 = (unsigned int)(u1 * total_width / element_width);
532                 j1 = (unsigned int)(v1 * total_height / element_height);
533                 i2 = R_MIN(i2, (unsigned int)ceil(u2 * total_width / element_width));
534                 j2 = R_MIN(j2, (unsigned int)ceil(v2 * total_height / element_height));
535             }
536 
537             /* TODO: Maybe don't push a new matrix just for this... */
538             glPushMatrix();
539             glTranslatef(-0.5f, 0.5f, 0);
540 
541             for (j = j1, y1 = 0.0f; j < j2; ++j)
542             {
543                 r_real_t y2 = y1 - ((r_real_t)(image->storage.composite.elements[j * columns].height)) / ((v2 - v1) * image->storage.composite.height);
544 
545                 for (i = i1, x1 = 0.0f; i < i2; ++i)
546                 {
547                     const r_image_element_t *element = &image->storage.composite.elements[j * columns + i];
548                     r_real_t x2 = x1 + ((r_real_t)element->width) / ((u2 - u1) * image->storage.composite.width);
549                     const r_real_t element_x2 = element->x2;
550                     const r_real_t element_y2 = element->y2;
551                     r_real_t element_u1 = u1;
552                     r_real_t element_v1 = v1;
553                     r_real_t element_u2 = u2;
554                     r_real_t element_v2 = v2;
555 
556                     if (region)
557                     {
558                         const unsigned int total_width = image->storage.composite.width;
559                         const unsigned int total_height = image->storage.composite.height;
560                         const unsigned int element_width = image->storage.composite.elements[0].width;
561                         const unsigned int element_height = image->storage.composite.elements[0].height;
562 
563                         /* Default to an "inner" element surrounded by others */
564                         element_u1 = 0;
565                         element_v1 = 0;
566                         element_u2 = 1;
567                         element_v2 = 1;
568 
569                         if (j == j1 || j == j2 - 1)
570                         {
571                             if (j == j1)
572                             {
573                                 /* First row */
574                                 element_v1 = (v1 * total_height / element_height - j1);
575 
576                                 /* Last element may have a different (smaller) size */
577                                 if (j == rows - 1 && element->height != element_height)
578                                 {
579                                     element_v1 = (element_v1 * element_height) / element->height;
580                                 }
581                             }
582 
583                             if (j == j2 - 1)
584                             {
585                                 /* Last row */
586                                 element_v2 = (v2 * total_height / element_height - (j2 - 1));
587 
588                                 /* Last element may have a different (smaller) size */
589                                 if (j == rows - 1 && element->height != element_height)
590                                 {
591                                     element_v2 = (element_v2 * element_height) / element->height;
592                                 }
593                             }
594 
595                             /* Weight the size according to the amount of the image shown--but only compute this once for the row */
596                             if (i == i1)
597                             {
598                                 y2 = y2 * (element_v2 - element_v1);
599                             }
600                         }
601 
602                         if (i == i1 || i == i2 - 1)
603                         {
604                             if (i == i1)
605                             {
606                                 /* First column */
607                                 element_u1 = (u1 * total_width / element_width - i1);
608 
609                                 /* Last element may have a different (smaller) size */
610                                 if (i == columns - 1 && element->width != element_width)
611                                 {
612                                     element_u1 = (element_u1 * element_width) / element->width;
613                                 }
614                             }
615 
616                             if (i == i2 - 1)
617                             {
618                                 /* Last column */
619                                 element_u2 = (u2 * total_width / element_width - (i2 - 1));
620 
621                                 /* Last element may have a different (smaller) size */
622                                 if (i == columns - 1 && element->width != element_width)
623                                 {
624                                     element_u2 = (element_u2 * element_width) / element->width;
625                                 }
626                             }
627 
628                             /* Weight the size according to the amount of the image shown */
629                             x2 = x2 * (element_u2 - element_u1);
630                         }
631                     }
632 
633                     /* Use the given texture */
634                     glBindTexture(GL_TEXTURE_2D, (GLuint)(element->id));
635 
636                     /* Draw the rectangle */
637                     glBegin(GL_POLYGON);
638                     glTexCoord2f(element_u1 * element_x2, element_v1 * element_y2);
639                     glVertex3f(x1, y1, 0.0f);
640 
641                     glTexCoord2f(element_u1 * element_x2, element_v2 * element_y2);
642                     glVertex3f(x1, y2, 0.0f);
643 
644                     glTexCoord2f(element_u2 * element_x2, element_v2 * element_y2);
645                     glVertex3f(x2, y2, 0.0f);
646 
647                     glTexCoord2f(element_u2 * element_x2, element_v1 * element_y2);
648                     glVertex3f(x2, y1, 0.0f);
649                     glEnd();
650 
651                     /* Move to the next element's area */
652                     x1 = x2;
653                 }
654 
655                 /* Move to the next element's area */
656                 y1 = y2;
657             }
658 
659             glPopMatrix();
660         }
661         break;
662 
663     default:
664         R_ASSERT(0); /* Unsupported storage type */
665         break;
666     }
667 
668     return r_glenum_to_status(glGetError());
669 }
670 
r_video_draw_element(r_state_t * rs,r_element_t * element)671 static r_status_t r_video_draw_element(r_state_t *rs, r_element_t *element)
672 {
673     r_status_t status = (element != NULL) ? R_SUCCESS : R_F_INVALID_POINTER;
674     R_ASSERT(R_SUCCEEDED(status));
675 
676     if (R_SUCCEEDED(status))
677     {
678         /* Common element setup */
679         r_color_t color_base;
680         r_color_t *color = (r_color_t*)element->color.value.object;
681         r_boolean_t visible = R_TRUE;
682 
683         r_video_color_get_current_color(&color_base);
684         visible = (color_base.opacity > 0);
685 
686         if (color != NULL)
687         {
688             r_video_color_blend(&color_base, color);
689             visible = visible && (color->opacity > 0);
690         }
691 
692         if (element->image.value.object != NULL && visible)
693         {
694             glPushMatrix();
695             glTranslatef(element->x, element->y, 0);
696             glRotatef(element->angle, 0, 0, 1);
697             glScalef(element->width, element->height, 0);
698 
699             switch (element->element_type)
700             {
701             case R_ELEMENT_TYPE_IMAGE:
702                 status = r_video_draw_image_internal(rs, (r_image_t*)element->image.value.object, R_FALSE, 0, 0, 1, 1);
703                 break;
704 
705             case R_ELEMENT_TYPE_IMAGE_REGION:
706                 {
707                     /* TODO: Coordinate checks could go in the "set" functions */
708                     const r_element_image_region_t *element_image_region = (r_element_image_region_t*)element;
709                     r_image_t *image = (r_image_t*)element->image.value.object;
710                     r_real_t u1 = R_MAX(0, element_image_region->u1);
711                     r_real_t v1 = R_MAX(0, element_image_region->v1);
712                     r_real_t u2 = R_MIN(1, element_image_region->u2);
713                     r_real_t v2 = R_MIN(1, element_image_region->v2);
714 
715                     /* Sanity-check the texture coordinates */
716                     if (u1 < 0 || v1 < 0 || u2 > 1 || v2 > 1 || u1 >= u2 || v1 >= v2)
717                     {
718                         status = RV_F_BAD_COORDINATES;
719                     }
720 
721                     if (R_SUCCEEDED(status))
722                     {
723                         status = r_video_draw_image_internal(rs, image, R_TRUE, u1, v1, u2, v2);
724                     }
725                 }
726                 break;
727 
728             case R_ELEMENT_TYPE_ANIMATION:
729                 {
730                     /* Draw the current frame */
731                     const r_element_animation_t *element_animation = (r_element_animation_t*)element;
732                     const r_animation_t *animation = (r_animation_t*)element->image.value.object;
733                     const unsigned int frame_index = element_animation->frame_index;
734 
735                     if (frame_index < animation->frames.count)
736                     {
737                         const r_animation_frame_t *animation_frame = r_animation_frame_list_get_index(rs, &animation->frames, frame_index);
738 
739                         status = r_video_draw_image_internal(rs, (r_image_t*)animation_frame->image.value.object, R_FALSE, 0, 0, 1, 1);
740                     }
741                 }
742                 break;
743 
744             case R_ELEMENT_TYPE_TEXT:
745                 /* Draw text, one character at a time */
746                 {
747                     r_element_text_t *element_text = (r_element_text_t*)element;
748                     r_image_t *image = (r_image_t*)element->image.value.object;
749                     const char *pc = NULL;
750                     int length = -1;
751 
752                     /* If text is provided, use it; otherwise check for an underlying string buffer */
753                     if (element_text->text.value.str != NULL)
754                     {
755                         pc = element_text->text.value.str;
756                     }
757                     else if (element_text->buffer.value.object != NULL && element_text->buffer.value.object->header->type == R_OBJECT_TYPE_STRING_BUFFER)
758                     {
759                         r_string_buffer_t *string_buffer = (r_string_buffer_t*)element_text->buffer.value.object;
760 
761                         if (string_buffer->buffer != NULL)
762                         {
763                             pc = string_buffer->buffer;
764                             length = string_buffer->length;
765                         }
766                     }
767 
768                     if (pc != NULL)
769                     {
770                         /* Determine length, if not already given */
771                         if (length == -1)
772                         {
773                             /* TODO: C-style string length could be cached */
774                             length = strlen(pc);
775                         }
776 
777                         /* Adjust position for alignment */
778                         switch (element_text->alignment)
779                         {
780                         case R_ELEMENT_TEXT_ALIGNMENT_LEFT:
781                             /* No adjustment needed for left alignment */
782                             break;
783 
784                         case R_ELEMENT_TEXT_ALIGNMENT_CENTER:
785                             glTranslatef(-((r_real_t)length) / 2, 0, 0);
786                             break;
787 
788                         case R_ELEMENT_TEXT_ALIGNMENT_RIGHT:
789                             glTranslatef(-((r_real_t)length), 0, 0);
790                             break;
791 
792                         default:
793                             R_ASSERT(0);
794                             status = R_F_INVALID_INDEX;
795                             break;
796                         }
797 
798                         if (R_SUCCEEDED(status))
799                         {
800                             glTranslatef(0.5f, 0.5f, 0);
801 
802                             /* Draw each character */
803                             for (; *pc != '\0' && R_SUCCEEDED(status); ++pc)
804                             {
805                                 /* Characters in a font are stored in a 12x8 table */
806                                 r_font_coordinates_t *fc = &r_font_coordinates[(unsigned char)(*pc)];
807 
808                                 status = r_video_draw_image_internal(rs, image, R_TRUE, fc->x_min, fc->y_min, fc->x_max, fc->y_max);
809                                 glTranslatef(1, 0, 0);
810                             }
811 
812                             glTranslatef(-0.5f, -0.5f, 0);
813                         }
814                     }
815                 }
816                 break;
817 
818             default:
819                 status = R_VIDEO_FAILURE;
820             }
821 
822             glPopMatrix();
823         }
824 
825         if (color != NULL)
826         {
827             r_video_color_unblend(&color_base);
828         }
829 
830         if (R_SUCCEEDED(status))
831         {
832             status = (glGetError() == 0) ? R_SUCCESS : R_VIDEO_FAILURE;
833         }
834     }
835 
836     return status;
837 }
838 
839 static r_status_t r_video_draw_entity_list(r_state_t *rs, r_entity_list_t *entity_list);
840 
r_video_draw_entity(r_state_t * rs,r_entity_t * entity)841 static r_status_t r_video_draw_entity(r_state_t *rs, r_entity_t *entity)
842 {
843     /* Set up transformations */
844     r_status_t status = R_SUCCESS;
845     r_element_list_t *element_list = (r_element_list_t*)entity->elements.value.object;
846 
847     if (element_list != NULL)
848     {
849         r_color_t color_base;
850         r_color_t *color = (r_color_t*)entity->color.value.object;
851         r_boolean_t visible = R_TRUE;
852 
853         r_video_color_get_current_color(&color_base);
854         visible = (color_base.opacity > 0);
855 
856         if (color != NULL)
857         {
858             r_video_color_blend(&color_base, color);
859             visible = visible && (color->opacity > 0);
860         }
861 
862         if (visible)
863         {
864             glPushMatrix();
865             glTranslatef(entity->x, entity->y, 0);
866             glRotatef(entity->angle, 0, 0, 1);
867             glScalef(entity->width, entity->height, 0);
868 
869             /* TODO: Call glGetError at appropriate places everywhere */
870             status = (glGetError() == 0) ? R_SUCCESS : R_VIDEO_FAILURE;
871 
872             if (R_SUCCEEDED(status))
873             {
874                 unsigned int i;
875 
876                 /* Draw all elements */
877                 for (i = 0; i < element_list->object_list.count && R_SUCCEEDED(status); ++i)
878                 {
879                     /* Assume the entity list is not locked (it shouldn't be when drawing) */
880                     r_element_t *element = (r_element_t*)element_list->object_list.items[i].object_ref.value.object;
881 
882                     status = r_video_draw_element(rs, element);
883                 }
884 
885                 /* Draw children, if necessary */
886                 if (R_SUCCEEDED(status))
887                 {
888                     if (entity->has_children && entity->children_display.object_list.count > 0)
889                     {
890                         status = r_video_draw_entity_list(rs, &entity->children_display);
891                     }
892                 }
893             }
894 
895             glPopMatrix();
896         }
897 
898         if (color != NULL)
899         {
900             r_video_color_unblend(&color_base);
901         }
902     }
903 
904     return status;
905 }
906 
r_video_draw_entity_list(r_state_t * rs,r_entity_list_t * entity_list)907 static r_status_t r_video_draw_entity_list(r_state_t *rs, r_entity_list_t *entity_list)
908 {
909     r_status_t status = (entity_list != NULL) ? R_SUCCESS : R_F_INVALID_POINTER;
910     R_ASSERT(R_SUCCEEDED(status));
911 
912     if (R_SUCCEEDED(status))
913     {
914         unsigned int i;
915 
916         /* Draw each entity */
917         for (i = 0; i < entity_list->object_list.count && R_SUCCEEDED(status); ++i)
918         {
919             /* Get the entity */
920             /* Assume the entity list is not locked (it shouldn't be when drawing) */
921             r_object_ref_t *entity_ref = &entity_list->object_list.items[i].object_ref;
922             r_entity_t *entity = (r_entity_t*)entity_ref->value.object;
923 
924             status = r_video_draw_entity(rs, entity);
925         }
926     }
927 
928     return status;
929 }
930 
931 r_real_t r_collision_tree_colors[][3] = {
932     { 1, 0, 0 },
933     { 0, 1, 0 },
934     { 0, 0, 1 },
935     { 1, 1, 0 },
936     { 1, 0, 1 },
937     { 0, 1, 1 },
938     { 1, 0.5f, 0 },
939     { 1, 0, 0.5f },
940     { 0, 1, 0.5f },
941     { 0.5f, 1, 0 },
942     { 0.5f, 0, 1 },
943     { 0, 0.5f, 1 },
944     { 1, 1, 1 }
945 };
946 
947 r_real_t r_collision_tree_node_opacities[] = {
948         0.1f,
949         0.3f,
950         0.15f,
951         0.25f,
952         0.2f,
953 };
954 
955 int r_collision_tree_color_index = 0;
956 int r_collision_tree_node_opacity_index = 0;
957 
r_video_draw_collision_tree_node(r_state_t * rs,r_collision_tree_node_t * node)958 static r_status_t r_video_draw_collision_tree_node(r_state_t *rs, r_collision_tree_node_t *node)
959 {
960     r_status_t status = R_SUCCESS;
961 
962     if (node->children != NULL)
963     {
964         int i;
965 
966         for (i = 0; i < R_COLLISION_TREE_CHILD_COUNT && R_SUCCEEDED(status); ++i)
967         {
968             status = r_video_draw_collision_tree_node(rs, &node->children[i]);
969         }
970     }
971     else
972     {
973         r_vector2d_t min = { R_MAX(-320, node->min[0]), R_MAX(-240, node->min[1]) };
974         r_vector2d_t max = { R_MIN(320, node->max[0]), R_MIN(240, node->max[1]) };
975 
976         glColor4f(0.0f, 0.0f, 1.0f, r_collision_tree_node_opacities[r_collision_tree_node_opacity_index]);
977         glDisable(GL_TEXTURE_2D);
978         glBegin(GL_POLYGON);
979         glVertex3f(min[0], max[1], 0.0f);
980         glVertex3f(min[0], min[1], 0.0f);
981         glVertex3f(max[0], min[1], 0.0f);
982         glVertex3f(max[0], max[1], 0.0f);
983         glEnd();
984         glEnable(GL_TEXTURE_2D);
985 
986         r_collision_tree_node_opacity_index = (r_collision_tree_node_opacity_index + 1) % R_ARRAY_SIZE(r_collision_tree_node_opacities);
987     }
988 
989     return status;
990 }
991 
r_video_draw_collision_detector(r_state_t * rs,r_collision_detector_t * collision_detector)992 static r_status_t r_video_draw_collision_detector(r_state_t *rs, r_collision_detector_t *collision_detector)
993 {
994     unsigned int i;
995     r_status_t status = R_SUCCESS;
996 
997     /* Draw meshes for all children */
998     for (i = 0; i < collision_detector->children.count && R_SUCCEEDED(status); ++i)
999     {
1000         /* Assume the entity list is not locked (it shouldn't be when drawing) */
1001         r_entity_t *entity = (r_entity_t*)collision_detector->children.items[i].object_ref.value.object;
1002         r_mesh_t *mesh = (r_mesh_t*)entity->mesh.value.object;
1003 
1004         if (mesh != NULL)
1005         {
1006             r_transform2d_t *transform;
1007 
1008             status = r_entity_get_absolute_transform(rs, entity, &transform);
1009 
1010             if (R_SUCCEEDED(status))
1011             {
1012                 unsigned int j;
1013 
1014                 glDisable(GL_TEXTURE_2D);
1015                 r_collision_tree_color_index = 0;
1016 
1017                 for (j = 0; j < mesh->triangles.count && R_SUCCEEDED(status); ++j)
1018                 {
1019                     r_triangle_t *triangle = r_triangle_list_get_index(rs, &mesh->triangles, j);
1020                     r_vector2d_t a;
1021                     r_vector2d_t b;
1022                     r_vector2d_t c;
1023                     r_real_t *color = r_collision_tree_colors[r_collision_tree_color_index];
1024 
1025                     glColor4f(color[0], color[1], color[2], 0.25f);
1026                     r_transform2d_transform(transform, &(*triangle)[0], &a);
1027                     r_transform2d_transform(transform, &(*triangle)[1], &b);
1028                     r_transform2d_transform(transform, &(*triangle)[2], &c);
1029 
1030                     glBegin(GL_POLYGON);
1031                     glVertex3f(a[0], a[1], 0.0f);
1032                     glVertex3f(b[0], b[1], 0.0f);
1033                     glVertex3f(c[0], c[1], 0.0f);
1034                     glEnd();
1035 
1036                     r_collision_tree_color_index = (r_collision_tree_color_index + 1) % R_ARRAY_SIZE(r_collision_tree_colors);
1037                 }
1038 
1039                 /* Draw bounding rectangle */
1040                 {
1041                     r_vector2d_t *min = NULL;
1042                     r_vector2d_t *max = NULL;
1043 
1044                     status = r_entity_get_bounds(rs, entity, &min, &max);
1045 
1046                     if (R_SUCCEEDED(status))
1047                     {
1048                         glColor4f(0.25f, 0.25f, 1.0f, 0.25f);
1049 
1050                         glBegin(GL_POLYGON);
1051                         glVertex3f((*min)[0], (*max)[1], 0.0f);
1052                         glVertex3f((*min)[0], (*min)[1], 0.0f);
1053                         glVertex3f((*max)[0], (*min)[1], 0.0f);
1054                         glVertex3f((*max)[0], (*max)[1], 0.0f);
1055                         glEnd();
1056                     }
1057                 }
1058 
1059                 glEnable(GL_TEXTURE_2D);
1060             }
1061         }
1062     }
1063 
1064     if (R_SUCCEEDED(status))
1065     {
1066         /* Draw collision tree */
1067         r_collision_tree_node_opacity_index = 0;
1068         status = r_video_draw_collision_tree_node(rs, &collision_detector->tree.root);
1069     }
1070 
1071     return status;
1072 }
1073 
r_video_draw(r_state_t * rs)1074 r_status_t r_video_draw(r_state_t *rs)
1075 {
1076     r_status_t status = (rs != NULL && rs->script_state != NULL) ? R_SUCCESS : R_F_INVALID_POINTER;
1077     R_ASSERT(R_SUCCEEDED(status));
1078 
1079     /* Ensure a video mode has been set */
1080     if (R_SUCCEEDED(status))
1081     {
1082         status = rs->video_mode_set ? R_SUCCESS : R_F_NO_VIDEO_MODE_SET;
1083 
1084         if (R_FAILED(status))
1085         {
1086             r_log_error(rs, "No video mode was set (e.g. \"Video.setMode(640, 480, false)\")");
1087         }
1088     }
1089 
1090     /* Save previous frame, if capturing */
1091     if (R_SUCCEEDED(status) && rs->capture != NULL)
1092     {
1093         r_capture_t *capture = (r_capture_t*)rs->capture;
1094 
1095         /* For now, ignore capture errors */
1096         r_capture_write_video_packet(rs, capture);
1097     }
1098 
1099     /* Draw the scene */
1100     if (R_SUCCEEDED(status))
1101     {
1102         glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
1103         glLoadIdentity();
1104 
1105         /* This will set the coordinates to (0,0) in the middle, and (R_VIDEO_HEIGHT / 2 * aspect ratio, R_VIDEO_HEIGHT / 2) in the upper right */
1106         glTranslatef(0, 0, (GLfloat)(-R_VIDEO_HEIGHT / (2 * R_TAN_PI_OVER_8)));
1107         glColor4f(1, 1, 1, 1);
1108 
1109         {
1110             r_layer_t *layer = NULL;
1111 
1112             status = r_layer_stack_get_active_layer(rs, &layer);
1113 
1114             if (R_SUCCEEDED(status))
1115             {
1116                 if (layer != NULL)
1117                 {
1118                     if (layer->entities_display.object_list.count > 0)
1119                     {
1120                         status = r_video_draw_entity_list(rs, &layer->entities_display);
1121                     }
1122 
1123                     if (R_SUCCEEDED(status) && layer->debug_collision_detectors)
1124                     {
1125                         unsigned int i;
1126 
1127                         for (i = 0; i < layer->collision_detectors.count && R_SUCCEEDED(status); ++i)
1128                         {
1129                             unsigned int id = r_object_id_list_get_index(rs, &layer->collision_detectors, i);
1130                             lua_State *ls = rs->script_state;
1131                             r_status_t status_pushed = r_object_push_by_id(rs, id);
1132 
1133                             if (R_SUCCEEDED(status_pushed))
1134                             {
1135                                 status = r_video_draw_collision_detector(rs, (r_collision_detector_t*)lua_touserdata(ls, -1));
1136 
1137                                 /* Pop from the stack and move to next index in the list */
1138                                 lua_pop(ls, 1);
1139                             }
1140                         }
1141                     }
1142                 }
1143             }
1144         }
1145 
1146         /* Swap video buffers to display the scene */
1147         if (R_SUCCEEDED(status))
1148         {
1149             SDL_GL_SwapBuffers();
1150         }
1151     }
1152 
1153     return status;
1154 }
1155 
1156