From e80f7a44658db4226bac976a2b1af99babbe9e89 Mon Sep 17 00:00:00 2001 From: Tom Black Date: Mon, 8 Feb 2021 23:45:14 -0600 Subject: Merge Simple 2D into gem --- .gitmodules | 2 +- README.md | 4 +- assets | 2 +- bin/ruby2d | 1 + ext/ruby2d/common.c | 185 ++++++++++++ ext/ruby2d/controllers.c | 110 +++++++ ext/ruby2d/extconf.rb | 38 +-- ext/ruby2d/gl.c | 430 +++++++++++++++++++++++++++ ext/ruby2d/gl2.c | 146 +++++++++ ext/ruby2d/gl3.c | 348 ++++++++++++++++++++++ ext/ruby2d/gles.c | 308 +++++++++++++++++++ ext/ruby2d/image.c | 138 +++++++++ ext/ruby2d/input.c | 48 +++ ext/ruby2d/music.c | 114 +++++++ ext/ruby2d/ruby2d.c | 251 ++++++++-------- ext/ruby2d/ruby2d.h | 757 +++++++++++++++++++++++++++++++++++++++++++++++ ext/ruby2d/shapes.c | 154 ++++++++++ ext/ruby2d/sound.c | 93 ++++++ ext/ruby2d/sprite.c | 147 +++++++++ ext/ruby2d/text.c | 129 ++++++++ ext/ruby2d/window.c | 414 ++++++++++++++++++++++++++ lib/ruby2d/cli/build.rb | 9 +- ruby2d.gemspec | 2 +- 23 files changed, 3657 insertions(+), 173 deletions(-) create mode 100644 ext/ruby2d/common.c create mode 100644 ext/ruby2d/controllers.c create mode 100644 ext/ruby2d/gl.c create mode 100644 ext/ruby2d/gl2.c create mode 100644 ext/ruby2d/gl3.c create mode 100644 ext/ruby2d/gles.c create mode 100644 ext/ruby2d/image.c create mode 100644 ext/ruby2d/input.c create mode 100644 ext/ruby2d/music.c create mode 100644 ext/ruby2d/ruby2d.h create mode 100644 ext/ruby2d/shapes.c create mode 100644 ext/ruby2d/sound.c create mode 100644 ext/ruby2d/sprite.c create mode 100644 ext/ruby2d/text.c create mode 100644 ext/ruby2d/window.c diff --git a/.gitmodules b/.gitmodules index 2ad3adc..308f192 100644 --- a/.gitmodules +++ b/.gitmodules @@ -3,4 +3,4 @@ url = https://github.com/simple2d/test_media.git [submodule "assets"] path = assets - url = https://github.com/ruby2d/assets + url = https://github.com/ruby2d/assets.git diff --git a/README.md b/README.md index 60b242c..401e225 100644 --- a/README.md +++ b/README.md @@ -23,10 +23,9 @@ Update these submodules at any time using `git submodule update --remote` or the Next, install dependencies: - With [Bundler](http://bundler.io), run `bundle install` to get the required development gems. -- Install the native graphics library [Simple 2D](https://github.com/simple2d/simple2d) by following the instructions in its README. - Install [MRuby](http://mruby.org) in order to build Ruby 2D apps natively. On macOS using [Homebrew](https://brew.sh), run `brew install mruby`. On Ubuntu, use `sudo apt install mruby libmruby-dev` -Finally, run `rake` to build and install the gem locally. Use `rake dev` to build referencing user-installed libraries (e.g. Simple 2D and SDL). +Finally, run `rake` to build and install the gem locally. Use `rake dev` to build referencing user-installed libraries (e.g. SDL). ## Tests @@ -73,7 +72,6 @@ In order to achieve such simplicity, a lot has to happen under the hood. Whether 1. Update the [assets](https://github.com/ruby2d/assets) repo, follow the instructions in the README 2. Run `rake update` to update the submodules -3. Update the Simple 2D minimum version required in [`extconf.rb`](ext/ruby2d/extconf.rb) ## Preparing a release diff --git a/assets b/assets index 166014b..d640f88 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 166014b677a69c7966edfdffe301947fa4df9dda +Subproject commit d640f88bdda5c3882f365190b9b3b6f66e8a7a7a diff --git a/bin/ruby2d b/bin/ruby2d index 71d5f2b..a915b64 100755 --- a/bin/ruby2d +++ b/bin/ruby2d @@ -115,6 +115,7 @@ when 'launch' else puts usage_launch end +# TODO: Need add this functionality to the gem when 'simulator' case ARGV[1] when '--list' diff --git a/ext/ruby2d/common.c b/ext/ruby2d/common.c new file mode 100644 index 0000000..1950049 --- /dev/null +++ b/ext/ruby2d/common.c @@ -0,0 +1,185 @@ +// Ruby 2D Shared functions and data + +#include "ruby2d.h" + +// Initalize shared data +bool R2D_diagnostics = false; + +// Initialization status +static bool initted = false; + + +/* + * Provide a `vasprintf()` implementation for Windows + */ +#if WINDOWS && !MINGW +int vasprintf(char **strp, const char *fmt, va_list ap) { + int r = -1, size = _vscprintf(fmt, ap); + if ((size >= 0) && (size < INT_MAX)) { + *strp = (char *)malloc(size + 1); + if (*strp) { + r = vsnprintf(*strp, size + 1, fmt, ap); + if (r == -1) free(*strp); + } + } else { *strp = 0; } + return(r); +} +#endif + + +/* + * Checks if a file exists and can be accessed + */ +bool R2D_FileExists(const char *path) { + if (!path) return false; + + if (access(path, F_OK) != -1) { + return true; + } else { + return false; + } +} + + +/* + * Logs standard messages to the console + */ +void R2D_Log(int type, const char *msg, ...) { + + // Always log if diagnostics set, or if a warning or error message + if (R2D_diagnostics || type != R2D_INFO) { + + va_list args; + va_start(args, msg); + + switch (type) { + case R2D_INFO: + printf("\033[1;36mInfo:\033[0m "); + break; + case R2D_WARN: + printf("\033[1;33mWarning:\033[0m "); + break; + case R2D_ERROR: + printf("\033[1;31mError:\033[0m "); + break; + } + + vprintf(msg, args); + printf("\n"); + va_end(args); + } +} + + +/* + * Logs Ruby 2D errors to the console, with caller and message body + */ +void R2D_Error(const char *caller, const char *msg, ...) { + va_list args; + va_start(args, msg); + char *fmsg; + vasprintf(&fmsg, msg, args); + R2D_Log(R2D_ERROR, "(%s) %s", caller, fmsg); + free(fmsg); + va_end(args); +} + + +/* + * Enable/disable logging of diagnostics + */ +void R2D_Diagnostics(bool status) { + R2D_diagnostics = status; +} + + +/* + * Enable terminal colors in Windows + */ +void R2D_Windows_EnableTerminalColors() { + #if WINDOWS + HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); + DWORD dwMode = 0; + GetConsoleMode(hOut, &dwMode); + dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; + SetConsoleMode(hOut, dwMode); + #endif +} + + +/* + * Initialize Ruby 2D subsystems + */ +bool R2D_Init() { + if (initted) return true; + + // Enable terminal colors in Windows + R2D_Windows_EnableTerminalColors(); + + R2D_Log(R2D_INFO, "Initializing Ruby 2D"); + + // Initialize SDL + if (SDL_Init(SDL_INIT_EVERYTHING) != 0) { + R2D_Error("SDL_Init", SDL_GetError()); + return false; + } + + // Initialize SDL_ttf + if (TTF_Init() != 0) { + R2D_Error("TTF_Init", TTF_GetError()); + return false; + } + + // Initialize SDL_mixer + int mix_flags = MIX_INIT_FLAC | MIX_INIT_OGG | MIX_INIT_MP3; + int mix_initted = Mix_Init(mix_flags); + + // Bug in SDL2_mixer 2.0.2: + // Mix_Init should return OR'ed flags if successful, but returns 0 instead. + // Fixed in: https://hg.libsdl.org/SDL_mixer/rev/7fa15b556953 + const SDL_version *linked_version = Mix_Linked_Version(); + if (linked_version->major == 2 && linked_version->minor == 0 && linked_version->patch == 2) { + // It's version 2.0.2, don't check for Mix_Init errors + } else { + if ((mix_initted&mix_flags) != mix_flags) { + R2D_Error("Mix_Init", Mix_GetError()); + } + } + + if (Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 4096) != 0) { + R2D_Error("Mix_OpenAudio", Mix_GetError()); + return false; + } + + // Call `R2D_Quit` at program exit + atexit(R2D_Quit); + + // All subsystems initted + initted = true; + return true; +} + + +/* + * Gets the primary display's dimensions + */ +void R2D_GetDisplayDimensions(int *w, int *h) { + R2D_Init(); + SDL_DisplayMode dm; + SDL_GetCurrentDisplayMode(0, &dm); + *w = dm.w; + *h = dm.h; +} + + +/* + * Quits Ruby 2D subsystems + */ +void R2D_Quit() { + IMG_Quit(); + Mix_CloseAudio(); + Mix_Quit(); + TTF_Quit(); + SDL_Quit(); + initted = false; +} diff --git a/ext/ruby2d/controllers.c b/ext/ruby2d/controllers.c new file mode 100644 index 0000000..1566653 --- /dev/null +++ b/ext/ruby2d/controllers.c @@ -0,0 +1,110 @@ +// controllers.c + +#include "ruby2d.h" + +// Stores the last joystick instance ID seen by the system. These instance IDs +// are unique and increment with each new joystick connected. +static int last_intance_id = -1; + + +/* + * Add controller mapping from string + */ +void R2D_AddControllerMapping(const char *map) { + int result = SDL_GameControllerAddMapping(map); + + char guid[33]; + strncpy(guid, map, 32); + + switch (result) { + case 1: + R2D_Log(R2D_INFO, "Mapping added for GUID: %s", guid); + break; + case 0: + R2D_Log(R2D_INFO, "Mapping updated for GUID: %s", guid); + break; + case -1: + R2D_Error("SDL_GameControllerAddMapping", SDL_GetError()); + break; + } +} + + +/* + * Add controller mappings from the specified file + */ +void R2D_AddControllerMappingsFromFile(const char *path) { + if (!R2D_FileExists(path)) { + R2D_Log(R2D_WARN, "Controller mappings file not found: %s", path); + return; + } + + int mappings_added = SDL_GameControllerAddMappingsFromFile(path); + if (mappings_added == -1) { + R2D_Error("SDL_GameControllerAddMappingsFromFile", SDL_GetError()); + } else { + R2D_Log(R2D_INFO, "Added %i controller mapping(s)", mappings_added); + } +} + + +/* + * Check if joystick is a controller + */ +bool R2D_IsController(SDL_JoystickID id) { + return SDL_GameControllerFromInstanceID(id) == NULL ? false : true; +} + + +/* + * Open controllers and joysticks + */ +void R2D_OpenControllers() { + + char guid_str[33]; + + // Enumerate joysticks + for (int device_index = 0; device_index < SDL_NumJoysticks(); ++device_index) { + + // Check if joystick supports SDL's game controller interface (a mapping is available) + if (SDL_IsGameController(device_index)) { + SDL_GameController *controller = SDL_GameControllerOpen(device_index); + SDL_JoystickID intance_id = SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(controller)); + + SDL_JoystickGetGUIDString( + SDL_JoystickGetGUID(SDL_GameControllerGetJoystick(controller)), + guid_str, 33 + ); + + if (intance_id > last_intance_id) { + if (controller) { + R2D_Log(R2D_INFO, "Controller #%i: %s\n GUID: %s", intance_id, SDL_GameControllerName(controller), guid_str); + } else { + R2D_Log(R2D_ERROR, "Could not open controller #%i: %s", intance_id, SDL_GetError()); + } + last_intance_id = intance_id; + } + + // Controller interface not supported, try to open as joystick + } else { + SDL_Joystick *joy = SDL_JoystickOpen(device_index); + SDL_JoystickID intance_id = SDL_JoystickInstanceID(joy); + + if (!joy) { + R2D_Log(R2D_ERROR, "Could not open controller"); + } else if(intance_id > last_intance_id) { + SDL_JoystickGetGUIDString( + SDL_JoystickGetGUID(joy), + guid_str, 33 + ); + R2D_Log(R2D_INFO, + "Controller #%i: %s\n GUID: %s\n Axes: %d\n Buttons: %d\n Balls: %d", + intance_id, SDL_JoystickName(joy), guid_str, SDL_JoystickNumAxes(joy), + SDL_JoystickNumButtons(joy), SDL_JoystickNumBalls(joy) + ); + R2D_Log(R2D_WARN, "Controller #%i does not have a mapping available", intance_id); + last_intance_id = intance_id; + } + } + } +} diff --git a/ext/ruby2d/extconf.rb b/ext/ruby2d/extconf.rb index cfb741e..9aaddbb 100644 --- a/ext/ruby2d/extconf.rb +++ b/ext/ruby2d/extconf.rb @@ -1,7 +1,6 @@ require 'mkmf' require_relative '../../lib/ruby2d/cli/colorize' -S2D_VERSION = '1.2.0' # Simple 2D minimum version required $errors = [] # Holds errors # Set the OS platform @@ -31,27 +30,6 @@ def print_errors end -# Check that Simple 2D is installed and meets minimum version requirements -def check_s2d - - # Simple 2D not installed - if `which simple2d`.empty? - $errors << "Ruby 2D uses a native library called Simple 2D, which was not found." << - "To install, follow the instructions at #{"ruby2d.com".bold}" - print_errors; exit - - # Simple 2D installed, checking version - else - unless Gem::Version.new(`bash simple2d --version`) >= Gem::Version.new(S2D_VERSION) - $errors << "Simple 2D needs to be updated for this version of Ruby 2D." << - "Run the following, then try reinstalling this gem:\n" << - " simple2d update".bold - print_errors; exit - end - end -end - - # Add compiler and linker flags def add_flags(type, flags) case type @@ -106,14 +84,11 @@ def set_rpi_flags end -# Use the Simple 2D, SDL, and other libraries installed by the user (not those bundled with the gem) +# Use SDL and other libraries installed by the user (not those bundled with the gem) def use_usr_libs - check_s2d - # Add flags set_rpi_flags add_flags(:c, '-I/usr/local/include') - add_flags(:ld, `bash simple2d --libs`) end @@ -134,7 +109,6 @@ else add_flags(:c, '-I../../assets/include') ldir = "#{Dir.pwd}/../../assets/macos/lib" - add_flags(:ld, "#{ldir}/libsimple2d.a") add_flags(:ld, "#{ldir}/libSDL2.a #{ldir}/libSDL2_image.a #{ldir}/libSDL2_mixer.a #{ldir}/libSDL2_ttf.a") add_flags(:ld, "#{ldir}/libjpeg.a #{ldir}/libpng16.a #{ldir}/libtiff.a #{ldir}/libwebp.a") add_flags(:ld, "#{ldir}/libmpg123.a #{ldir}/libogg.a #{ldir}/libFLAC.a #{ldir}/libvorbis.a #{ldir}/libvorbisfile.a") @@ -143,18 +117,14 @@ else when :linux, :linux_rpi check_sdl_linux - simple2d_dir = "#{Dir.pwd}/../../assets/linux/simple2d" - - `(cd #{simple2d_dir} && make)` set_rpi_flags - add_flags(:c, "-I#{simple2d_dir}/include") - add_flags(:ld, "#{simple2d_dir}/build/libsimple2d.a -lSDL2 -lSDL2_image -lSDL2_mixer -lSDL2_ttf -lm") + add_flags(:ld, "-lSDL2 -lSDL2_image -lSDL2_mixer -lSDL2_ttf -lm") if $platform == :linux then add_flags(:ld, '-lGL') end when :windows add_flags(:c, '-I../../assets/include') - add_flags(:ld, '-L../../assets/mingw/lib -lsimple2d -lSDL2 -lSDL2_image -lSDL2_mixer -lSDL2_ttf') + add_flags(:ld, '-L../../assets/mingw/lib -lSDL2 -lSDL2_image -lSDL2_mixer -lSDL2_ttf') add_flags(:ld, '-lmingw32 -lopengl32 -lglew32') # If can't detect the platform, use libraries installed by the user @@ -165,5 +135,5 @@ end $LDFLAGS.gsub!(/\n/, ' ') # remove newlines in flags, they can cause problems -# Create the Makefile +# Create Makefile create_makefile('ruby2d/ruby2d') diff --git a/ext/ruby2d/gl.c b/ext/ruby2d/gl.c new file mode 100644 index 0000000..a2f14fa --- /dev/null +++ b/ext/ruby2d/gl.c @@ -0,0 +1,430 @@ +// Ruby 2D OpenGL Functions + +#include "ruby2d.h" + +// Set to `true` to force OpenGL 2.1 (for testing) +static bool FORCE_GL2 = false; + +// Flag set if using OpenGL 2.1 +static bool R2D_GL2 = false; + +// The orthographic projection matrix for 2D rendering. +// Elements 0 and 5 are set in R2D_GL_SetViewport. +static GLfloat orthoMatrix[16] = + { 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + -1.0f, 1.0f, -1.0f, 1.0f }; + + +/* + * Prints current GL error + */ +void R2D_GL_PrintError(char *error) { + R2D_Log(R2D_ERROR, "%s (%d)", error, glGetError()); +} + + +/* + * Print info about the current OpenGL context + */ +void R2D_GL_PrintContextInfo(R2D_Window *window) { + R2D_Log(R2D_INFO, + "OpenGL Context\n" + " GL_VENDOR: %s\n" + " GL_RENDERER: %s\n" + " GL_VERSION: %s\n" + " GL_SHADING_LANGUAGE_VERSION: %s", + window->R2D_GL_VENDOR, + window->R2D_GL_RENDERER, + window->R2D_GL_VERSION, + window->R2D_GL_SHADING_LANGUAGE_VERSION + ); +} + + +/* + * Store info about the current OpenGL context + */ +void R2D_GL_StoreContextInfo(R2D_Window *window) { + + window->R2D_GL_VENDOR = glGetString(GL_VENDOR); + window->R2D_GL_RENDERER = glGetString(GL_RENDERER); + window->R2D_GL_VERSION = glGetString(GL_VERSION); + + // These are not defined in GLES + #if GLES + window->R2D_GL_MAJOR_VERSION = 0; + window->R2D_GL_MINOR_VERSION = 0; + #else + glGetIntegerv(GL_MAJOR_VERSION, &window->R2D_GL_MAJOR_VERSION); + glGetIntegerv(GL_MINOR_VERSION, &window->R2D_GL_MINOR_VERSION); + #endif + + window->R2D_GL_SHADING_LANGUAGE_VERSION = glGetString(GL_SHADING_LANGUAGE_VERSION); +}; + + +/* + * Creates a shader object, loads shader string, and compiles. + * Returns 0 if shader could not be compiled. + */ +GLuint R2D_GL_LoadShader(GLenum type, const GLchar *shaderSrc, char *shaderName) { + + // Create the shader object + GLuint shader = glCreateShader(type); + + if (shader == 0) { + R2D_GL_PrintError("Failed to create shader program"); + return 0; + } + + // Load the shader source + glShaderSource(shader, 1, &shaderSrc, NULL); + + // Compile the shader + glCompileShader(shader); + + // Check the compile status + GLint compiled; + glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled); + + if (!compiled) { + GLint infoLen = 0; + glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen); + + if (infoLen > 1) { + char *infoLog = malloc(sizeof(char) * infoLen); + glGetShaderInfoLog(shader, infoLen, NULL, infoLog); + printf("Error compiling shader \"%s\":\n%s\n", shaderName, infoLog); + free(infoLog); + } + + glDeleteShader(shader); + return 0; + } + + return shader; +} + + +/* + * Check if shader program was linked + */ +int R2D_GL_CheckLinked(GLuint program, char *name) { + + GLint linked; + glGetProgramiv(program, GL_LINK_STATUS, &linked); + + if (!linked) { + GLint infoLen = 0; + glGetProgramiv(program, GL_INFO_LOG_LENGTH, &infoLen); + + if (infoLen > 1) { + char *infoLog = malloc(sizeof(char) * infoLen); + glGetProgramInfoLog(program, infoLen, NULL, infoLog); + printf("Error linking program `%s`: %s\n", name, infoLog); + free(infoLog); + } + + glDeleteProgram(program); + return GL_FALSE; + } + + return GL_TRUE; +} + + +/* + * Calculate the viewport's scaled width and height + */ +void R2D_GL_GetViewportScale(R2D_Window *window, int *w, int *h, double *scale) { + + double s = fmin( + window->width / (double)window->viewport.width, + window->height / (double)window->viewport.height + ); + + *w = window->viewport.width * s; + *h = window->viewport.height * s; + + if (scale) *scale = s; +} + + +/* + * Sets the viewport and matrix projection + */ +void R2D_GL_SetViewport(R2D_Window *window) { + + int ortho_w = window->viewport.width; + int ortho_h = window->viewport.height; + int x, y, w, h; // calculated GL viewport values + + x = 0; y = 0; w = window->width; h = window->height; + + switch (window->viewport.mode) { + + case R2D_FIXED: + w = window->orig_width; + h = window->orig_height; + y = window->height - h; + break; + + case R2D_EXPAND: + ortho_w = w; + ortho_h = h; + break; + + case R2D_SCALE: + R2D_GL_GetViewportScale(window, &w, &h, NULL); + // Center the viewport + x = window->width / 2.0 - w/2.0; + y = window->height / 2.0 - h/2.0; + break; + + case R2D_STRETCH: + break; + } + + glViewport(x, y, w, h); + + // Set orthographic projection matrix + orthoMatrix[0] = 2.0f / (GLfloat)ortho_w; + orthoMatrix[5] = -2.0f / (GLfloat)ortho_h; + + #if GLES + R2D_GLES_ApplyProjection(orthoMatrix); + #else + if (R2D_GL2) { + R2D_GL2_ApplyProjection(ortho_w, ortho_h); + } else { + R2D_GL3_ApplyProjection(orthoMatrix); + } + #endif +} + + +/* + * Initialize OpenGL + */ +int R2D_GL_Init(R2D_Window *window) { + + // Specify OpenGL contexts and set attributes + #if GLES + SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8); + SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8); + SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8); + SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8); + #else + // Use legacy OpenGL 2.1 + if (FORCE_GL2) { + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1); + + // Request an OpenGL 3.3 forward-compatible core profile + } else { + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); + } + #endif + + // Create and store the OpenGL context + if (FORCE_GL2) { + window->glcontext = NULL; + } else { + // Ask SDL to create an OpenGL context + window->glcontext = SDL_GL_CreateContext(window->sdl); + } + + // Check if a valid OpenGL context was created + if (window->glcontext) { + // Valid context found + + // Initialize OpenGL ES 2.0 + #if GLES + R2D_GLES_Init(); + R2D_GL_SetViewport(window); + + // Initialize OpenGL 3.3+ + #else + // Initialize GLEW on Windows + #if WINDOWS + GLenum err = glewInit(); + if (GLEW_OK != err) R2D_Error("GLEW", glewGetErrorString(err)); + #endif + R2D_GL3_Init(); + R2D_GL_SetViewport(window); + #endif + + // Context could not be created + } else { + + #if GLES + R2D_Error("GLES / SDL_GL_CreateContext", SDL_GetError()); + + #else + // Try to fallback using an OpenGL 2.1 context + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1); + + // Try creating the context again + window->glcontext = SDL_GL_CreateContext(window->sdl); + + // Check if this context was created + if (window->glcontext) { + // Valid context found + R2D_GL2 = true; + R2D_GL2_Init(); + R2D_GL_SetViewport(window); + + // Could not create any OpenGL contexts, hard failure + } else { + R2D_Error("GL2 / SDL_GL_CreateContext", SDL_GetError()); + R2D_Log(R2D_ERROR, "An OpenGL context could not be created"); + return -1; + } + #endif + } + + // Store the context and print it if diagnostics is enabled + R2D_GL_StoreContextInfo(window); + if (R2D_diagnostics) R2D_GL_PrintContextInfo(window); + + return 0; +} + + +/* + * Creates a texture for rendering + */ +void R2D_GL_CreateTexture(GLuint *id, GLint format, + int w, int h, + const GLvoid *data, GLint filter) { + + // If 0, then a new texture; generate name + if (*id == 0) glGenTextures(1, id); + + // Bind the named texture to a texturing target + glBindTexture(GL_TEXTURE_2D, *id); + + // Specifies the 2D texture image + glTexImage2D( + GL_TEXTURE_2D, 0, format, w, h, + 0, format, GL_UNSIGNED_BYTE, data + ); + + // Set the filtering mode + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter); +} + + +/* + * Free a texture + */ +void R2D_GL_FreeTexture(GLuint *id) { + if (*id != 0) { + glDeleteTextures(1, id); + *id = 0; + } +} + + +/* + * Draw a triangle + */ +void R2D_GL_DrawTriangle(GLfloat x1, GLfloat y1, + GLfloat r1, GLfloat g1, GLfloat b1, GLfloat a1, + GLfloat x2, GLfloat y2, + GLfloat r2, GLfloat g2, GLfloat b2, GLfloat a2, + GLfloat x3, GLfloat y3, + GLfloat r3, GLfloat g3, GLfloat b3, GLfloat a3) { + + #if GLES + R2D_GLES_DrawTriangle(x1, y1, r1, g1, b1, a1, + x2, y2, r2, g2, b2, a2, + x3, y3, r3, g3, b3, a3); + #else + if (R2D_GL2) { + R2D_GL2_DrawTriangle(x1, y1, r1, g1, b1, a1, + x2, y2, r2, g2, b2, a2, + x3, y3, r3, g3, b3, a3); + } else { + R2D_GL3_DrawTriangle(x1, y1, r1, g1, b1, a1, + x2, y2, r2, g2, b2, a2, + x3, y3, r3, g3, b3, a3); + } + #endif +} + + +/* + * Draw an image + */ +void R2D_GL_DrawImage(R2D_Image *img) { + #if GLES + R2D_GLES_DrawImage(img); + #else + if (R2D_GL2) { + R2D_GL2_DrawImage(img); + } else { + R2D_GL3_DrawImage(img); + } + #endif +} + + +/* + * Draw sprite + */ +void R2D_GL_DrawSprite(R2D_Sprite *spr) { + #if GLES + R2D_GLES_DrawSprite(spr); + #else + if (R2D_GL2) { + R2D_GL2_DrawSprite(spr); + } else { + R2D_GL3_DrawSprite(spr); + } + #endif +} + + +/* + * Draw text + */ +void R2D_GL_DrawText(R2D_Text *txt) { + #if GLES + R2D_GLES_DrawText(txt); + #else + if (R2D_GL2) { + R2D_GL2_DrawText(txt); + } else { + R2D_GL3_DrawText(txt); + } + #endif +} + + +/* + * Render and flush OpenGL buffers + */ +void R2D_GL_FlushBuffers() { + // Only implemented in our OpenGL 3.3+ and ES 2.0 renderers + #if GLES + // TODO: R2D_GLES_FlushBuffers(); + #else + if (!R2D_GL2) R2D_GL3_FlushBuffers(); + #endif +} + + +/* + * Clear buffers to given color values + */ +void R2D_GL_Clear(R2D_Color clr) { + glClearColor(clr.r, clr.g, clr.b, clr.a); + glClear(GL_COLOR_BUFFER_BIT); +} diff --git a/ext/ruby2d/gl2.c b/ext/ruby2d/gl2.c new file mode 100644 index 0000000..92e6408 --- /dev/null +++ b/ext/ruby2d/gl2.c @@ -0,0 +1,146 @@ +// OpenGL 2.1 + +#include "ruby2d.h" + +#if !GLES + + +/* + * Applies the projection matrix + */ +void R2D_GL2_ApplyProjection(int w, int h) { + + // Initialize the projection matrix + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + + // Multiply the current matrix with the orthographic matrix + glOrtho(0.f, w, h, 0.f, -1.f, 1.f); + + // Initialize the model-view matrix + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); +} + + +/* + * Initalize OpenGL + */ +int R2D_GL2_Init() { + + GLenum error = GL_NO_ERROR; + + // Enable transparency + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + // Check for errors + error = glGetError(); + if (error != GL_NO_ERROR) { + R2D_GL_PrintError("OpenGL initialization failed"); + return 1; + } else { + return 0; + } +} + + +/* + * Draw triangle + */ +void R2D_GL2_DrawTriangle(GLfloat x1, GLfloat y1, + GLfloat r1, GLfloat g1, GLfloat b1, GLfloat a1, + GLfloat x2, GLfloat y2, + GLfloat r2, GLfloat g2, GLfloat b2, GLfloat a2, + GLfloat x3, GLfloat y3, + GLfloat r3, GLfloat g3, GLfloat b3, GLfloat a3) { + + glBegin(GL_TRIANGLES); + glColor4f(r1, g1, b1, a1); glVertex2f(x1, y1); + glColor4f(r2, g2, b2, a2); glVertex2f(x2, y2); + glColor4f(r3, g3, b3, a3); glVertex2f(x3, y3); + glEnd(); +} + + +/* + * Draw texture + */ +static void R2D_GL2_DrawTexture(int x, int y, int w, int h, + GLfloat angle, GLfloat rx, GLfloat ry, + GLfloat r, GLfloat g, GLfloat b, GLfloat a, + GLfloat tx1, GLfloat ty1, GLfloat tx2, GLfloat ty2, + GLfloat tx3, GLfloat ty3, GLfloat tx4, GLfloat ty4, + GLuint texture_id) { + + R2D_GL_Point v1 = { .x = x, .y = y }; + R2D_GL_Point v2 = { .x = x + w, .y = y }; + R2D_GL_Point v3 = { .x = x + w, .y = y + h }; + R2D_GL_Point v4 = { .x = x, .y = y + h }; + + // Rotate vertices + if (angle != 0) { + v1 = R2D_RotatePoint(v1, angle, rx, ry); + v2 = R2D_RotatePoint(v2, angle, rx, ry); + v3 = R2D_RotatePoint(v3, angle, rx, ry); + v4 = R2D_RotatePoint(v4, angle, rx, ry); + } + + glEnable(GL_TEXTURE_2D); + + glBindTexture(GL_TEXTURE_2D, texture_id); + + glBegin(GL_QUADS); + glColor4f(r, g, b, a); + glTexCoord2f(tx1, ty1); glVertex2f(v1.x, v1.y); + glTexCoord2f(tx2, ty2); glVertex2f(v2.x, v2.y); + glTexCoord2f(tx3, ty3); glVertex2f(v3.x, v3.y); + glTexCoord2f(tx4, ty4); glVertex2f(v4.x, v4.y); + glEnd(); + + glDisable(GL_TEXTURE_2D); +} + + +/* + * Draw image + */ +void R2D_GL2_DrawImage(R2D_Image *img) { + R2D_GL2_DrawTexture( + img->x, img->y, img->width, img->height, + img->rotate, img->rx, img->ry, + img->color.r, img->color.g, img->color.b, img->color.a, + 0.f, 0.f, 1.f, 0.f, 1.f, 1.f, 0.f, 1.f, + img->texture_id + ); +} + + +/* + * Draw sprite + */ +void R2D_GL2_DrawSprite(R2D_Sprite *spr) { + R2D_GL2_DrawTexture( + spr->x, spr->y, spr->width, spr->height, + spr->rotate, spr->rx, spr->ry, + spr->color.r, spr->color.g, spr->color.b, spr->color.a, + spr->tx1, spr->ty1, spr->tx2, spr->ty2, spr->tx3, spr->ty3, spr->tx4, spr->ty4, + spr->img->texture_id + ); +} + + +/* + * Draw text + */ +void R2D_GL2_DrawText(R2D_Text *txt) { + R2D_GL2_DrawTexture( + txt->x, txt->y, txt->width, txt->height, + txt->rotate, txt->rx, txt->ry, + txt->color.r, txt->color.g, txt->color.b, txt->color.a, + 0.f, 0.f, 1.f, 0.f, 1.f, 1.f, 0.f, 1.f, + txt->texture_id + ); +} + +#endif diff --git a/ext/ruby2d/gl3.c b/ext/ruby2d/gl3.c new file mode 100644 index 0000000..f7cfe31 --- /dev/null +++ b/ext/ruby2d/gl3.c @@ -0,0 +1,348 @@ +// OpenGL 3.3+ + +#include "ruby2d.h" + +// Skip this file if OpenGL ES +#if !GLES + +static GLuint vbo; // our primary vertex buffer object (VBO) +static GLuint vboSize; // size of the VBO in bytes +static GLfloat *vboData; // pointer to the VBO data +static GLfloat *vboDataCurrent; // pointer to the data for the current vertices +static GLuint vboDataIndex = 0; // index of the current object being rendered +static GLuint vboObjCapacity = 2500; // number of objects the VBO can store +static GLuint shaderProgram; // triangle shader program +static GLuint texShaderProgram; // texture shader program +static GLuint indices[] = // indices for rendering textured quads + { 0, 1, 2, + 2, 3, 0 }; + + +/* + * Applies the projection matrix + */ +void R2D_GL3_ApplyProjection(GLfloat orthoMatrix[16]) { + + // Use the program object + glUseProgram(shaderProgram); + + // Apply the projection matrix to the triangle shader + glUniformMatrix4fv( + glGetUniformLocation(shaderProgram, "u_mvpMatrix"), + 1, GL_FALSE, orthoMatrix + ); + + // Use the texture program object + glUseProgram(texShaderProgram); + + // Apply the projection matrix to the texture shader + glUniformMatrix4fv( + glGetUniformLocation(texShaderProgram, "u_mvpMatrix"), + 1, GL_FALSE, orthoMatrix + ); +} + + +/* + * Initalize OpenGL + */ +int R2D_GL3_Init() { + + // Enable transparency + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + // Vertex shader source string + GLchar vertexSource[] = + "#version 150 core\n" // shader version + + "uniform mat4 u_mvpMatrix;" // projection matrix + + // Input attributes to the vertex shader + "in vec4 position;" // position value + "in vec4 color;" // vertex color + "in vec2 texcoord;" // texture coordinates + + // Outputs to the fragment shader + "out vec4 Color;" // vertex color + "out vec2 Texcoord;" // texture coordinates + + "void main() {" + // Send the color and texture coordinates right through to the fragment shader + " Color = color;" + " Texcoord = texcoord;" + // Transform the vertex position using the projection matrix + " gl_Position = u_mvpMatrix * position;" + "}"; + + // Fragment shader source string + GLchar fragmentSource[] = + "#version 150 core\n" // shader version + "in vec4 Color;" // input color from vertex shader + "out vec4 outColor;" // output fragment color + + "void main() {" + " outColor = Color;" // pass the color right through + "}"; + + // Fragment shader source string for textures + GLchar texFragmentSource[] = + "#version 150 core\n" // shader version + "in vec4 Color;" // input color from vertex shader + "in vec2 Texcoord;" // input texture coordinates + "out vec4 outColor;" // output fragment color + "uniform sampler2D tex;" // 2D texture unit + + "void main() {" + // Apply the texture unit, texture coordinates, and color + " outColor = texture(tex, Texcoord) * Color;" + "}"; + + // Create a vertex array object + GLuint vao; + glGenVertexArrays(1, &vao); + glBindVertexArray(vao); + + // Create a vertex buffer object and allocate data + glGenBuffers(1, &vbo); + glBindBuffer(GL_ARRAY_BUFFER, vbo); + vboSize = vboObjCapacity * sizeof(GLfloat) * 24; + vboData = (GLfloat *) malloc(vboSize); + vboDataCurrent = vboData; + + // Create an element buffer object + GLuint ebo; + glGenBuffers(1, &ebo); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo); + + // Load the vertex and fragment shaders + GLuint vertexShader = R2D_GL_LoadShader( GL_VERTEX_SHADER, vertexSource, "GL3 Vertex"); + GLuint fragmentShader = R2D_GL_LoadShader(GL_FRAGMENT_SHADER, fragmentSource, "GL3 Fragment"); + GLuint texFragmentShader = R2D_GL_LoadShader(GL_FRAGMENT_SHADER, texFragmentSource, "GL3 Texture Fragment"); + + // Triangle Shader // + + // Create the shader program object + shaderProgram = glCreateProgram(); + + // Check if program was created successfully + if (shaderProgram == 0) { + R2D_GL_PrintError("Failed to create shader program"); + return GL_FALSE; + } + + // Attach the shader objects to the program object + glAttachShader(shaderProgram, vertexShader); + glAttachShader(shaderProgram, fragmentShader); + + // Bind the output color variable to the fragment shader color number + glBindFragDataLocation(shaderProgram, 0, "outColor"); + + // Link the shader program + glLinkProgram(shaderProgram); + + // Check if linked + R2D_GL_CheckLinked(shaderProgram, "GL3 shader"); + + // Specify the layout of the position vertex data... + GLint posAttrib = glGetAttribLocation(shaderProgram, "position"); + glEnableVertexAttribArray(posAttrib); + glVertexAttribPointer(posAttrib, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), 0); + + // ...and the color vertex data + GLint colAttrib = glGetAttribLocation(shaderProgram, "color"); + glEnableVertexAttribArray(colAttrib); + glVertexAttribPointer(colAttrib, 4, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (void*)(2 * sizeof(GLfloat))); + + // Texture Shader // + + // Create the texture shader program object + texShaderProgram = glCreateProgram(); + + // Check if program was created successfully + if (texShaderProgram == 0) { + R2D_GL_PrintError("Failed to create shader program"); + return GL_FALSE; + } + + // Attach the shader objects to the program object + glAttachShader(texShaderProgram, vertexShader); + glAttachShader(texShaderProgram, texFragmentShader); + + // Bind the output color variable to the fragment shader color number + glBindFragDataLocation(texShaderProgram, 0, "outColor"); + + // Link the shader program + glLinkProgram(texShaderProgram); + + // Check if linked + R2D_GL_CheckLinked(texShaderProgram, "GL3 texture shader"); + + // Specify the layout of the position vertex data... + posAttrib = glGetAttribLocation(texShaderProgram, "position"); + glVertexAttribPointer(posAttrib, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), 0); + glEnableVertexAttribArray(posAttrib); + + // ...and the color vertex data... + colAttrib = glGetAttribLocation(texShaderProgram, "color"); + glVertexAttribPointer(colAttrib, 4, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (void*)(2 * sizeof(GLfloat))); + glEnableVertexAttribArray(colAttrib); + + // ...and the texture coordinates + GLint texAttrib = glGetAttribLocation(texShaderProgram, "texcoord"); + glVertexAttribPointer(texAttrib, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (void*)(6 * sizeof(GLfloat))); + glEnableVertexAttribArray(texAttrib); + + // Clean up + glDeleteShader(vertexShader); + glDeleteShader(fragmentShader); + glDeleteShader(texFragmentShader); + + // If successful, return true + return GL_TRUE; +} + + +/* + * Render the vertex buffer and reset it + */ +void R2D_GL3_FlushBuffers() { + + // Use the triangle shader program + glUseProgram(shaderProgram); + + // Bind to the vertex buffer object and update its data + glBindBuffer(GL_ARRAY_BUFFER, vbo); + glBufferData(GL_ARRAY_BUFFER, vboSize, NULL, GL_DYNAMIC_DRAW); + glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(GLfloat) * vboDataIndex * 24, vboData); + + // Render all the triangles in the buffer + glDrawArrays(GL_TRIANGLES, 0, (GLsizei)(vboDataIndex * 3)); + + // Reset the buffer object index and data pointer + vboDataIndex = 0; + vboDataCurrent = vboData; +} + + +/* + * Draw triangle + */ +void R2D_GL3_DrawTriangle(GLfloat x1, GLfloat y1, + GLfloat r1, GLfloat g1, GLfloat b1, GLfloat a1, + GLfloat x2, GLfloat y2, + GLfloat r2, GLfloat g2, GLfloat b2, GLfloat a2, + GLfloat x3, GLfloat y3, + GLfloat r3, GLfloat g3, GLfloat b3, GLfloat a3) { + + // If buffer is full, flush it + if (vboDataIndex >= vboObjCapacity) R2D_GL3_FlushBuffers(); + + // Set the triangle data into a formatted array + GLfloat vertices[] = + { x1, y1, r1, g1, b1, a1, 0, 0, + x2, y2, r2, g2, b2, a2, 0, 0, + x3, y3, r3, g3, b3, a3, 0, 0 }; + + // Copy the vertex data into the current position of the buffer + memcpy(vboDataCurrent, vertices, sizeof(vertices)); + + // Increment the buffer object index and the vertex data pointer for next use + vboDataIndex++; + vboDataCurrent = (GLfloat *)((char *)vboDataCurrent + (sizeof(GLfloat) * 24)); +} + + +/* + * Draw a texture + */ +static void R2D_GL3_DrawTexture(int x, int y, int w, int h, + GLfloat angle, GLfloat rx, GLfloat ry, + GLfloat r, GLfloat g, GLfloat b, GLfloat a, + GLfloat tx1, GLfloat ty1, GLfloat tx2, GLfloat ty2, + GLfloat tx3, GLfloat ty3, GLfloat tx4, GLfloat ty4, + GLuint texture_id) { + + // Currently, textures are not buffered, so we have to flush all buffers so + // textures get rendered in the correct Z order + R2D_GL3_FlushBuffers(); + + // Set up the vertex points + R2D_GL_Point v1 = { .x = x, .y = y }; + R2D_GL_Point v2 = { .x = x + w, .y = y }; + R2D_GL_Point v3 = { .x = x + w, .y = y + h }; + R2D_GL_Point v4 = { .x = x, .y = y + h }; + + // Rotate vertices + if (angle != 0) { + v1 = R2D_RotatePoint(v1, angle, rx, ry); + v2 = R2D_RotatePoint(v2, angle, rx, ry); + v3 = R2D_RotatePoint(v3, angle, rx, ry); + v4 = R2D_RotatePoint(v4, angle, rx, ry); + } + + // Set the textured quad data into a formatted array + GLfloat vertices[] = + // vertex coords | colors | x, y texture coords + { v1.x, v1.y, r, g, b, a, tx1, ty1, // Top-left + v2.x, v2.y, r, g, b, a, tx2, ty2, // Top-right + v3.x, v3.y, r, g, b, a, tx3, ty3, // Bottom-right + v4.x, v4.y, r, g, b, a, tx4, ty4 }; // Bottom-left + + // Use the texture shader program + glUseProgram(texShaderProgram); + + // Bind the texture using the provided ID + glBindTexture(GL_TEXTURE_2D, texture_id); + + // Create and Initialize the vertex data and array indices + glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW); + + // Render the textured quad + glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); +} + + +/* + * Draw image + */ +void R2D_GL3_DrawImage(R2D_Image *img) { + R2D_GL3_DrawTexture( + img->x, img->y, img->width, img->height, + img->rotate, img->rx, img->ry, + img->color.r, img->color.g, img->color.b, img->color.a, + 0.f, 0.f, 1.f, 0.f, 1.f, 1.f, 0.f, 1.f, + img->texture_id + ); +} + + +/* + * Draw sprite + */ +void R2D_GL3_DrawSprite(R2D_Sprite *spr) { + R2D_GL3_DrawTexture( + spr->x, spr->y, spr->width, spr->height, + spr->rotate, spr->rx, spr->ry, + spr->color.r, spr->color.g, spr->color.b, spr->color.a, + spr->tx1, spr->ty1, spr->tx2, spr->ty2, spr->tx3, spr->ty3, spr->tx4, spr->ty4, + spr->img->texture_id + ); +} + + +/* + * Draw text + */ +void R2D_GL3_DrawText(R2D_Text *txt) { + R2D_GL3_DrawTexture( + txt->x, txt->y, txt->width, txt->height, + txt->rotate, txt->rx, txt->ry, + txt->color.r, txt->color.g, txt->color.b, txt->color.a, + 0.f, 0.f, 1.f, 0.f, 1.f, 1.f, 0.f, 1.f, + txt->texture_id + ); +} + +#endif diff --git a/ext/ruby2d/gles.c b/ext/ruby2d/gles.c new file mode 100644 index 0000000..81da94c --- /dev/null +++ b/ext/ruby2d/gles.c @@ -0,0 +1,308 @@ +// OpenGL ES 2.0 + +#include "ruby2d.h" + +#if GLES + +// Triangle shader +static GLuint shaderProgram; +static GLuint positionLocation; +static GLuint colorLocation; + +// Texture shader +static GLuint texShaderProgram; +static GLuint texPositionLocation; +static GLuint texColorLocation; +static GLuint texCoordLocation; +static GLuint samplerLocation; + +static GLushort indices[] = + { 0, 1, 2, + 2, 3, 0 }; + + +/* + * Applies the projection matrix + */ +void R2D_GLES_ApplyProjection(GLfloat orthoMatrix[16]) { + + // Use the program object + glUseProgram(shaderProgram); + + glUniformMatrix4fv( + glGetUniformLocation(shaderProgram, "u_mvpMatrix"), + 1, GL_FALSE, orthoMatrix + ); + + // Use the texture program object + glUseProgram(texShaderProgram); + + glUniformMatrix4fv( + glGetUniformLocation(texShaderProgram, "u_mvpMatrix"), + 1, GL_FALSE, orthoMatrix + ); +} + + +/* + * Initalize OpenGL ES + */ +int R2D_GLES_Init() { + + // Enable transparency + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + // Vertex shader source string + GLchar vertexSource[] = + // uniforms used by the vertex shader + "uniform mat4 u_mvpMatrix;" // projection matrix + + // attributes input to the vertex shader + "attribute vec4 a_position;" // position value + "attribute vec4 a_color;" // input vertex color + "attribute vec2 a_texcoord;" // input texture + + // varying variables, input to the fragment shader + "varying vec4 v_color;" // output vertex color + "varying vec2 v_texcoord;" // output texture + + "void main()" + "{" + " v_color = a_color;" + " v_texcoord = a_texcoord;" + " gl_Position = u_mvpMatrix * a_position;" + "}"; + + // Fragment shader source string + GLchar fragmentSource[] = + "precision mediump float;" + // input vertex color from vertex shader + "varying vec4 v_color;" + + "void main()" + "{" + " gl_FragColor = v_color;" + "}"; + + // Fragment shader source string for textures + GLchar texFragmentSource[] = + "precision mediump float;" + // input vertex color from vertex shader + "varying vec4 v_color;" + "varying vec2 v_texcoord;" + "uniform sampler2D s_texture;" + + "void main()" + "{" + " gl_FragColor = texture2D(s_texture, v_texcoord) * v_color;" + "}"; + + // Load the vertex and fragment shaders + GLuint vertexShader = R2D_GL_LoadShader( GL_VERTEX_SHADER, vertexSource, "GLES Vertex"); + GLuint fragmentShader = R2D_GL_LoadShader(GL_FRAGMENT_SHADER, fragmentSource, "GLES Fragment"); + GLuint texFragmentShader = R2D_GL_LoadShader(GL_FRAGMENT_SHADER, texFragmentSource, "GLES Texture Fragment"); + + // Triangle Shader // + + // Create the shader program object + shaderProgram = glCreateProgram(); + + // Check if program was created successfully + if (shaderProgram == 0) { + R2D_GL_PrintError("Failed to create shader program"); + return GL_FALSE; + } + + // Attach the shader objects to the program object + glAttachShader(shaderProgram, vertexShader); + glAttachShader(shaderProgram, fragmentShader); + + // Link the shader program + glLinkProgram(shaderProgram); + + // Check if linked + R2D_GL_CheckLinked(shaderProgram, "GLES shader"); + + // Get the attribute locations + positionLocation = glGetAttribLocation(shaderProgram, "a_position"); + colorLocation = glGetAttribLocation(shaderProgram, "a_color"); + + // Texture Shader // + + // Create the texture shader program object + texShaderProgram = glCreateProgram(); + + // Check if program was created successfully + if (texShaderProgram == 0) { + R2D_GL_PrintError("Failed to create shader program"); + return GL_FALSE; + } + + // Attach the shader objects to the program object + glAttachShader(texShaderProgram, vertexShader); + glAttachShader(texShaderProgram, texFragmentShader); + + // Link the shader program + glLinkProgram(texShaderProgram); + + // Check if linked + R2D_GL_CheckLinked(texShaderProgram, "GLES texture shader"); + + // Get the attribute locations + texPositionLocation = glGetAttribLocation(texShaderProgram, "a_position"); + texColorLocation = glGetAttribLocation(texShaderProgram, "a_color"); + texCoordLocation = glGetAttribLocation(texShaderProgram, "a_texcoord"); + + // Get the sampler location + samplerLocation = glGetUniformLocation(texShaderProgram, "s_texture"); + + // Clean up + glDeleteShader(vertexShader); + glDeleteShader(fragmentShader); + glDeleteShader(texFragmentShader); + + return GL_TRUE; +} + + +/* + * Draw triangle + */ +void R2D_GLES_DrawTriangle(GLfloat x1, GLfloat y1, + GLfloat r1, GLfloat g1, GLfloat b1, GLfloat a1, + GLfloat x2, GLfloat y2, + GLfloat r2, GLfloat g2, GLfloat b2, GLfloat a2, + GLfloat x3, GLfloat y3, + GLfloat r3, GLfloat g3, GLfloat b3, GLfloat a3) { + + GLfloat vertices[] = + { x1, y1, 0.f, + x2, y2, 0.f, + x3, y3, 0.f }; + + GLfloat colors[] = + { r1, g1, b1, a1, + r2, g2, b2, a2, + r3, g3, b3, a3 }; + + glUseProgram(shaderProgram); + + // Load the vertex position + glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, vertices); + glEnableVertexAttribArray(positionLocation); + + // Load the colors + glVertexAttribPointer(colorLocation, 4, GL_FLOAT, GL_FALSE, 0, colors); + glEnableVertexAttribArray(colorLocation); + + // draw + glDrawArrays(GL_TRIANGLES, 0, 3); +} + + +/* + * Draw a texture + */ +static void R2D_GLES_DrawTexture(int x, int y, int w, int h, + GLfloat angle, GLfloat rx, GLfloat ry, + GLfloat r, GLfloat g, GLfloat b, GLfloat a, + GLfloat tx1, GLfloat ty1, GLfloat tx2, GLfloat ty2, + GLfloat tx3, GLfloat ty3, GLfloat tx4, GLfloat ty4, + GLuint texture_id) { + + R2D_GL_Point v1 = { .x = x, .y = y }; + R2D_GL_Point v2 = { .x = x + w, .y = y }; + R2D_GL_Point v3 = { .x = x + w, .y = y + h }; + R2D_GL_Point v4 = { .x = x, .y = y + h }; + + // Rotate vertices + if (angle != 0) { + v1 = R2D_RotatePoint(v1, angle, rx, ry); + v2 = R2D_RotatePoint(v2, angle, rx, ry); + v3 = R2D_RotatePoint(v3, angle, rx, ry); + v4 = R2D_RotatePoint(v4, angle, rx, ry); + } + + GLfloat vertices[] = + // x, y coords | x, y texture coords + { v1.x, v1.y, 0.f, tx1, ty1, + v2.x, v2.y, 0.f, tx2, ty2, + v3.x, v3.y, 0.f, tx3, ty3, + v4.x, v4.y, 0.f, tx4, ty4 }; + + GLfloat colors[] = + { r, g, b, a, + r, g, b, a, + r, g, b, a, + r, g, b, a }; + + glUseProgram(texShaderProgram); + + // Load the vertex position + glVertexAttribPointer(texPositionLocation, 3, GL_FLOAT, GL_FALSE, + 5 * sizeof(GLfloat), vertices); + glEnableVertexAttribArray(texPositionLocation); + + // Load the colors + glVertexAttribPointer(texColorLocation, 4, GL_FLOAT, GL_FALSE, 0, colors); + glEnableVertexAttribArray(texColorLocation); + + // Load the texture coordinate + glVertexAttribPointer(texCoordLocation, 2, GL_FLOAT, GL_FALSE, + 5 * sizeof(GLfloat), &vertices[3]); + glEnableVertexAttribArray(texCoordLocation); + + // Bind the texture + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, texture_id); + + // Set the sampler texture unit to 0 + glUniform1i(samplerLocation, 0); + + glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, indices); +} + + +/* + * Draw image + */ +void R2D_GLES_DrawImage(R2D_Image *img) { + R2D_GLES_DrawTexture( + img->x, img->y, img->width, img->height, + img->rotate, img->rx, img->ry, + img->color.r, img->color.g, img->color.b, img->color.a, + 0.f, 0.f, 1.f, 0.f, 1.f, 1.f, 0.f, 1.f, + img->texture_id + ); +} + + +/* + * Draw sprite + */ +void R2D_GLES_DrawSprite(R2D_Sprite *spr) { + R2D_GLES_DrawTexture( + spr->x, spr->y, spr->width, spr->height, + spr->rotate, spr->rx, spr->ry, + spr->color.r, spr->color.g, spr->color.b, spr->color.a, + spr->tx1, spr->ty1, spr->tx2, spr->ty2, spr->tx3, spr->ty3, spr->tx4, spr->ty4, + spr->img->texture_id + ); +} + + +/* + * Draw text + */ +void R2D_GLES_DrawText(R2D_Text *txt) { + R2D_GLES_DrawTexture( + txt->x, txt->y, txt->width, txt->height, + txt->rotate, txt->rx, txt->ry, + txt->color.r, txt->color.g, txt->color.b, txt->color.a, + 0.f, 0.f, 1.f, 0.f, 1.f, 1.f, 0.f, 1.f, + txt->texture_id + ); +} + +#endif diff --git a/ext/ruby2d/image.c b/ext/ruby2d/image.c new file mode 100644 index 0000000..68d0c30 --- /dev/null +++ b/ext/ruby2d/image.c @@ -0,0 +1,138 @@ +// image.c + +#include "ruby2d.h" + + +/* + * Create an image, given a file path + */ +R2D_Image *R2D_CreateImage(const char *path) { + R2D_Init(); + + // Check if image file exists + if (!R2D_FileExists(path)) { + R2D_Error("R2D_CreateImage", "Image file `%s` not found", path); + return NULL; + } + + // Allocate the image structure + R2D_Image *img = (R2D_Image *) malloc(sizeof(R2D_Image)); + if (!img) { + R2D_Error("R2D_CreateImage", "Out of memory!"); + return NULL; + } + + // Load image from file as SDL_Surface + img->surface = IMG_Load(path); + if (!img->surface) { + R2D_Error("IMG_Load", IMG_GetError()); + free(img); + return NULL; + } + + int bits_per_color = img->surface->format->Amask == 0 ? + img->surface->format->BitsPerPixel / 3 : + img->surface->format->BitsPerPixel / 4; + + if (bits_per_color < 8) { + R2D_Log(R2D_WARN, "`%s` has less than 8 bits per color and will likely not render correctly", path, bits_per_color); + } + + // Initialize values + img->path = path; + img->x = 0; + img->y = 0; + img->color.r = 1.f; + img->color.g = 1.f; + img->color.b = 1.f; + img->color.a = 1.f; + img->orig_width = img->surface->w; + img->orig_height = img->surface->h; + img->width = img->orig_width; + img->height = img->orig_height; + img->rotate = 0; + img->rx = 0; + img->ry = 0; + img->texture_id = 0; + + // Detect image mode + img->format = GL_RGB; + if (img->surface->format->BytesPerPixel == 4) { + img->format = GL_RGBA; + } + + // Flip image bits if BGA + + Uint32 r = img->surface->format->Rmask; + Uint32 g = img->surface->format->Gmask; + Uint32 a = img->surface->format->Amask; + + if (r&0xFF000000 || r&0xFF0000) { + char *p = (char *)img->surface->pixels; + int bpp = img->surface->format->BytesPerPixel; + int w = img->surface->w; + int h = img->surface->h; + char tmp; + for (int i = 0; i < bpp * w * h; i += bpp) { + if (a&0xFF) { + tmp = p[i]; + p[i] = p[i+3]; + p[i+3] = tmp; + } + if (g&0xFF0000) { + tmp = p[i+1]; + p[i+1] = p[i+2]; + p[i+2] = tmp; + } + if (r&0xFF0000) { + tmp = p[i]; + p[i] = p[i+2]; + p[i+2] = tmp; + } + } + } + + return img; +} + + +/* + * Rotate an image + */ +void R2D_RotateImage(R2D_Image *img, GLfloat angle, int position) { + + R2D_GL_Point p = R2D_GetRectRotationPoint( + img->x, img->y, img->width, img->height, position + ); + + img->rotate = angle; + img->rx = p.x; + img->ry = p.y; +} + + +/* + * Draw an image + */ +void R2D_DrawImage(R2D_Image *img) { + if (!img) return; + + if (img->texture_id == 0) { + R2D_GL_CreateTexture(&img->texture_id, img->format, + img->orig_width, img->orig_height, + img->surface->pixels, GL_NEAREST); + SDL_FreeSurface(img->surface); + } + + R2D_GL_DrawImage(img); +} + + +/* + * Free an image + */ +void R2D_FreeImage(R2D_Image *img) { + if (!img) return; + R2D_GL_FreeTexture(&img->texture_id); + free(img); +} diff --git a/ext/ruby2d/input.c b/ext/ruby2d/input.c new file mode 100644 index 0000000..d992d4d --- /dev/null +++ b/ext/ruby2d/input.c @@ -0,0 +1,48 @@ +// input.c + +#include "ruby2d.h" + + +/* + * Get the mouse coordinates relative to the viewport + */ +void R2D_GetMouseOnViewport(R2D_Window *window, int wx, int wy, int *x, int *y) { + + double scale; // viewport scale factor + int w, h; // width and height of scaled viewport + + switch (window->viewport.mode) { + + case R2D_FIXED: case R2D_EXPAND: + *x = wx / (window->orig_width / (double)window->viewport.width); + *y = wy / (window->orig_height / (double)window->viewport.height); + break; + + case R2D_SCALE: + R2D_GL_GetViewportScale(window, &w, &h, &scale); + *x = wx * 1 / scale - (window->width - w) / (2.0 * scale); + *y = wy * 1 / scale - (window->height - h) / (2.0 * scale); + break; + + case R2D_STRETCH: + *x = wx * window->viewport.width / (double)window->width; + *y = wy * window->viewport.height / (double)window->height; + break; + } +} + + +/* + * Show the cursor over the window + */ +void R2D_ShowCursor() { + SDL_ShowCursor(SDL_ENABLE); +} + + +/* + * Hide the cursor over the window + */ +void R2D_HideCursor() { + SDL_ShowCursor(SDL_DISABLE); +} diff --git a/ext/ruby2d/music.c b/ext/ruby2d/music.c new file mode 100644 index 0000000..0c1716f --- /dev/null +++ b/ext/ruby2d/music.c @@ -0,0 +1,114 @@ +// music.c + +#include "ruby2d.h" + + +/* + * Create the music + */ +R2D_Music *R2D_CreateMusic(const char *path) { + R2D_Init(); + + // Check if music file exists + if (!R2D_FileExists(path)) { + R2D_Error("R2D_CreateMusic", "Music file `%s` not found", path); + return NULL; + } + + // Allocate the music structure + R2D_Music *mus = (R2D_Music *) malloc(sizeof(R2D_Music)); + if (!mus) { + R2D_Error("R2D_CreateMusic", "Out of memory!"); + return NULL; + } + + // Load the music data from file + mus->data = Mix_LoadMUS(path); + if (!mus->data) { + R2D_Error("Mix_LoadMUS", Mix_GetError()); + free(mus); + return NULL; + } + + // Initialize values + mus->path = path; + + return mus; +} + + +/* + * Play the music + */ +void R2D_PlayMusic(R2D_Music *mus, bool loop) { + if (!mus) return; + + // If looping, set to -1 times; else 0 + int times = loop ? -1 : 0; + + // times: 0 == once, -1 == forever + if (Mix_PlayMusic(mus->data, times) == -1) { + // No music for you + R2D_Error("R2D_PlayMusic", Mix_GetError()); + } +} + + +/* + * Pause the playing music + */ +void R2D_PauseMusic() { + Mix_PauseMusic(); +} + + +/* + * Resume the current music + */ +void R2D_ResumeMusic() { + Mix_ResumeMusic(); +} + + +/* + * Stop the playing music; interrupts fader effects + */ +void R2D_StopMusic() { + Mix_HaltMusic(); +} + + +/* + * Get the music volume + */ +int R2D_GetMusicVolume() { + // Get music volume as percentage of maximum mix volume + return ceil(Mix_VolumeMusic(-1) * (100.0 / MIX_MAX_VOLUME)); +} + + +/* + * Set the music volume a given percentage + */ +void R2D_SetMusicVolume(int volume) { + // Set volume to be a percentage of the maximum mix volume + Mix_VolumeMusic((volume / 100.0) * MIX_MAX_VOLUME); +} + + +/* + * Fade out the playing music + */ +void R2D_FadeOutMusic(int ms) { + Mix_FadeOutMusic(ms); +} + + +/* + * Free the music + */ +void R2D_FreeMusic(R2D_Music *mus) { + if (!mus) return; + Mix_FreeMusic(mus->data); + free(mus); +} diff --git a/ext/ruby2d/ruby2d.c b/ext/ruby2d/ruby2d.c index bad3ae1..655a44a 100644 --- a/ext/ruby2d/ruby2d.c +++ b/ext/ruby2d/ruby2d.c @@ -1,10 +1,9 @@ // Native C extension for Ruby and MRuby -// Simple 2D includes +// Ruby 2D includes #if RUBY2D_IOS_TVOS - #include #else - #include + #include #endif // Ruby includes @@ -100,11 +99,11 @@ static mrb_state *mrb; #endif -// Ruby 2D window +// Ruby 2D interpreter window static R_VAL ruby2d_window; -// Simple 2D window -static S2D_Window *window; +// Ruby 2D native window +static R2D_Window *window; // Method signatures and structures for Ruby 2D classes @@ -130,19 +129,19 @@ static S2D_Window *window; "music", free_music }; #else - static void free_image(S2D_Image *img); - static void free_sprite(S2D_Sprite *spr); - static void free_text(S2D_Text *txt); - static void free_sound(S2D_Sound *snd); - static void free_music(S2D_Music *mus); + static void free_image(R2D_Image *img); + static void free_sprite(R2D_Sprite *spr); + static void free_text(R2D_Text *txt); + static void free_sound(R2D_Sound *snd); + static void free_music(R2D_Music *mus); #endif /* - * Function pointer to free the Simple 2D window + * Function pointer to free the Ruby 2D native window */ static void free_window() { - S2D_FreeWindow(window); + R2D_FreeWindow(window); } @@ -178,7 +177,7 @@ static R_VAL ruby2d_triangle_ext_render(R_VAL self) { R_VAL c2 = r_iv_get(self, "@c2"); R_VAL c3 = r_iv_get(self, "@c3"); - S2D_DrawTriangle( + R2D_DrawTriangle( NUM2DBL(r_iv_get(self, "@x1")), NUM2DBL(r_iv_get(self, "@y1")), NUM2DBL(r_iv_get(c1, "@r")), @@ -218,7 +217,7 @@ static R_VAL ruby2d_quad_ext_render(R_VAL self) { R_VAL c3 = r_iv_get(self, "@c3"); R_VAL c4 = r_iv_get(self, "@c4"); - S2D_DrawQuad( + R2D_DrawQuad( NUM2DBL(r_iv_get(self, "@x1")), NUM2DBL(r_iv_get(self, "@y1")), NUM2DBL(r_iv_get(c1, "@r")), @@ -265,7 +264,7 @@ static R_VAL ruby2d_line_ext_render(R_VAL self) { R_VAL c3 = r_iv_get(self, "@c3"); R_VAL c4 = r_iv_get(self, "@c4"); - S2D_DrawLine( + R2D_DrawLine( NUM2DBL(r_iv_get(self, "@x1")), NUM2DBL(r_iv_get(self, "@y1")), NUM2DBL(r_iv_get(self, "@x2")), @@ -307,7 +306,7 @@ static R_VAL ruby2d_circle_ext_render(R_VAL self) { #endif R_VAL c = r_iv_get(self, "@color"); - S2D_DrawCircle( + R2D_DrawCircle( NUM2DBL(r_iv_get(self, "@x")), NUM2DBL(r_iv_get(self, "@y")), NUM2DBL(r_iv_get(self, "@radius")), @@ -333,7 +332,7 @@ static R_VAL ruby2d_image_ext_init(mrb_state* mrb, R_VAL self) { #else static R_VAL ruby2d_image_ext_init(R_VAL self, R_VAL path) { #endif - S2D_Image *img = S2D_CreateImage(RSTRING_PTR(path)); + R2D_Image *img = R2D_CreateImage(RSTRING_PTR(path)); if (!img) return R_FALSE; // Get width and height from Ruby class. If set, use it, else choose the @@ -356,8 +355,8 @@ static R_VAL ruby2d_image_ext_render(mrb_state* mrb, R_VAL self) { #else static R_VAL ruby2d_image_ext_render(R_VAL self) { #endif - S2D_Image *img; - r_data_get_struct(self, "@data", &image_data_type, S2D_Image, img); + R2D_Image *img; + r_data_get_struct(self, "@data", &image_data_type, R2D_Image, img); img->x = NUM2DBL(r_iv_get(self, "@x")); img->y = NUM2DBL(r_iv_get(self, "@y")); @@ -367,7 +366,7 @@ static R_VAL ruby2d_image_ext_render(R_VAL self) { if (r_test(w)) img->width = NUM2INT(w); if (r_test(h)) img->height = NUM2INT(h); - S2D_RotateImage(img, NUM2DBL(r_iv_get(self, "@rotate")), S2D_CENTER); + R2D_RotateImage(img, NUM2DBL(r_iv_get(self, "@rotate")), R2D_CENTER); R_VAL c = r_iv_get(self, "@color"); img->color.r = NUM2DBL(r_iv_get(c, "@r")); @@ -375,7 +374,7 @@ static R_VAL ruby2d_image_ext_render(R_VAL self) { img->color.b = NUM2DBL(r_iv_get(c, "@b")); img->color.a = NUM2DBL(r_iv_get(c, "@a")); - S2D_DrawImage(img); + R2D_DrawImage(img); return R_NIL; } @@ -386,11 +385,11 @@ static R_VAL ruby2d_image_ext_render(R_VAL self) { */ #if MRUBY static void free_image(mrb_state *mrb, void *p_) { - S2D_Image *img = (S2D_Image *)p_; + R2D_Image *img = (R2D_Image *)p_; #else -static void free_image(S2D_Image *img) { +static void free_image(R2D_Image *img) { #endif - S2D_FreeImage(img); + R2D_FreeImage(img); } @@ -405,7 +404,7 @@ static R_VAL ruby2d_sprite_ext_init(mrb_state* mrb, R_VAL self) { #else static R_VAL ruby2d_sprite_ext_init(R_VAL self, R_VAL path) { #endif - S2D_Sprite *spr = S2D_CreateSprite(RSTRING_PTR(path)); + R2D_Sprite *spr = R2D_CreateSprite(RSTRING_PTR(path)); if (!spr) return R_FALSE; r_iv_set(self, "@img_width" , INT2NUM(spr->width)); @@ -426,8 +425,8 @@ static R_VAL ruby2d_sprite_ext_render(R_VAL self) { #endif r_funcall(self, "update", 0); - S2D_Sprite *spr; - r_data_get_struct(self, "@data", &sprite_data_type, S2D_Sprite, spr); + R2D_Sprite *spr; + r_data_get_struct(self, "@data", &sprite_data_type, R2D_Sprite, spr); spr->x = NUM2DBL(r_iv_get(self, "@flip_x")); spr->y = NUM2DBL(r_iv_get(self, "@flip_y")); @@ -438,7 +437,7 @@ static R_VAL ruby2d_sprite_ext_render(R_VAL self) { R_VAL h = r_iv_get(self, "@flip_height"); if (r_test(h)) spr->height = NUM2DBL(h); - S2D_RotateSprite(spr, NUM2DBL(r_iv_get(self, "@rotate")), S2D_CENTER); + R2D_RotateSprite(spr, NUM2DBL(r_iv_get(self, "@rotate")), R2D_CENTER); R_VAL c = r_iv_get(self, "@color"); spr->color.r = NUM2DBL(r_iv_get(c, "@r")); @@ -446,7 +445,7 @@ static R_VAL ruby2d_sprite_ext_render(R_VAL self) { spr->color.b = NUM2DBL(r_iv_get(c, "@b")); spr->color.a = NUM2DBL(r_iv_get(c, "@a")); - S2D_ClipSprite( + R2D_ClipSprite( spr, NUM2INT(r_iv_get(self, "@clip_x")), NUM2INT(r_iv_get(self, "@clip_y")), @@ -454,7 +453,7 @@ static R_VAL ruby2d_sprite_ext_render(R_VAL self) { NUM2INT(r_iv_get(self, "@clip_height")) ); - S2D_DrawSprite(spr); + R2D_DrawSprite(spr); return R_NIL; } @@ -465,11 +464,11 @@ static R_VAL ruby2d_sprite_ext_render(R_VAL self) { */ #if MRUBY static void free_sprite(mrb_state *mrb, void *p_) { - S2D_Sprite *spr = (S2D_Sprite *)p_; + R2D_Sprite *spr = (R2D_Sprite *)p_; #else -static void free_sprite(S2D_Sprite *spr) { +static void free_sprite(R2D_Sprite *spr) { #endif - S2D_FreeSprite(spr); + R2D_FreeSprite(spr); } @@ -488,7 +487,7 @@ static R_VAL ruby2d_text_ext_init(R_VAL self) { mrb_str_resize(mrb, s, RSTRING_LEN(s)); #endif - S2D_Text *txt = S2D_CreateText( + R2D_Text *txt = R2D_CreateText( RSTRING_PTR(r_iv_get(self, "@font")), RSTRING_PTR(r_iv_get(self, "@text")), NUM2DBL(r_iv_get(self, "@size")) @@ -513,10 +512,10 @@ static R_VAL ruby2d_text_ext_set(mrb_state* mrb, R_VAL self) { #else static R_VAL ruby2d_text_ext_set(R_VAL self, R_VAL text) { #endif - S2D_Text *txt; - r_data_get_struct(self, "@data", &text_data_type, S2D_Text, txt); + R2D_Text *txt; + r_data_get_struct(self, "@data", &text_data_type, R2D_Text, txt); - S2D_SetText(txt, RSTRING_PTR(text)); + R2D_SetText(txt, RSTRING_PTR(text)); r_iv_set(self, "@width", INT2NUM(txt->width)); r_iv_set(self, "@height", INT2NUM(txt->height)); @@ -533,13 +532,13 @@ static R_VAL ruby2d_text_ext_render(mrb_state* mrb, R_VAL self) { #else static R_VAL ruby2d_text_ext_render(R_VAL self) { #endif - S2D_Text *txt; - r_data_get_struct(self, "@data", &text_data_type, S2D_Text, txt); + R2D_Text *txt; + r_data_get_struct(self, "@data", &text_data_type, R2D_Text, txt); txt->x = NUM2DBL(r_iv_get(self, "@x")); txt->y = NUM2DBL(r_iv_get(self, "@y")); - S2D_RotateText(txt, NUM2DBL(r_iv_get(self, "@rotate")), S2D_CENTER); + R2D_RotateText(txt, NUM2DBL(r_iv_get(self, "@rotate")), R2D_CENTER); R_VAL c = r_iv_get(self, "@color"); txt->color.r = NUM2DBL(r_iv_get(c, "@r")); @@ -547,7 +546,7 @@ static R_VAL ruby2d_text_ext_render(R_VAL self) { txt->color.b = NUM2DBL(r_iv_get(c, "@b")); txt->color.a = NUM2DBL(r_iv_get(c, "@a")); - S2D_DrawText(txt); + R2D_DrawText(txt); return R_NIL; } @@ -558,11 +557,11 @@ static R_VAL ruby2d_text_ext_render(R_VAL self) { */ #if MRUBY static void free_text(mrb_state *mrb, void *p_) { - S2D_Text *txt = (S2D_Text *)p_; + R2D_Text *txt = (R2D_Text *)p_; #else -static void free_text(S2D_Text *txt) { +static void free_text(R2D_Text *txt) { #endif - S2D_FreeText(txt); + R2D_FreeText(txt); } @@ -577,7 +576,7 @@ static R_VAL ruby2d_sound_ext_init(mrb_state* mrb, R_VAL self) { #else static R_VAL ruby2d_sound_ext_init(R_VAL self, R_VAL path) { #endif - S2D_Sound *snd = S2D_CreateSound(RSTRING_PTR(path)); + R2D_Sound *snd = R2D_CreateSound(RSTRING_PTR(path)); if (!snd) return R_FALSE; r_iv_set(self, "@data", r_data_wrap_struct(sound, snd)); return R_TRUE; @@ -592,9 +591,9 @@ static R_VAL ruby2d_sound_ext_play(mrb_state* mrb, R_VAL self) { #else static R_VAL ruby2d_sound_ext_play(R_VAL self) { #endif - S2D_Sound *snd; - r_data_get_struct(self, "@data", &sound_data_type, S2D_Sound, snd); - S2D_PlaySound(snd); + R2D_Sound *snd; + r_data_get_struct(self, "@data", &sound_data_type, R2D_Sound, snd); + R2D_PlaySound(snd); return R_NIL; } @@ -604,11 +603,11 @@ static R_VAL ruby2d_sound_ext_play(R_VAL self) { */ #if MRUBY static void free_sound(mrb_state *mrb, void *p_) { - S2D_Sound *snd = (S2D_Sound *)p_; + R2D_Sound *snd = (R2D_Sound *)p_; #else -static void free_sound(S2D_Sound *snd) { +static void free_sound(R2D_Sound *snd) { #endif - S2D_FreeSound(snd); + R2D_FreeSound(snd); } @@ -623,7 +622,7 @@ static R_VAL ruby2d_music_ext_init(mrb_state* mrb, R_VAL self) { #else static R_VAL ruby2d_music_ext_init(R_VAL self, R_VAL path) { #endif - S2D_Music *mus = S2D_CreateMusic(RSTRING_PTR(path)); + R2D_Music *mus = R2D_CreateMusic(RSTRING_PTR(path)); if (!mus) return R_FALSE; r_iv_set(self, "@data", r_data_wrap_struct(music, mus)); return R_TRUE; @@ -638,9 +637,9 @@ static R_VAL ruby2d_music_ext_play(mrb_state* mrb, R_VAL self) { #else static R_VAL ruby2d_music_ext_play(R_VAL self) { #endif - S2D_Music *mus; - r_data_get_struct(self, "@data", &music_data_type, S2D_Music, mus); - S2D_PlayMusic(mus, r_test(r_iv_get(self, "@loop"))); + R2D_Music *mus; + r_data_get_struct(self, "@data", &music_data_type, R2D_Music, mus); + R2D_PlayMusic(mus, r_test(r_iv_get(self, "@loop"))); return R_NIL; } @@ -653,7 +652,7 @@ static R_VAL ruby2d_music_ext_pause(mrb_state* mrb, R_VAL self) { #else static R_VAL ruby2d_music_ext_pause(R_VAL self) { #endif - S2D_PauseMusic(); + R2D_PauseMusic(); return R_NIL; } @@ -666,7 +665,7 @@ static R_VAL ruby2d_music_ext_resume(mrb_state* mrb, R_VAL self) { #else static R_VAL ruby2d_music_ext_resume(R_VAL self) { #endif - S2D_ResumeMusic(); + R2D_ResumeMusic(); return R_NIL; } @@ -679,7 +678,7 @@ static R_VAL ruby2d_music_ext_stop(mrb_state* mrb, R_VAL self) { #else static R_VAL ruby2d_music_ext_stop(R_VAL self) { #endif - S2D_StopMusic(); + R2D_StopMusic(); return R_NIL; } @@ -692,7 +691,7 @@ static R_VAL ruby2d_music_ext_get_volume(mrb_state* mrb, R_VAL self) { #else static R_VAL ruby2d_music_ext_get_volume(R_VAL self) { #endif - return INT2NUM(S2D_GetMusicVolume()); + return INT2NUM(R2D_GetMusicVolume()); } @@ -706,7 +705,7 @@ static R_VAL ruby2d_music_ext_set_volume(mrb_state* mrb, R_VAL self) { #else static R_VAL ruby2d_music_ext_set_volume(R_VAL self, R_VAL volume) { #endif - S2D_SetMusicVolume(NUM2INT(volume)); + R2D_SetMusicVolume(NUM2INT(volume)); return R_NIL; } @@ -721,7 +720,7 @@ static R_VAL ruby2d_music_ext_fadeout(mrb_state* mrb, R_VAL self) { #else static R_VAL ruby2d_music_ext_fadeout(R_VAL self, R_VAL ms) { #endif - S2D_FadeOutMusic(NUM2INT(ms)); + R2D_FadeOutMusic(NUM2INT(ms)); return R_NIL; } @@ -731,29 +730,29 @@ static R_VAL ruby2d_music_ext_fadeout(R_VAL self, R_VAL ms) { */ #if MRUBY static void free_music(mrb_state *mrb, void *p_) { - S2D_Music *mus = (S2D_Music *)p_; + R2D_Music *mus = (R2D_Music *)p_; #else -static void free_music(S2D_Music *mus) { +static void free_music(R2D_Music *mus) { #endif - S2D_FreeMusic(mus); + R2D_FreeMusic(mus); } /* - * Simple 2D `on_key` input callback function + * Ruby 2D native `on_key` input callback function */ -static void on_key(S2D_Event e) { +static void on_key(R2D_Event e) { R_VAL type; switch (e.type) { - case S2D_KEY_DOWN: + case R2D_KEY_DOWN: type = r_char_to_sym("down"); break; - case S2D_KEY_HELD: + case R2D_KEY_HELD: type = r_char_to_sym("held"); break; - case S2D_KEY_UP: + case R2D_KEY_UP: type = r_char_to_sym("up"); break; } @@ -763,48 +762,48 @@ static void on_key(S2D_Event e) { /* - * Simple 2D `on_mouse` input callback function + * Ruby 2D native `on_mouse` input callback function */ -void on_mouse(S2D_Event e) { +void on_mouse(R2D_Event e) { R_VAL type = R_NIL; R_VAL button = R_NIL; R_VAL direction = R_NIL; switch (e.type) { - case S2D_MOUSE_DOWN: + case R2D_MOUSE_DOWN: // type, button, x, y type = r_char_to_sym("down"); break; - case S2D_MOUSE_UP: + case R2D_MOUSE_UP: // type, button, x, y type = r_char_to_sym("up"); break; - case S2D_MOUSE_SCROLL: + case R2D_MOUSE_SCROLL: // type, direction, delta_x, delta_y type = r_char_to_sym("scroll"); - direction = e.direction == S2D_MOUSE_SCROLL_NORMAL ? + direction = e.direction == R2D_MOUSE_SCROLL_NORMAL ? r_char_to_sym("normal") : r_char_to_sym("inverted"); break; - case S2D_MOUSE_MOVE: + case R2D_MOUSE_MOVE: // type, x, y, delta_x, delta_y type = r_char_to_sym("move"); break; } - if (e.type == S2D_MOUSE_DOWN || e.type == S2D_MOUSE_UP) { + if (e.type == R2D_MOUSE_DOWN || e.type == R2D_MOUSE_UP) { switch (e.button) { - case S2D_MOUSE_LEFT: + case R2D_MOUSE_LEFT: button = r_char_to_sym("left"); break; - case S2D_MOUSE_MIDDLE: + case R2D_MOUSE_MIDDLE: button = r_char_to_sym("middle"); break; - case S2D_MOUSE_RIGHT: + case R2D_MOUSE_RIGHT: button = r_char_to_sym("right"); break; - case S2D_MOUSE_X1: + case R2D_MOUSE_X1: button = r_char_to_sym("x1"); break; - case S2D_MOUSE_X2: + case R2D_MOUSE_X2: button = r_char_to_sym("x2"); break; } @@ -818,88 +817,88 @@ void on_mouse(S2D_Event e) { /* - * Simple 2D `on_controller` input callback function + * Ruby 2D native `on_controller` input callback function */ -static void on_controller(S2D_Event e) { +static void on_controller(R2D_Event e) { R_VAL type = R_NIL; R_VAL axis = R_NIL; R_VAL button = R_NIL; switch (e.type) { - case S2D_AXIS: + case R2D_AXIS: type = r_char_to_sym("axis"); switch (e.axis) { - case S2D_AXIS_LEFTX: + case R2D_AXIS_LEFTX: axis = r_char_to_sym("left_x"); break; - case S2D_AXIS_LEFTY: + case R2D_AXIS_LEFTY: axis = r_char_to_sym("left_y"); break; - case S2D_AXIS_RIGHTX: + case R2D_AXIS_RIGHTX: axis = r_char_to_sym("right_x"); break; - case S2D_AXIS_RIGHTY: + case R2D_AXIS_RIGHTY: axis = r_char_to_sym("right_y"); break; - case S2D_AXIS_TRIGGERLEFT: + case R2D_AXIS_TRIGGERLEFT: axis = r_char_to_sym("trigger_left"); break; - case S2D_AXIS_TRIGGERRIGHT: + case R2D_AXIS_TRIGGERRIGHT: axis = r_char_to_sym("trigger_right"); break; - case S2D_AXIS_INVALID: + case R2D_AXIS_INVALID: axis = r_char_to_sym("invalid"); break; } break; - case S2D_BUTTON_DOWN: case S2D_BUTTON_UP: - type = e.type == S2D_BUTTON_DOWN ? r_char_to_sym("button_down") : r_char_to_sym("button_up"); + case R2D_BUTTON_DOWN: case R2D_BUTTON_UP: + type = e.type == R2D_BUTTON_DOWN ? r_char_to_sym("button_down") : r_char_to_sym("button_up"); switch (e.button) { - case S2D_BUTTON_A: + case R2D_BUTTON_A: button = r_char_to_sym("a"); break; - case S2D_BUTTON_B: + case R2D_BUTTON_B: button = r_char_to_sym("b"); break; - case S2D_BUTTON_X: + case R2D_BUTTON_X: button = r_char_to_sym("x"); break; - case S2D_BUTTON_Y: + case R2D_BUTTON_Y: button = r_char_to_sym("y"); break; - case S2D_BUTTON_BACK: + case R2D_BUTTON_BACK: button = r_char_to_sym("back"); break; - case S2D_BUTTON_GUIDE: + case R2D_BUTTON_GUIDE: button = r_char_to_sym("guide"); break; - case S2D_BUTTON_START: + case R2D_BUTTON_START: button = r_char_to_sym("start"); break; - case S2D_BUTTON_LEFTSTICK: + case R2D_BUTTON_LEFTSTICK: button = r_char_to_sym("left_stick"); break; - case S2D_BUTTON_RIGHTSTICK: + case R2D_BUTTON_RIGHTSTICK: button = r_char_to_sym("right_stick"); break; - case S2D_BUTTON_LEFTSHOULDER: + case R2D_BUTTON_LEFTSHOULDER: button = r_char_to_sym("left_shoulder"); break; - case S2D_BUTTON_RIGHTSHOULDER: + case R2D_BUTTON_RIGHTSHOULDER: button = r_char_to_sym("right_shoulder"); break; - case S2D_BUTTON_DPAD_UP: + case R2D_BUTTON_DPAD_UP: button = r_char_to_sym("up"); break; - case S2D_BUTTON_DPAD_DOWN: + case R2D_BUTTON_DPAD_DOWN: button = r_char_to_sym("down"); break; - case S2D_BUTTON_DPAD_LEFT: + case R2D_BUTTON_DPAD_LEFT: button = r_char_to_sym("left"); break; - case S2D_BUTTON_DPAD_RIGHT: + case R2D_BUTTON_DPAD_RIGHT: button = r_char_to_sym("right"); break; - case S2D_BUTTON_INVALID: + case R2D_BUTTON_INVALID: button = r_char_to_sym("invalid"); break; } @@ -914,7 +913,7 @@ static void on_controller(S2D_Event e) { /* - * Simple 2D `update` callback function + * Ruby 2D native `update` callback function */ static void update() { @@ -934,7 +933,7 @@ static void update() { /* - * Simple 2D `render` callback function + * Ruby 2D native `render` callback function */ static void render() { @@ -967,8 +966,8 @@ static R_VAL ruby2d_ext_diagnostics(mrb_state* mrb, R_VAL self) { #else static R_VAL ruby2d_ext_diagnostics(R_VAL self, R_VAL enable) { #endif - // Set Simple 2D diagnostics - S2D_Diagnostics(r_test(enable)); + // Set Ruby 2D native diagnostics + R2D_Diagnostics(r_test(enable)); return R_TRUE; } @@ -982,7 +981,7 @@ static R_VAL ruby2d_window_ext_get_display_dimensions(mrb_state* mrb, R_VAL self static R_VAL ruby2d_window_ext_get_display_dimensions(R_VAL self) { #endif int w; int h; - S2D_GetDisplayDimensions(&w, &h); + R2D_GetDisplayDimensions(&w, &h); r_iv_set(self, "@display_width" , INT2NUM(w)); r_iv_set(self, "@display_height", INT2NUM(h)); return R_NIL; @@ -999,8 +998,8 @@ static R_VAL ruby2d_window_ext_add_controller_mappings(mrb_state* mrb, R_VAL sel #else static R_VAL ruby2d_window_ext_add_controller_mappings(R_VAL self, R_VAL path) { #endif - S2D_Log(S2D_INFO, "Adding controller mappings from `%s`", RSTRING_PTR(path)); - S2D_AddControllerMappingsFromFile(RSTRING_PTR(path)); + R2D_Log(R2D_INFO, "Adding controller mappings from `%s`", RSTRING_PTR(path)); + R2D_AddControllerMappingsFromFile(RSTRING_PTR(path)); return R_NIL; } @@ -1032,16 +1031,16 @@ static R_VAL ruby2d_window_ext_show(R_VAL self) { // Get window flags int flags = 0; if (r_test(r_iv_get(self, "@resizable"))) { - flags = flags | S2D_RESIZABLE; + flags = flags | R2D_RESIZABLE; } if (r_test(r_iv_get(self, "@borderless"))) { - flags = flags | S2D_BORDERLESS; + flags = flags | R2D_BORDERLESS; } if (r_test(r_iv_get(self, "@fullscreen"))) { - flags = flags | S2D_FULLSCREEN; + flags = flags | R2D_FULLSCREEN; } if (r_test(r_iv_get(self, "@highdpi"))) { - flags = flags | S2D_HIGHDPI; + flags = flags | R2D_HIGHDPI; } // Check viewport size and set @@ -1054,7 +1053,7 @@ static R_VAL ruby2d_window_ext_show(R_VAL self) { // Create and show window - window = S2D_CreateWindow( + window = R2D_CreateWindow( title, width, height, update, render, flags ); @@ -1066,7 +1065,7 @@ static R_VAL ruby2d_window_ext_show(R_VAL self) { window->on_mouse = on_mouse; window->on_controller = on_controller; - S2D_Show(window); + R2D_Show(window); atexit(free_window); return R_NIL; @@ -1084,7 +1083,7 @@ static R_VAL ruby2d_ext_screenshot(mrb_state* mrb, R_VAL self) { static R_VAL ruby2d_ext_screenshot(R_VAL self, R_VAL path) { #endif if (window) { - S2D_Screenshot(window, RSTRING_PTR(path)); + R2D_Screenshot(window, RSTRING_PTR(path)); return path; } else { return R_FALSE; @@ -1096,7 +1095,7 @@ static R_VAL ruby2d_ext_screenshot(R_VAL self, R_VAL path) { * Ruby2D::Window#ext_close */ static R_VAL ruby2d_window_ext_close() { - S2D_Close(window); + R2D_Close(window); return R_NIL; } diff --git a/ext/ruby2d/ruby2d.h b/ext/ruby2d/ruby2d.h new file mode 100644 index 0000000..e6d21d7 --- /dev/null +++ b/ext/ruby2d/ruby2d.h @@ -0,0 +1,757 @@ +// ruby2d.h + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +// Set Platform Constants ////////////////////////////////////////////////////// + +// Apple +#ifdef __APPLE__ + #ifndef __TARGETCONDITIONALS__ + #include "TargetConditionals.h" + #endif + #if TARGET_OS_OSX + #define MACOS true + #elif TARGET_OS_IOS + #define IOS true + #elif TARGET_OS_TV + #define TVOS true + #endif +#endif + +// Windows +#ifdef _WIN32 + #define WINDOWS true +#endif + +// Windows and MinGW +#ifdef __MINGW32__ + #define MINGW true +#endif + +// GLES +#if defined(__arm__) || IOS || TVOS + #define GLES true +#else + #define GLES false +#endif + +// Includes //////////////////////////////////////////////////////////////////// + +// Define to get GNU extension functions and types, like `vasprintf()` and M_PI +#ifndef _GNU_SOURCE +#define _GNU_SOURCE 1 +#endif + +#if WINDOWS && !MINGW + #include + #define F_OK 0 // For testing file existence +#else + #include +#endif + +#if WINDOWS + #include + #include + #include + // For terminal colors + #ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING + #define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004 + #endif +#endif + +// SDL +#if IOS || TVOS + #include "SDL2/SDL.h" +#else + #include +#endif + +// If MinGW, undefine `main()` from SDL_main.c +#if MINGW + #undef main +#endif + +// OpenGL +#if GLES + #if IOS || TVOS + #include "SDL2/SDL_opengles2.h" + #else + #include + #endif +#else + #define GL_GLEXT_PROTOTYPES 1 + #if WINDOWS + #include + #endif + #include +#endif + +// SDL libraries +#if IOS || TVOS + #include "SDL2/SDL_image.h" + #include "SDL2/SDL_mixer.h" + #include "SDL2/SDL_ttf.h" +#else + #include + #include + #include +#endif + +// Ruby 2D Definitions ///////////////////////////////////////////////////////// + +// Messages +#define R2D_INFO 1 +#define R2D_WARN 2 +#define R2D_ERROR 3 + +// Window attributes +#define R2D_RESIZABLE SDL_WINDOW_RESIZABLE +#define R2D_BORDERLESS SDL_WINDOW_BORDERLESS +#define R2D_FULLSCREEN SDL_WINDOW_FULLSCREEN_DESKTOP +#define R2D_HIGHDPI SDL_WINDOW_ALLOW_HIGHDPI +#define R2D_DISPLAY_WIDTH 0 +#define R2D_DISPLAY_HEIGHT 0 + +// Viewport scaling modes +#define R2D_FIXED 1 +#define R2D_EXPAND 2 +#define R2D_SCALE 3 +#define R2D_STRETCH 4 + +// Positions +#define R2D_CENTER 1 +#define R2D_TOP_LEFT 2 +#define R2D_TOP_RIGHT 3 +#define R2D_BOTTOM_LEFT 4 +#define R2D_BOTTOM_RIGHT 5 + +// Keyboard events +#define R2D_KEY_DOWN 1 // key is pressed +#define R2D_KEY_HELD 2 // key is held down +#define R2D_KEY_UP 3 // key is released + +// Mouse events +#define R2D_MOUSE_DOWN 1 // mouse button pressed +#define R2D_MOUSE_UP 2 // mouse button released +#define R2D_MOUSE_SCROLL 3 // mouse scrolling or wheel movement +#define R2D_MOUSE_MOVE 4 // mouse movement +#define R2D_MOUSE_LEFT SDL_BUTTON_LEFT +#define R2D_MOUSE_MIDDLE SDL_BUTTON_MIDDLE +#define R2D_MOUSE_RIGHT SDL_BUTTON_RIGHT +#define R2D_MOUSE_X1 SDL_BUTTON_X1 +#define R2D_MOUSE_X2 SDL_BUTTON_X2 +#define R2D_MOUSE_SCROLL_NORMAL SDL_MOUSEWHEEL_NORMAL +#define R2D_MOUSE_SCROLL_INVERTED SDL_MOUSEWHEEL_FLIPPED + +// Controller events +#define R2D_AXIS 1 +#define R2D_BUTTON_DOWN 2 +#define R2D_BUTTON_UP 3 + +// Controller axis labels +#define R2D_AXIS_INVALID SDL_CONTROLLER_AXIS_INVALID +#define R2D_AXIS_LEFTX SDL_CONTROLLER_AXIS_LEFTX +#define R2D_AXIS_LEFTY SDL_CONTROLLER_AXIS_LEFTY +#define R2D_AXIS_RIGHTX SDL_CONTROLLER_AXIS_RIGHTX +#define R2D_AXIS_RIGHTY SDL_CONTROLLER_AXIS_RIGHTY +#define R2D_AXIS_TRIGGERLEFT SDL_CONTROLLER_AXIS_TRIGGERLEFT +#define R2D_AXIS_TRIGGERRIGHT SDL_CONTROLLER_AXIS_TRIGGERRIGHT +#define R2D_AXIS_MAX SDL_CONTROLLER_AXIS_MAX + +// Controller button labels +#define R2D_BUTTON_INVALID SDL_CONTROLLER_BUTTON_INVALID +#define R2D_BUTTON_A SDL_CONTROLLER_BUTTON_A +#define R2D_BUTTON_B SDL_CONTROLLER_BUTTON_B +#define R2D_BUTTON_X SDL_CONTROLLER_BUTTON_X +#define R2D_BUTTON_Y SDL_CONTROLLER_BUTTON_Y +#define R2D_BUTTON_BACK SDL_CONTROLLER_BUTTON_BACK +#define R2D_BUTTON_GUIDE SDL_CONTROLLER_BUTTON_GUIDE +#define R2D_BUTTON_START SDL_CONTROLLER_BUTTON_START +#define R2D_BUTTON_LEFTSTICK SDL_CONTROLLER_BUTTON_LEFTSTICK +#define R2D_BUTTON_RIGHTSTICK SDL_CONTROLLER_BUTTON_RIGHTSTICK +#define R2D_BUTTON_LEFTSHOULDER SDL_CONTROLLER_BUTTON_LEFTSHOULDER +#define R2D_BUTTON_RIGHTSHOULDER SDL_CONTROLLER_BUTTON_RIGHTSHOULDER +#define R2D_BUTTON_DPAD_UP SDL_CONTROLLER_BUTTON_DPAD_UP +#define R2D_BUTTON_DPAD_DOWN SDL_CONTROLLER_BUTTON_DPAD_DOWN +#define R2D_BUTTON_DPAD_LEFT SDL_CONTROLLER_BUTTON_DPAD_LEFT +#define R2D_BUTTON_DPAD_RIGHT SDL_CONTROLLER_BUTTON_DPAD_RIGHT +#define R2D_BUTTON_MAX SDL_CONTROLLER_BUTTON_MAX + +// Internal Shared Data //////////////////////////////////////////////////////// + +extern bool R2D_diagnostics; // flag for whether to print diagnostics with R2D_Log + +// Type Definitions //////////////////////////////////////////////////////////// + +// R2D_Event +typedef struct { + int which; + int type; + int button; + bool dblclick; + const char *key; + int x; + int y; + int delta_x; + int delta_y; + int direction; + int axis; + int value; +} R2D_Event; + +typedef void (*R2D_Update)(); +typedef void (*R2D_Render)(); +typedef void (*R2D_On_Key)(R2D_Event e); +typedef void (*R2D_On_Mouse)(R2D_Event e); +typedef void (*R2D_On_Controller)(R2D_Event e); + +// R2D_GL_Point, for graphics calculations +typedef struct { + GLfloat x; + GLfloat y; +} R2D_GL_Point; + +// R2D_Color +typedef struct { + GLfloat r; + GLfloat g; + GLfloat b; + GLfloat a; +} R2D_Color; + +// R2D_Mouse +typedef struct { + int visible; + int x; + int y; +} R2D_Mouse; + +// R2D_Viewport +typedef struct { + int width; + int height; + int mode; +} R2D_Viewport; + +// R2D_Window +typedef struct { + SDL_Window *sdl; + SDL_GLContext glcontext; + const GLubyte *R2D_GL_VENDOR; + const GLubyte *R2D_GL_RENDERER; + const GLubyte *R2D_GL_VERSION; + GLint R2D_GL_MAJOR_VERSION; + GLint R2D_GL_MINOR_VERSION; + const GLubyte *R2D_GL_SHADING_LANGUAGE_VERSION; + const char *title; + int width; + int height; + int orig_width; + int orig_height; + R2D_Viewport viewport; + R2D_Update update; + R2D_Render render; + int flags; + R2D_Mouse mouse; + R2D_On_Key on_key; + R2D_On_Mouse on_mouse; + R2D_On_Controller on_controller; + bool vsync; + int fps_cap; + R2D_Color background; + const char *icon; + Uint32 frames; + Uint32 elapsed_ms; + Uint32 loop_ms; + Uint32 delay_ms; + double fps; + bool close; +} R2D_Window; + +// R2D_Image +typedef struct { + const char *path; + SDL_Surface *surface; + int format; + GLuint texture_id; + R2D_Color color; + int x; + int y; + int width; + int height; + int orig_width; + int orig_height; + GLfloat rotate; // Rotation angle in degrees + GLfloat rx; // X coordinate to be rotated around + GLfloat ry; // Y coordinate to be rotated around +} R2D_Image; + +// R2D_Sprite +typedef struct { + const char *path; + R2D_Image *img; + R2D_Color color; + int x; + int y; + int width; + int height; + int clip_width; + int clip_height; + GLfloat rotate; // Rotation angle in degrees + GLfloat rx; // X coordinate to be rotated around + GLfloat ry; // Y coordinate to be rotated around + GLfloat tx1; + GLfloat ty1; + GLfloat tx2; + GLfloat ty2; + GLfloat tx3; + GLfloat ty3; + GLfloat tx4; + GLfloat ty4; +} R2D_Sprite; + +// R2D_Text +typedef struct { + const char *font; + SDL_Surface *surface; + GLuint texture_id; + TTF_Font *font_data; + R2D_Color color; + char *msg; + int x; + int y; + int width; + int height; + GLfloat rotate; // Rotation angle in degrees + GLfloat rx; // X coordinate to be rotated around + GLfloat ry; // Y coordinate to be rotated around +} R2D_Text; + +// R2D_Sound +typedef struct { + const char *path; + Mix_Chunk *data; +} R2D_Sound; + +// R2D_Music +typedef struct { + const char *path; + Mix_Music *data; +} R2D_Music; + +// Ruby 2D Functions /////////////////////////////////////////////////////////// + +/* + * Checks if a file exists and can be accessed + */ +bool R2D_FileExists(const char *path); + +/* + * Logs standard messages to the console + */ +void R2D_Log(int type, const char *msg, ...); + +/* + * Logs Ruby 2D errors to the console, with caller and message body + */ +void R2D_Error(const char *caller, const char *msg, ...); + +/* + * Enable/disable logging of diagnostics + */ +void R2D_Diagnostics(bool status); + +/* + * Enable terminal colors in Windows + */ +void R2D_Windows_EnableTerminalColors(); + +/* +* Initialize Ruby 2D subsystems +*/ +bool R2D_Init(); + +/* + * Gets the primary display's dimensions + */ +void R2D_GetDisplayDimensions(int *w, int *h); + +/* + * Quits Ruby 2D subsystems + */ +void R2D_Quit(void); + +// Shapes ////////////////////////////////////////////////////////////////////// + +/* + * Rotate a point around a given point + * Params: + * p The point to rotate + * angle The angle in degrees + * rx The x coordinate to rotate around + * ry The y coordinate to rotate around + */ +R2D_GL_Point R2D_RotatePoint(R2D_GL_Point p, GLfloat angle, GLfloat rx, GLfloat ry); + +/* + * Get the point to be rotated around given a position in a rectangle + */ +R2D_GL_Point R2D_GetRectRotationPoint(int x, int y, int w, int h, int position); + +/* + * Draw a triangle + */ +void R2D_DrawTriangle( + GLfloat x1, GLfloat y1, + GLfloat r1, GLfloat g1, GLfloat b1, GLfloat a1, + GLfloat x2, GLfloat y2, + GLfloat r2, GLfloat g2, GLfloat b2, GLfloat a2, + GLfloat x3, GLfloat y3, + GLfloat r3, GLfloat g3, GLfloat b3, GLfloat a3 +); + +/* + * Draw a quad, using two triangles + */ +void R2D_DrawQuad( + GLfloat x1, GLfloat y1, + GLfloat r1, GLfloat g1, GLfloat b1, GLfloat a1, + GLfloat x2, GLfloat y2, + GLfloat r2, GLfloat g2, GLfloat b2, GLfloat a2, + GLfloat x3, GLfloat y3, + GLfloat r3, GLfloat g3, GLfloat b3, GLfloat a3, + GLfloat x4, GLfloat y4, + GLfloat r4, GLfloat g4, GLfloat b4, GLfloat a4 +); + +/* + * Draw a line from a quad + */ +void R2D_DrawLine( + GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2, + GLfloat width, + GLfloat r1, GLfloat g1, GLfloat b1, GLfloat a1, + GLfloat r2, GLfloat g2, GLfloat b2, GLfloat a2, + GLfloat r3, GLfloat g3, GLfloat b3, GLfloat a3, + GLfloat r4, GLfloat g4, GLfloat b4, GLfloat a4 +); + +/* + * Draw a circle from triangles + */ +void R2D_DrawCircle( + GLfloat x, GLfloat y, GLfloat radius, int sectors, + GLfloat r, GLfloat g, GLfloat b, GLfloat a +); + +// Image /////////////////////////////////////////////////////////////////////// + +/* + * Create an image, given a file path + */ +R2D_Image *R2D_CreateImage(const char *path); + +/* + * Rotate an image + */ +void R2D_RotateImage(R2D_Image *img, GLfloat angle, int position); + +/* + * Draw an image + */ +void R2D_DrawImage(R2D_Image *img); + +/* + * Free an image + */ +void R2D_FreeImage(R2D_Image *img); + +// Sprite ////////////////////////////////////////////////////////////////////// + +/* + * Create a sprite, given an image file path + */ +R2D_Sprite *R2D_CreateSprite(const char *path); + +/* + * Clip a sprite + */ +void R2D_ClipSprite(R2D_Sprite *spr, int x, int y, int w, int h); + +/* + * Rotate a sprite + */ +void R2D_RotateSprite(R2D_Sprite *spr, GLfloat angle, int position); + +/* + * Draw a sprite + */ +void R2D_DrawSprite(R2D_Sprite *spr); + +/* + * Free a sprite + */ +void R2D_FreeSprite(R2D_Sprite *spr); + +// Text //////////////////////////////////////////////////////////////////////// + +/* + * Create text, given a font file path, the message, and size + */ +R2D_Text *R2D_CreateText(const char *font, const char *msg, int size); + +/* +* Set the text message +*/ +void R2D_SetText(R2D_Text *txt, const char *msg, ...); + +/* + * Rotate text + */ +void R2D_RotateText(R2D_Text *txt, GLfloat angle, int position); + +/* + * Draw text + */ +void R2D_DrawText(R2D_Text *txt); + +/* + * Free the text + */ +void R2D_FreeText(R2D_Text *txt); + +// Sound /////////////////////////////////////////////////////////////////////// + +/* + * Create a sound, given an audio file path + */ +R2D_Sound *R2D_CreateSound(const char *path); + +/* + * Play the sound + */ +void R2D_PlaySound(R2D_Sound *snd); + +/* + * Get the sound's volume + */ +int R2D_GetSoundVolume(R2D_Sound *snd); + +/* + * Set the sound's volume a given percentage + */ +void R2D_SetSoundVolume(R2D_Sound *snd, int volume); + +/* + * Get the sound mixer volume + */ +int R2D_GetSoundMixVolume(); + +/* + * Set the sound mixer volume a given percentage + */ +void R2D_SetSoundMixVolume(int volume); + +/* + * Free the sound + */ +void R2D_FreeSound(R2D_Sound *snd); + +// Music /////////////////////////////////////////////////////////////////////// + +/* + * Create the music, given an audio file path + */ +R2D_Music *R2D_CreateMusic(const char *path); + +/* + * Play the music + */ +void R2D_PlayMusic(R2D_Music *mus, bool loop); + +/* + * Pause the playing music + */ +void R2D_PauseMusic(); + +/* + * Resume the current music + */ +void R2D_ResumeMusic(); + +/* + * Stop the playing music; interrupts fader effects + */ +void R2D_StopMusic(); + +/* + * Get the music volume + */ +int R2D_GetMusicVolume(); + +/* + * Set the music volume a given percentage + */ +void R2D_SetMusicVolume(int volume); + +/* + * Fade out the playing music + */ +void R2D_FadeOutMusic(int ms); + +/* + * Free the music + */ +void R2D_FreeMusic(R2D_Music *mus); + +// Input /////////////////////////////////////////////////////////////////////// + +/* + * Get the mouse coordinates relative to the viewport + */ +void R2D_GetMouseOnViewport(R2D_Window *window, int wx, int wy, int *x, int *y); + +/* + * Show the cursor over the window + */ +void R2D_ShowCursor(); + +/* + * Hide the cursor over the window + */ +void R2D_HideCursor(); + +// Controllers ///////////////////////////////////////////////////////////////// + +/* + * Add controller mapping from string + */ +void R2D_AddControllerMapping(const char *map); + +/* + * Load controller mappings from the specified file + */ +void R2D_AddControllerMappingsFromFile(const char *path); + +/* + * Check if joystick is a controller + */ +bool R2D_IsController(SDL_JoystickID id); + +/* + * Open controllers and joysticks + */ +void R2D_OpenControllers(); + +// Window ////////////////////////////////////////////////////////////////////// + +/* + * Create a window + */ +R2D_Window *R2D_CreateWindow( + const char *title, int width, int height, R2D_Update, R2D_Render, int flags +); + +/* + * Show the window + */ +int R2D_Show(R2D_Window *window); + +/* + * Set the icon for the window + */ +void R2D_SetIcon(R2D_Window *window, const char *icon); + +/* + * Take a screenshot of the window + */ +void R2D_Screenshot(R2D_Window *window, const char *path); + +/* + * Close the window + */ +int R2D_Close(R2D_Window *window); + +/* + * Free all resources + */ +int R2D_FreeWindow(R2D_Window *window); + +// Ruby 2D OpenGL Functions //////////////////////////////////////////////////// + +int R2D_GL_Init(R2D_Window *window); +void R2D_GL_PrintError(char *error); +void R2D_GL_PrintContextInfo(R2D_Window *window); +void R2D_GL_StoreContextInfo(R2D_Window *window); +GLuint R2D_GL_LoadShader(GLenum type, const GLchar *shaderSrc, char *shaderName); +int R2D_GL_CheckLinked(GLuint program, char *name); +void R2D_GL_GetViewportScale(R2D_Window *window, int *w, int *h, double *scale); +void R2D_GL_SetViewport(R2D_Window *window); +void R2D_GL_CreateTexture( + GLuint *id, GLint format, + int w, int h, + const GLvoid *data, GLint filter); +void R2D_GL_DrawTriangle( + GLfloat x1, GLfloat y1, + GLfloat r1, GLfloat g1, GLfloat b1, GLfloat a1, + GLfloat x2, GLfloat y2, + GLfloat r2, GLfloat g2, GLfloat b2, GLfloat a2, + GLfloat x3, GLfloat y3, + GLfloat r3, GLfloat g3, GLfloat b3, GLfloat a3); +void R2D_GL_DrawImage(R2D_Image *img); +void R2D_GL_DrawSprite(R2D_Sprite *spr); +void R2D_GL_DrawText(R2D_Text *txt); +void R2D_GL_FreeTexture(GLuint *id); +void R2D_GL_Clear(R2D_Color clr); +void R2D_GL_FlushBuffers(); + +// OpenGL & GLES Internal Functions //////////////////////////////////////////// + +#if GLES + int R2D_GLES_Init(); + void R2D_GLES_ApplyProjection(GLfloat orthoMatrix[16]); + void R2D_GLES_DrawTriangle( + GLfloat x1, GLfloat y1, + GLfloat r1, GLfloat g1, GLfloat b1, GLfloat a1, + GLfloat x2, GLfloat y2, + GLfloat r2, GLfloat g2, GLfloat b2, GLfloat a2, + GLfloat x3, GLfloat y3, + GLfloat r3, GLfloat g3, GLfloat b3, GLfloat a3); + void R2D_GLES_DrawImage(R2D_Image *img); + void R2D_GLES_DrawSprite(R2D_Sprite *spr); + void R2D_GLES_DrawText(R2D_Text *txt); +#else + int R2D_GL2_Init(); + int R2D_GL3_Init(); + void R2D_GL2_ApplyProjection(int w, int h); + void R2D_GL3_ApplyProjection(GLfloat orthoMatrix[16]); + void R2D_GL2_DrawTriangle( + GLfloat x1, GLfloat y1, + GLfloat r1, GLfloat g1, GLfloat b1, GLfloat a1, + GLfloat x2, GLfloat y2, + GLfloat r2, GLfloat g2, GLfloat b2, GLfloat a2, + GLfloat x3, GLfloat y3, + GLfloat r3, GLfloat g3, GLfloat b3, GLfloat a3); + void R2D_GL3_DrawTriangle( + GLfloat x1, GLfloat y1, + GLfloat r1, GLfloat g1, GLfloat b1, GLfloat a1, + GLfloat x2, GLfloat y2, + GLfloat r2, GLfloat g2, GLfloat b2, GLfloat a2, + GLfloat x3, GLfloat y3, + GLfloat r3, GLfloat g3, GLfloat b3, GLfloat a3); + void R2D_GL2_DrawImage(R2D_Image *img); + void R2D_GL3_DrawImage(R2D_Image *img); + void R2D_GL2_DrawSprite(R2D_Sprite *spr); + void R2D_GL3_DrawSprite(R2D_Sprite *spr); + void R2D_GL2_DrawText(R2D_Text *txt); + void R2D_GL3_DrawText(R2D_Text *txt); + void R2D_GL3_FlushBuffers(); +#endif + +#ifdef __cplusplus +} +#endif diff --git a/ext/ruby2d/shapes.c b/ext/ruby2d/shapes.c new file mode 100644 index 0000000..5698e3f --- /dev/null +++ b/ext/ruby2d/shapes.c @@ -0,0 +1,154 @@ +// shapes.c + +#include "ruby2d.h" + + +/* + * Rotate a point around a given point + * Params: + * p The point to rotate + * angle The angle in degrees + * rx The x coordinate to rotate around + * ry The y coordinate to rotate around + */ +R2D_GL_Point R2D_RotatePoint(R2D_GL_Point p, GLfloat angle, GLfloat rx, GLfloat ry) { + + // Convert from degrees to radians + angle = angle * M_PI / 180.0; + + // Get the sine and cosine of the angle + GLfloat sa = sin(angle); + GLfloat ca = cos(angle); + + // Translate point to origin + p.x -= rx; + p.y -= ry; + + // Rotate point + GLfloat xnew = p.x * ca - p.y * sa; + GLfloat ynew = p.x * sa + p.y * ca; + + // Translate point back + p.x = xnew + rx; + p.y = ynew + ry; + + return p; +} + + +/* + * Get the point to be rotated around given a position in a rectangle + */ +R2D_GL_Point R2D_GetRectRotationPoint(int x, int y, int w, int h, int position) { + + R2D_GL_Point p; + + switch (position) { + case R2D_CENTER: + p.x = x + (w / 2.0); + p.y = y + (h / 2.0); + break; + case R2D_TOP_LEFT: + p.x = x; + p.y = y; + break; + case R2D_TOP_RIGHT: + p.x = x + w; + p.y = y; + break; + case R2D_BOTTOM_LEFT: + p.x = x; + p.y = y + h; + break; + case R2D_BOTTOM_RIGHT: + p.x = x + w; + p.y = y + h; + break; + } + + return p; +} + + +/* + * Draw a triangle + */ +void R2D_DrawTriangle(GLfloat x1, GLfloat y1, + GLfloat r1, GLfloat g1, GLfloat b1, GLfloat a1, + GLfloat x2, GLfloat y2, + GLfloat r2, GLfloat g2, GLfloat b2, GLfloat a2, + GLfloat x3, GLfloat y3, + GLfloat r3, GLfloat g3, GLfloat b3, GLfloat a3) { + + R2D_GL_DrawTriangle(x1, y1, r1, g1, b1, a1, + x2, y2, r2, g2, b2, a2, + x3, y3, r3, g3, b3, a3); +} + + +/* + * Draw a quad, using two triangles + */ +void R2D_DrawQuad(GLfloat x1, GLfloat y1, + GLfloat r1, GLfloat g1, GLfloat b1, GLfloat a1, + GLfloat x2, GLfloat y2, + GLfloat r2, GLfloat g2, GLfloat b2, GLfloat a2, + GLfloat x3, GLfloat y3, + GLfloat r3, GLfloat g3, GLfloat b3, GLfloat a3, + GLfloat x4, GLfloat y4, + GLfloat r4, GLfloat g4, GLfloat b4, GLfloat a4) { + + R2D_GL_DrawTriangle(x1, y1, r1, g1, b1, a1, + x2, y2, r2, g2, b2, a2, + x3, y3, r3, g3, b3, a3); + + R2D_GL_DrawTriangle(x3, y3, r3, g3, b3, a3, + x4, y4, r4, g4, b4, a4, + x1, y1, r1, g1, b1, a1); +}; + + +/* + * Draw a line from a quad + */ +void R2D_DrawLine(GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2, + GLfloat width, + GLfloat r1, GLfloat g1, GLfloat b1, GLfloat a1, + GLfloat r2, GLfloat g2, GLfloat b2, GLfloat a2, + GLfloat r3, GLfloat g3, GLfloat b3, GLfloat a3, + GLfloat r4, GLfloat g4, GLfloat b4, GLfloat a4) { + + double length = sqrt(powf(x1 - x2, 2) + powf(y1 - y2, 2)); + double x = ((x2 - x1) / length) * width / 2; + double y = ((y2 - y1) / length) * width / 2; + + R2D_DrawQuad( + x1 - y, y1 + x, r1, g1, b1, a1, + x1 + y, y1 - x, r2, g2, b2, a2, + x2 + y, y2 - x, r3, g3, b3, a3, + x2 - y, y2 + x, r4, g4, b4, a4 + ); +}; + + +/* + * Draw a circle from triangles + */ +void R2D_DrawCircle(GLfloat x, GLfloat y, GLfloat radius, int sectors, + GLfloat r, GLfloat g, GLfloat b, GLfloat a) { + + double angle = 2 * M_PI / sectors; + + for (int i = 0; i < sectors; i++) { + + GLfloat x1 = x + radius * cos(i * angle); + GLfloat y1 = y + radius * sin(i * angle); + + GLfloat x2 = x + radius * cos((i - 1) * angle); + GLfloat y2 = y + radius * sin((i - 1) * angle); + + R2D_GL_DrawTriangle( x, y, r, g, b, a, + x1, y1, r, g, b, a, + x2, y2, r, g, b, a); + } +} diff --git a/ext/ruby2d/sound.c b/ext/ruby2d/sound.c new file mode 100644 index 0000000..326c695 --- /dev/null +++ b/ext/ruby2d/sound.c @@ -0,0 +1,93 @@ +// sound.c + +#include "ruby2d.h" + + +/* + * Create a sound, given an audio file path + */ +R2D_Sound *R2D_CreateSound(const char *path) { + R2D_Init(); + + // Check if sound file exists + if (!R2D_FileExists(path)) { + R2D_Error("R2D_CreateSound", "Sound file `%s` not found", path); + return NULL; + } + + // Allocate the sound structure + R2D_Sound *snd = (R2D_Sound *) malloc(sizeof(R2D_Sound)); + if (!snd) { + R2D_Error("R2D_CreateSound", "Out of memory!"); + return NULL; + } + + // Load the sound data from file + snd->data = Mix_LoadWAV(path); + if (!snd->data) { + R2D_Error("Mix_LoadWAV", Mix_GetError()); + free(snd); + return NULL; + } + + // Initialize values + snd->path = path; + + return snd; +} + + +/* + * Play the sound + */ +void R2D_PlaySound(R2D_Sound *snd) { + if (!snd) return; + Mix_PlayChannel(-1, snd->data, 0); +} + + +/* + * Get the sound's volume + */ +int R2D_GetSoundVolume(R2D_Sound *snd) { + if (!snd) return -1; + return ceil(Mix_VolumeChunk(snd->data, -1) * (100.0 / MIX_MAX_VOLUME)); +} + + +/* + * Set the sound's volume a given percentage + */ +void R2D_SetSoundVolume(R2D_Sound *snd, int volume) { + if (!snd) return; + // Set volume to be a percentage of the maximum mix volume + Mix_VolumeChunk(snd->data, (volume / 100.0) * MIX_MAX_VOLUME); +} + + +/* + * Get the sound mixer volume + */ +int R2D_GetSoundMixVolume() { + return ceil(Mix_Volume(-1, -1) * (100.0 / MIX_MAX_VOLUME)); +} + + +/* + * Set the sound mixer volume a given percentage + */ +void R2D_SetSoundMixVolume(int volume) { + // This sets the volume value across all channels + // Set volume to be a percentage of the maximum mix volume + Mix_Volume(-1, (volume / 100.0) * MIX_MAX_VOLUME); +} + + +/* + * Free the sound + */ +void R2D_FreeSound(R2D_Sound *snd) { + if (!snd) return; + Mix_FreeChunk(snd->data); + free(snd); +} diff --git a/ext/ruby2d/sprite.c b/ext/ruby2d/sprite.c new file mode 100644 index 0000000..30d2042 --- /dev/null +++ b/ext/ruby2d/sprite.c @@ -0,0 +1,147 @@ +// sprite.c + +#include "ruby2d.h" + + +/* + * Create a sprite, given an image file path + */ +R2D_Sprite *R2D_CreateSprite(const char *path) { + + // Check if image file exists + if (!R2D_FileExists(path)) { + R2D_Error("R2D_CreateSprite", "Sprite image file `%s` not found", path); + return NULL; + } + + // Allocate the sprite structure + R2D_Sprite *spr = (R2D_Sprite *) malloc(sizeof(R2D_Sprite)); + if (!spr) { + R2D_Error("R2D_CreateSprite", "Out of memory!"); + return NULL; + } + + // Load the sprite image file + spr->img = R2D_CreateImage(path); + if (!spr->img) { + R2D_Error("R2D_CreateSprite", "Cannot create sprite image `%s`", path); + free(spr); + return NULL; + } + + // Initialize values + spr->path = path; + spr->x = 0; + spr->y = 0; + spr->color.r = 1.f; + spr->color.g = 1.f; + spr->color.b = 1.f; + spr->color.a = 1.f; + spr->width = spr->img->width; + spr->height = spr->img->height; + spr->clip_width = spr->img->width; + spr->clip_height = spr->img->height; + spr->rotate = 0; + spr->rx = 0; + spr->ry = 0; + spr->tx1 = 0.f; + spr->ty1 = 0.f; + spr->tx2 = 1.f; + spr->ty2 = 0.f; + spr->tx3 = 1.f; + spr->ty3 = 1.f; + spr->tx4 = 0.f; + spr->ty4 = 1.f; + + return spr; +} + + +/* + * Clip a sprite + */ +void R2D_ClipSprite(R2D_Sprite *spr, int x, int y, int w, int h) { + if (!spr) return; + + // Calculate ratios + // rw = ratio width; rh = ratio height + double rw = w / (double)spr->img->width; + double rh = h / (double)spr->img->height; + + // Apply ratios to x, y coordinates + // cx = crop x coord; cy = crop y coord + double cx = x * rw; + double cy = y * rh; + + // Convert given width, height to doubles + // cw = crop width; ch = crop height + double cw = (double)w; + double ch = (double)h; + + // Apply ratio to texture width and height + // tw = texture width; th = texture height + double tw = rw * w; + double th = rh * h; + + // Calculate and store sprite texture values + + spr->tx1 = cx / cw; + spr->ty1 = cy / ch; + + spr->tx2 = (cx + tw) / cw; + spr->ty2 = cy / ch; + + spr->tx3 = (cx + tw) / cw; + spr->ty3 = (cy + th) / ch; + + spr->tx4 = cx / cw; + spr->ty4 = (cy + th) / ch; + + // Store the sprite dimensions + spr->width = (spr->width / (double)spr->clip_width ) * w; + spr->height = (spr->height / (double)spr->clip_height) * h; + spr->clip_width = w; + spr->clip_height = h; +} + + +/* + * Rotate a sprite + */ +void R2D_RotateSprite(R2D_Sprite *spr, GLfloat angle, int position) { + + R2D_GL_Point p = R2D_GetRectRotationPoint( + spr->x, spr->y, spr->width, spr->height, position + ); + + spr->rotate = angle; + spr->rx = p.x; + spr->ry = p.y; +} + + +/* + * Draw a sprite + */ +void R2D_DrawSprite(R2D_Sprite *spr) { + if (!spr) return; + + if (spr->img->texture_id == 0) { + R2D_GL_CreateTexture(&spr->img->texture_id, spr->img->format, + spr->img->width, spr->img->height, + spr->img->surface->pixels, GL_NEAREST); + SDL_FreeSurface(spr->img->surface); + } + + R2D_GL_DrawSprite(spr); +} + + +/* + * Free a sprite + */ +void R2D_FreeSprite(R2D_Sprite *spr) { + if (!spr) return; + R2D_FreeImage(spr->img); + free(spr); +} diff --git a/ext/ruby2d/text.c b/ext/ruby2d/text.c new file mode 100644 index 0000000..58d4086 --- /dev/null +++ b/ext/ruby2d/text.c @@ -0,0 +1,129 @@ +// text.c + +#include "ruby2d.h" + + +/* + * Create text, given a font file path, the message, and size + */ +R2D_Text *R2D_CreateText(const char *font, const char *msg, int size) { + R2D_Init(); + + // Check if font file exists + if (!R2D_FileExists(font)) { + R2D_Error("R2D_CreateText", "Font file `%s` not found", font); + return NULL; + } + + // `msg` cannot be an empty string or NULL for TTF_SizeText + if (msg == NULL || strlen(msg) == 0) msg = " "; + + // Allocate the text structure + R2D_Text *txt = (R2D_Text *) malloc(sizeof(R2D_Text)); + if (!txt) { + R2D_Error("R2D_CreateText", "Out of memory!"); + return NULL; + } + + // Open the font + txt->font_data = TTF_OpenFont(font, size); + if (!txt->font_data) { + R2D_Error("TTF_OpenFont", TTF_GetError()); + free(txt); + return NULL; + } + + // Initialize values + txt->font = font; + txt->msg = (char *) malloc(strlen(msg) + 1 * sizeof(char)); + strcpy(txt->msg, msg); + txt->x = 0; + txt->y = 0; + txt->color.r = 1.f; + txt->color.g = 1.f; + txt->color.b = 1.f; + txt->color.a = 1.f; + txt->rotate = 0; + txt->rx = 0; + txt->ry = 0; + txt->texture_id = 0; + + // Save the width and height of the text + TTF_SizeText(txt->font_data, txt->msg, &txt->width, &txt->height); + + return txt; +} + + +/* + * Set the text message + */ +void R2D_SetText(R2D_Text *txt, const char *msg, ...) { + if (!txt) return; + + // `msg` cannot be an empty string or NULL for TTF_SizeText + if (msg == NULL || strlen(msg) == 0) msg = " "; + + // Format and store new text string + va_list args; + va_start(args, msg); + free(txt->msg); + vasprintf(&txt->msg, msg, args); + va_end(args); + + // Save the width and height of the text + TTF_SizeText(txt->font_data, txt->msg, &txt->width, &txt->height); + + // Delete the current texture so a new one can be generated + R2D_GL_FreeTexture(&txt->texture_id); +} + + +/* + * Rotate text + */ +void R2D_RotateText(R2D_Text *txt, GLfloat angle, int position) { + + R2D_GL_Point p = R2D_GetRectRotationPoint( + txt->x, txt->y, txt->width, txt->height, position + ); + + txt->rotate = angle; + txt->rx = p.x; + txt->ry = p.y; +} + + +/* + * Draw text + */ +void R2D_DrawText(R2D_Text *txt) { + if (!txt) return; + + if (txt->texture_id == 0) { + SDL_Color color = { 255, 255, 255 }; + txt->surface = TTF_RenderText_Blended(txt->font_data, txt->msg, color); + if (!txt->surface) { + R2D_Error("TTF_RenderText_Blended", TTF_GetError()); + return; + } + R2D_GL_CreateTexture(&txt->texture_id, GL_RGBA, + txt->width, txt->height, + txt->surface->pixels, GL_NEAREST); + SDL_FreeSurface(txt->surface); + } + + R2D_GL_DrawText(txt); +} + + +/* + * Free the text + */ +void R2D_FreeText(R2D_Text *txt) { + if (!txt) return; + free(txt->msg); + R2D_GL_FreeTexture(&txt->texture_id); + TTF_CloseFont(txt->font_data); + free(txt); +} diff --git a/ext/ruby2d/window.c b/ext/ruby2d/window.c new file mode 100644 index 0000000..f10c55b --- /dev/null +++ b/ext/ruby2d/window.c @@ -0,0 +1,414 @@ +// window.c + +#include "ruby2d.h" + + +/* + * Create a window + */ +R2D_Window *R2D_CreateWindow(const char *title, int width, int height, + R2D_Update update, R2D_Render render, int flags) { + + R2D_Init(); + + SDL_DisplayMode dm; + SDL_GetCurrentDisplayMode(0, &dm); + R2D_Log(R2D_INFO, "Current display mode is %dx%dpx @ %dhz", dm.w, dm.h, dm.refresh_rate); + + width = width == R2D_DISPLAY_WIDTH ? dm.w : width; + height = height == R2D_DISPLAY_HEIGHT ? dm.h : height; + + // Allocate window and set default values + R2D_Window *window = (R2D_Window *) malloc(sizeof(R2D_Window)); + window->sdl = NULL; + window->glcontext = NULL; + window->title = title; + window->width = width; + window->height = height; + window->orig_width = width; + window->orig_height = height; + window->viewport.width = width; + window->viewport.height = height; + window->viewport.mode = R2D_SCALE; + window->update = update; + window->render = render; + window->flags = flags; + window->on_key = NULL; + window->on_mouse = NULL; + window->on_controller = NULL; + window->vsync = true; + window->fps_cap = 60; + window->background.r = 0.0; + window->background.g = 0.0; + window->background.b = 0.0; + window->background.a = 1.0; + window->icon = NULL; + window->close = true; + + // Return the window structure + return window; +} + + +/* + * Show the window + */ +int R2D_Show(R2D_Window *window) { + + if (!window) { + R2D_Error("R2D_Show", "Window cannot be shown (because it's NULL)"); + return 1; + } + + // Create SDL window + window->sdl = SDL_CreateWindow( + window->title, // title + SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, // window position + window->width, window->height, // window size + SDL_WINDOW_OPENGL | window->flags // flags + ); + + if (!window->sdl) R2D_Error("SDL_CreateWindow", SDL_GetError()); + if (window->icon) R2D_SetIcon(window, window->icon); + + // The window created by SDL might not actually be the requested size. + // If it's not the same, retrieve and store the actual window size. + int actual_width, actual_height; + SDL_GetWindowSize(window->sdl, &actual_width, &actual_height); + + if ((window->width != actual_width) || (window->height != actual_height)) { + R2D_Log(R2D_INFO, + "Scaling window to %ix%i (requested size was %ix%i)", + actual_width, actual_height, window->width, window->height + ); + window->width = actual_width; + window->height = actual_height; + window->orig_width = actual_width; + window->orig_height = actual_height; + } + + // Set Up OpenGL ///////////////////////////////////////////////////////////// + + R2D_GL_Init(window); + + // SDL 2.0.10 and macOS 10.15 fix //////////////////////////////////////////// + + #if MACOS + SDL_SetWindowSize(window->sdl, window->width, window->height); + #endif + + // Set Main Loop Data //////////////////////////////////////////////////////// + + const Uint8 *key_state; + + Uint32 frames = 0; // Total frames since start + Uint32 frames_last_sec = 0; // Frames in the last second + Uint32 start_ms = SDL_GetTicks(); // Elapsed time since start + Uint32 next_second_ms = SDL_GetTicks(); // The last time plus a second + Uint32 begin_ms = start_ms; // Time at beginning of loop + Uint32 end_ms; // Time at end of loop + Uint32 elapsed_ms; // Total elapsed time + Uint32 loop_ms; // Elapsed time of loop + int delay_ms; // Amount of delay to achieve desired frame rate + const double decay_rate = 0.5; // Determines how fast an average decays over time + double fps = window->fps_cap; // Moving average of actual FPS, initial value a guess + + // Enable VSync + if (window->vsync) { + if (!SDL_SetHint(SDL_HINT_RENDER_VSYNC, "1")) { + R2D_Log(R2D_WARN, "VSync cannot be enabled"); + } + } + + window->close = false; + + // Main Loop ///////////////////////////////////////////////////////////////// + + while (!window->close) { + + // Clear Frame ///////////////////////////////////////////////////////////// + + R2D_GL_Clear(window->background); + + // Set FPS ///////////////////////////////////////////////////////////////// + + frames++; + frames_last_sec++; + end_ms = SDL_GetTicks(); + elapsed_ms = end_ms - start_ms; + + // Calculate the frame rate using an exponential moving average + if (next_second_ms < end_ms) { + fps = decay_rate * fps + (1.0 - decay_rate) * frames_last_sec; + frames_last_sec = 0; + next_second_ms = SDL_GetTicks() + 1000; + } + + loop_ms = end_ms - begin_ms; + delay_ms = (1000 / window->fps_cap) - loop_ms; + + if (delay_ms < 0) delay_ms = 0; + + // Note: `loop_ms + delay_ms` should equal `1000 / fps_cap` + + SDL_Delay(delay_ms); + begin_ms = SDL_GetTicks(); + + // Handle Input and Window Events ////////////////////////////////////////// + + int mx, my; // mouse x, y coordinates + + SDL_Event e; + while (SDL_PollEvent(&e)) { + switch (e.type) { + + case SDL_KEYDOWN: + if (window->on_key && e.key.repeat == 0) { + R2D_Event event = { + .type = R2D_KEY_DOWN, .key = SDL_GetScancodeName(e.key.keysym.scancode) + }; + window->on_key(event); + } + break; + + case SDL_KEYUP: + if (window->on_key) { + R2D_Event event = { + .type = R2D_KEY_UP, .key = SDL_GetScancodeName(e.key.keysym.scancode) + }; + window->on_key(event); + } + break; + + case SDL_MOUSEBUTTONDOWN: case SDL_MOUSEBUTTONUP: + if (window->on_mouse) { + R2D_GetMouseOnViewport(window, e.button.x, e.button.y, &mx, &my); + R2D_Event event = { + .button = e.button.button, .x = mx, .y = my + }; + event.type = e.type == SDL_MOUSEBUTTONDOWN ? R2D_MOUSE_DOWN : R2D_MOUSE_UP; + event.dblclick = e.button.clicks == 2 ? true : false; + window->on_mouse(event); + } + break; + + case SDL_MOUSEWHEEL: + if (window->on_mouse) { + R2D_Event event = { + .type = R2D_MOUSE_SCROLL, .direction = e.wheel.direction, + .delta_x = e.wheel.x, .delta_y = -e.wheel.y + }; + window->on_mouse(event); + } + break; + + case SDL_MOUSEMOTION: + if (window->on_mouse) { + R2D_GetMouseOnViewport(window, e.motion.x, e.motion.y, &mx, &my); + R2D_Event event = { + .type = R2D_MOUSE_MOVE, + .x = mx, .y = my, .delta_x = e.motion.xrel, .delta_y = e.motion.yrel + }; + window->on_mouse(event); + } + break; + + case SDL_CONTROLLERAXISMOTION: + if (window->on_controller) { + R2D_Event event = { + .which = e.caxis.which, .type = R2D_AXIS, + .axis = e.caxis.axis, .value = e.caxis.value + }; + window->on_controller(event); + } + break; + + case SDL_JOYAXISMOTION: + if (window->on_controller && !R2D_IsController(e.jbutton.which)) { + R2D_Event event = { + .which = e.jaxis.which, .type = R2D_AXIS, + .axis = e.jaxis.axis, .value = e.jaxis.value + }; + window->on_controller(event); + } + break; + + case SDL_CONTROLLERBUTTONDOWN: case SDL_CONTROLLERBUTTONUP: + if (window->on_controller) { + R2D_Event event = { + .which = e.cbutton.which, .button = e.cbutton.button + }; + event.type = e.type == SDL_CONTROLLERBUTTONDOWN ? R2D_BUTTON_DOWN : R2D_BUTTON_UP; + window->on_controller(event); + } + break; + + case SDL_JOYBUTTONDOWN: case SDL_JOYBUTTONUP: + if (window->on_controller && !R2D_IsController(e.jbutton.which)) { + R2D_Event event = { + .which = e.jbutton.which, .button = e.jbutton.button + }; + event.type = e.type == SDL_JOYBUTTONDOWN ? R2D_BUTTON_DOWN : R2D_BUTTON_UP; + window->on_controller(event); + } + break; + + case SDL_JOYDEVICEADDED: + R2D_Log(R2D_INFO, "Controller connected (%i total)", SDL_NumJoysticks()); + R2D_OpenControllers(); + break; + + case SDL_JOYDEVICEREMOVED: + if (R2D_IsController(e.jdevice.which)) { + R2D_Log(R2D_INFO, "Controller #%i: %s removed (%i remaining)", e.jdevice.which, SDL_GameControllerName(SDL_GameControllerFromInstanceID(e.jdevice.which)), SDL_NumJoysticks()); + SDL_GameControllerClose(SDL_GameControllerFromInstanceID(e.jdevice.which)); + } else { + R2D_Log(R2D_INFO, "Controller #%i: %s removed (%i remaining)", e.jdevice.which, SDL_JoystickName(SDL_JoystickFromInstanceID(e.jdevice.which)), SDL_NumJoysticks()); + SDL_JoystickClose(SDL_JoystickFromInstanceID(e.jdevice.which)); + } + break; + + case SDL_WINDOWEVENT: + switch (e.window.event) { + case SDL_WINDOWEVENT_RESIZED: + // Store new window size, set viewport + window->width = e.window.data1; + window->height = e.window.data2; + R2D_GL_SetViewport(window); + break; + } + break; + + case SDL_QUIT: + R2D_Close(window); + break; + } + } + + // Detect keys held down + int num_keys; + key_state = SDL_GetKeyboardState(&num_keys); + + for (int i = 0; i < num_keys; i++) { + if (window->on_key) { + if (key_state[i] == 1) { + R2D_Event event = { + .type = R2D_KEY_HELD, .key = SDL_GetScancodeName(i) + }; + window->on_key(event); + } + } + } + + // Get and store mouse position relative to the viewport + int wx, wy; // mouse x, y coordinates relative to the window + SDL_GetMouseState(&wx, &wy); + R2D_GetMouseOnViewport(window, wx, wy, &window->mouse.x, &window->mouse.y); + + // Update Window State ///////////////////////////////////////////////////// + + // Store new values in the window + window->frames = frames; + window->elapsed_ms = elapsed_ms; + window->loop_ms = loop_ms; + window->delay_ms = delay_ms; + window->fps = fps; + + // Call update and render callbacks + if (window->update) window->update(); + if (window->render) window->render(); + + // Draw Frame ////////////////////////////////////////////////////////////// + + // Render and flush all OpenGL buffers + R2D_GL_FlushBuffers(); + + // Swap buffers to display drawn contents in the window + SDL_GL_SwapWindow(window->sdl); + } + + return 0; +} + + +/* + * Set the icon for the window + */ +void R2D_SetIcon(R2D_Window *window, const char *icon) { + R2D_Image *img = R2D_CreateImage(icon); + if (img) { + window->icon = icon; + SDL_SetWindowIcon(window->sdl, img->surface); + R2D_FreeImage(img); + } else { + R2D_Log(R2D_WARN, "Could not set window icon"); + } +} + + +/* + * Take a screenshot of the window + */ +void R2D_Screenshot(R2D_Window *window, const char *path) { + + #if GLES + R2D_Error("R2D_Screenshot", "Not supported in OpenGL ES"); + #else + // Create a surface the size of the window + SDL_Surface *surface = SDL_CreateRGBSurface( + SDL_SWSURFACE, window->width, window->height, 24, + 0x000000FF, 0x0000FF00, 0x00FF0000, 0 + ); + + // Grab the pixels from the front buffer, save to surface + glReadBuffer(GL_FRONT); + glReadPixels(0, 0, window->width, window->height, GL_RGB, GL_UNSIGNED_BYTE, surface->pixels); + + // Flip image vertically + + void *temp_row = (void *)malloc(surface->pitch); + if (!temp_row) { + R2D_Error("R2D_Screenshot", "Out of memory!"); + SDL_FreeSurface(surface); + return; + } + + int height_div_2 = (int) (surface->h * 0.5); + + for (int index = 0; index < height_div_2; index++) { + memcpy((Uint8 *)temp_row,(Uint8 *)(surface->pixels) + surface->pitch * index, surface->pitch); + memcpy((Uint8 *)(surface->pixels) + surface->pitch * index, (Uint8 *)(surface->pixels) + surface->pitch * (surface->h - index-1), surface->pitch); + memcpy((Uint8 *)(surface->pixels) + surface->pitch * (surface->h - index-1), temp_row, surface->pitch); + } + + free(temp_row); + + // Save image to disk + IMG_SavePNG(surface, path); + SDL_FreeSurface(surface); + #endif +} + + +/* + * Close the window + */ +int R2D_Close(R2D_Window *window) { + if (!window->close) { + R2D_Log(R2D_INFO, "Closing window"); + window->close = true; + } + return 0; +} + + +/* + * Free all resources + */ +int R2D_FreeWindow(R2D_Window *window) { + R2D_Close(window); + SDL_GL_DeleteContext(window->glcontext); + SDL_DestroyWindow(window->sdl); + free(window); + return 0; +} diff --git a/lib/ruby2d/cli/build.rb b/lib/ruby2d/cli/build.rb index 9eb0728..1a47d1a 100644 --- a/lib/ruby2d/cli/build.rb +++ b/lib/ruby2d/cli/build.rb @@ -96,7 +96,7 @@ def build_native(rb_file) end # Compile to a native executable - `cc build/app.c -lmruby \`simple2d --libs\` -o build/app` + `cc build/app.c -lmruby -o build/app` # Clean up clean_up unless @debug @@ -162,12 +162,6 @@ end def build_ios_tvos(rb_file, device) check_build_src_file(rb_file) - # Check for Simple 2D framework, - unless File.exist?('/usr/local/Frameworks/Simple2D/iOS/Simple2D.framework') && File.exist?('/usr/local/Frameworks/Simple2D/tvOS/Simple2D.framework') - puts "#{'Error:'.error} Simple 2D iOS and tvOS frameworks not found. Install them and try again.\n" - exit - end - # Check if MRuby exists; if not, quit if `which mruby`.empty? puts "#{'Error:'.error} Can't find MRuby, which is needed to build native Ruby 2D applications.\n" @@ -197,6 +191,7 @@ def build_ios_tvos(rb_file, device) f << File.read("#{@gem_dir}/ext/ruby2d/ruby2d.c") end + # TODO: Need add this functionality to the gem # Build the Xcode project `simple2d build --#{device} build/#{device}/MyApp.xcodeproj` diff --git a/ruby2d.gemspec b/ruby2d.gemspec index 7afa33d..0247802 100644 --- a/ruby2d.gemspec +++ b/ruby2d.gemspec @@ -15,7 +15,7 @@ Gem::Specification.new do |s| s.files = Dir.glob('lib/**/*') + Dir.glob('assets/**/*') + - Dir.glob('ext/**/*.{c,js,rb}') + Dir.glob('ext/**/*.{h,c,rb}') s.extensions = ['ext/ruby2d/extconf.rb'] s.executables << 'ruby2d' end -- cgit v1.2.3