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