diff options
| author | Leander Scherer <leander@schererleander.de> | 2026-03-08 20:04:08 +0100 |
|---|---|---|
| committer | Leander Scherer <leander@schererleander.de> | 2026-03-08 20:07:58 +0100 |
| commit | c5455ef0fbfc4203a4aa8ad185dfa43bdadc0b82 (patch) | |
| tree | 731fffe1d9591587a366895f4a46859d2c79436f /include | |
| parent | 7bdc10ae6de645812f4e57185067f0a83ca5655f (diff) | |
feat(deps): add raylib and raytmx dependencies
Diffstat (limited to 'include')
| -rw-r--r-- | include/hoxml.h | 1497 | ||||
| -rw-r--r-- | include/raylib.h | 1708 | ||||
| -rw-r--r-- | include/raymath.h | 2941 | ||||
| -rw-r--r-- | include/raytmx.h | 4815 | ||||
| -rw-r--r-- | include/rlgl.h | 5262 |
5 files changed, 16223 insertions, 0 deletions
diff --git a/include/hoxml.h b/include/hoxml.h new file mode 100644 index 0000000..d7addec --- /dev/null +++ b/include/hoxml.h @@ -0,0 +1,1497 @@ +/* +Copyright (c) 2024-2025 Luke Philipsen + +Permission to use, copy, modify, and/or distribute this software for +any purpose with or without fee is hereby granted. + +THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL +WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE +FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY +DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN +AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +/* Usage + + Do this: + #define HOXML_IMPLEMENTATION + before you include this file in *one* C or C++ file to create the implementation. + + You can define HOXML_DECL with + #define HOXML_DECL static + or + #define HOXML_DECL extern + to specify hoxml function declarations as static or extern, respectively. + The default specifier is extern. +*/ + +#ifndef HOXML_H + #define HOXML_H + +#include <stdlib.h> /* strtoul() */ +#include <string.h> /* memcpy(), memset(), NULL, size_t */ + +#ifndef HOXML_DECL + #define HOXML_DECL +#endif /* HOXML_DECL */ + +#ifdef __cplusplus + extern "C" { +#endif /* __cpluspus */ + +/***************/ +/* Definitions */ + +/** + * Error and token codes returned after parsing. + */ +typedef enum { + HOXML_ERROR_INVALID_INPUT = -9, /**< One or more parameter passed to hoxml was unacceptable. */ + HOXML_ERROR_INTERNAL = -8, /**< There's a bug in hoxml and parsing must halt. */ + HOXML_ERROR_INSUFFICIENT_MEMORY = -7, /**< Initialization or continued parsing require more memory. */ + HOXML_ERROR_UNEXPECTED_EOF = -6, /**< Reached the end of the XML content before the end of the document. */ + HOXML_ERROR_SYNTAX = -5, /**< Syntax error (e.g. "<element<"). */ + HOXML_ERROR_ENCODING = -4, /**< Character encoding error or contradiction. */ + HOXML_ERROR_TAG_MISMATCH = -3, /**< Close tag does not match the open tag (e.g. "<tag>" followed by "</tga>"). */ + HOXML_ERROR_INVALID_DOCUMENT_TYPE_DECLARATION = -2, /**< <!DOCTYPE> declaration not before the root element. */ + HOXML_ERROR_INVALID_DOCUMENT_DECLARATION = -1, /**< <?xml?> declaration not before the root element. */ + HOXML_END_OF_DOCUMENT = 0, /**< The root element has been closed, parsing is done. */ + HOXML_ELEMENT_BEGIN, /**< A new element/tag began and its name is available. */ + HOXML_ELEMENT_END, /**< An element was closed, </tag> or <tag/>, and its name and content are available. */ + HOXML_ATTRIBUTE, /**< An attribute's value, its name, and its element are available. */ + HOXML_PROCESSING_INSTRUCTION_BEGIN, /**< A processing instruction began and its target is available. */ + HOXML_PROCESSING_INSTRUCTION_END /**< A processing instruction ended and its content is available. */ +} hoxml_code_t; + +/** + * Holds context and state information needed by hoxml. Some of this information is public and holds the data parsed + * from XML content (element names, attribute names and values, etc.) but some is private and only makes sense to hoxml. + */ +typedef struct { + /* Public */ + char* tag; /**< Holds the name of the open or just-closed tag, or processing instruction target. */ + char* attribute; /**< Holds the current attribute's name. */ + char* value; /**< Holds the current attribute's value. */ + char* content; /**< Holds the current element's content. This means all character data found, including spaces. */ + int line; /**< The line currently being parsed. Lines are determined by line feeds and carriage returns. */ + int column; /**< The column, on the current line, of the character last parsed. */ + int depth; /**< The nested level of elements. Assigned with the level in which the element was found. */ + + /* Private (for internal use) */ + int is_initialized; /* Set to 1, or true, by hoxml_init() and indicates this context is safe to use */ + const char* xml; /* XML content to be parsed */ + size_t xml_length; /* Length of the XML content to parse */ + int encoding; /* Character encoding of the XML content */ + const char* iterator; /* Pointer to the character in the XML content being parsed */ + char* buffer; /* Memory allocated for hoxml to use */ + size_t buffer_length; /* Amount of memory allocated for hoxml */ + char* reference_start; /* Pointer to a location on the stack where a reference entity string (e.g "<") began */ + char* stack; /* Pointer to the current node in the stack-like structure of elements */ + int state; /* Current parsing state, determines which characters are acceptable and when to return */ + int post_state; /* When not "none" this indicates a post-state that has a cleanup step */ + int return_state; /* State to return to after the processing of a comment or reference has finished */ + int error_return_state; /* State to return to after recovering from an error */ + unsigned long stream; /* Holds the current character, whole or partial. May contain bytes from different strings. */ + size_t stream_length; /* Length of the 'stream' variable in bytes */ + unsigned newline_character; /* The character used to increment the 'line' variable, \r or \n */ +} hoxml_context_t; + +/** + * Sets up the hoxml context object to begin parsing. Following this, call hoxml_parse() until + * HOXML_END_OF_DOCUMENT or one of the error values is returned. + * + * @param context Pointer to an allocated hoxml context object. This instance will be modified. + * @param buffer A pointer to some contiguous block of memory for hoxml to use. This will also be modified, frequently. + * @param buffer_length The length, in bytes, of the buffer handed to hoxml as the 'buffer' parameter. + */ +HOXML_DECL void hoxml_init(hoxml_context_t* context, void* buffer, size_t buffer_length); + +/** + * Instruct hoxml to use a new buffer. This maintains the current state of parsing meaning that the next call to + * hoxml_parse() will continue none the wiser. + * The buffer must have a length greater than the current buffer and both buffers must be allocated at the time this + * function is called. Once it returns, the original buffer may and should be freed. + * + * @param context An initialized hoxml context object. + * @param buffer A pointer to a new, contiguous block of memory for hoxml to use. + * @param buffer_length The length, in bytes, of the buffer handed to hoxml as the 'buffer' parameter. + */ +HOXML_DECL void hoxml_realloc(hoxml_context_t* context, void* buffer, size_t buffer_length); + +/** + * Begin or continue parsing the given XML content string. + * The XML content string does not need to contain the content in its entirety. If hoxml finds a null terminator or + * parses up to the indicated length of the content, HOXML_ERROR_UNEXPECTED_EOF is returned and parsing will cease. + * However, this error is recoverable and parsing will continue if the next call to hoxml_parse() passes a new XML + * content string, using the same pointer or not. + * + * @param context An initialized hoxml context object. This should be treated as read-only until parsing is done. + * @param xml XML content as an encoded string. Supported character encodings include ASCII, UTF-8, and UTF-16(BE|LE). + * @param xml_length Length of the XML content in bytes. + * @return A code indicating what information from the XML content is available or an error. + */ +HOXML_DECL hoxml_code_t hoxml_parse(hoxml_context_t* context, const char* xml, size_t xml_length); + +#ifdef __cplusplus + } +#endif /* __cplusplus */ + +#ifdef HOXML_IMPLEMENTATION + +/******************/ +/* Implementation */ + +enum { + /* Current parser states */ + HOXML_STATE_ERROR_INTERNAL = -8, + HOXML_STATE_ERROR_INSUFFICIENT_MEMORY = -7, + HOXML_STATE_ERROR_UNEXPECTED_EOF = -6, + HOXML_STATE_ERROR_SYNTAX = -5, + HOXML_STATE_ERROR_ENCODING = -4, + HOXML_STATE_ERROR_TAG_MISMATCH = -3, + HOXML_STATE_ERROR_INVALID_DOCUMENT_TYPE_DECLARATION = -2, + HOXML_STATE_ERROR_INVALID_DOCUMENT_DECLARATION = -1, + HOXML_STATE_NONE = 0, + HOXML_STATE_UTF8_BOM1, + HOXML_STATE_UTF8_BOM2, + HOXML_STATE_UTF16BE_BOM, + HOXML_STATE_UTF16LE_BOM, + HOXML_STATE_TAG_BEGIN, + HOXML_STATE_TAG_END, + HOXML_STATE_ELEMENT_NAME1, + HOXML_STATE_ELEMENT_NAME2, + HOXML_STATE_ATTRIBUTE_NAME1, + HOXML_STATE_ATTRIBUTE_NAME2, + HOXML_STATE_ATTRIBUTE_ASSIGNMENT, + HOXML_STATE_ATTRIBUTE_VALUE, + HOXML_STATE_OPEN_TAG, + HOXML_STATE_COMMENT_CDATA_OR_DTD_BEGIN, + HOXML_STATE_COMMENT_BEGIN, + HOXML_STATE_COMMENT, + HOXML_STATE_COMMENT_END1, + HOXML_STATE_COMMENT_END2, + HOXML_STATE_CDATA_BEGIN1, + HOXML_STATE_CDATA_BEGIN2, + HOXML_STATE_CDATA_BEGIN3, + HOXML_STATE_CDATA_BEGIN4, + HOXML_STATE_CDATA_BEGIN5, + HOXML_STATE_CDATA_BEGIN6, + HOXML_STATE_CDATA_CONTENT, + HOXML_STATE_CDATA_END1, + HOXML_STATE_CDATA_END2, + HOXML_STATE_REFERENCE_BEGIN, + HOXML_STATE_REFERENCE_ENTITY, + HOXML_STATE_REFERENCE_NUMERIC, + HOXML_STATE_REFERENCE_HEX, + HOXML_STATE_PROCESSING_INSTRUCTION_BEGIN, + HOXML_STATE_PROCESSING_INSTRUCTION_TARGET1, + HOXML_STATE_PROCESSING_INSTRUCTION_TARGET2, + HOXML_STATE_PROCESSING_INSTRUCTION_CONTENT, + HOXML_STATE_PROCESSING_INSTRUCTION_END, + HOXML_STATE_DTD_BEGIN1, + HOXML_STATE_DTD_BEGIN2, + HOXML_STATE_DTD_BEGIN3, + HOXML_STATE_DTD_BEGIN4, + HOXML_STATE_DTD_BEGIN5, + HOXML_STATE_DTD_BEGIN6, + HOXML_STATE_DTD_BEGIN7, + HOXML_STATE_DTD_BEGIN8, + HOXML_STATE_DTD_NAME, + HOXML_STATE_DTD_CONTENT, + HOXML_STATE_DTD_OPEN_BRACKET, + HOXML_STATE_DONE, + /* Post (i.e. after) parser states indicating actions to take on the next call to hoxml_parse() */ + HOXML_POST_STATE_TAG_END, + HOXML_POST_STATE_ATTRIBUTE_END, +}; + +enum { + HOXML_FLAG_END_TAG = 1, /* The node is a dedicated end tag (not an empty element) */ + HOXML_FLAG_EMPTY_ELEMENT = 2, /* The node is an empty element */ + HOXML_FLAG_PROCESSING_INSTRUCTION = 4, /* The node is a processing instruction */ + HOXML_FLAG_DOUBLE_QUOTE = 8, /* The value string being parsed was opened with a double quote (") */ + HOXML_FLAG_TERMINATED = 16, /* The node's current string (tag, attribute, etc.) is null terminated */ + HOXML_FLAG_BEGUN = 32, /* The "element begun" code was already returned for this node */ + HOXML_FLAG_INCREMENT_DEPTH = 64, /* Context object's depth value should increase by one next hoxml_parse() */ + HOXML_FLAG_DECREMENT_DEPTH = 128 /* Context object's depth value should decrease by one next hoxml_parse() */ +}; + +enum { + HOXML_ENC_UNKNOWN = 0, /* The character encoding is unknown. UTF-8 is assumed. */ + HOXML_ENC_UTF_8, /* Variable-length encoding (8, 16, 24, or 32 bits) compatible with ASCII */ + HOXML_ENC_UTF_16_LE, /* Variable-length encoding (16 or 32 bits), little-endian variant */ + HOXML_ENC_UTF_16_BE /* Variable-lenght encoding (16 or 32 bits), big-endian variant */ +}; + +enum { + HOXML_CASE_SENSITIVE = 0, /* Cases must match. 'A' == 'a' -> false. */ + HOXML_CASE_INSENSITIVE /* Cases need not match. 'A' == 'a' -> true. */ +}; + +enum { + HOXML_REF_TYPE_ENTITY = 0, /* Predefined strings representing known, problematic characters (e.g. '<') */ + HOXML_REF_TYPE_NUMERIC, /* A value of a character given as a decimal number */ + HOXML_REF_TYPE_HEX /* A value of a character given as a hexadecimal number */ +}; + +struct _hoxml_node_t; +typedef struct _hoxml_node_t { + struct _hoxml_node_t* parent; /* Points to the parent node, or NULL if this is the root */ + char* end; /* Points to the last byte of this node's data */ + int flags; /* May contain any number of the flags defined in hoxml_node_flags */ + char tag; /* Where the tag string will be stored in the buffer, must be defined last */ +} hoxml_node_t; + +typedef struct { + unsigned encoded; /* Character as it appeared in the content. In other words, the original, encoded character. */ + unsigned codepoint; /* Unicode codepoint of the character. In other words, the decoded character. */ + size_t bytes; /* Number of eight-bit bytes of the encoded character, in the [1, 4] range */ +} hoxml_character_t; + +#ifndef UINT32_MAX /* Defined in stdint.h with later revisions of C and C++ but not for some earlier ones */ + #define UINT32_MAX (0xffffffff) +#endif +#define HOXML_STACK ((hoxml_node_t*)context->stack) +#define HOXML_TO_LOWER(c) (c >= 'A' && c <= 'Z' ? c + 32 : c) +#define HOXML_IS_NEW_LINE(c) (c == 0x0A || c == 0x0D) +#define HOXML_IS_WHITESPACE(c) (c == 0x20 || c == 0x09 || HOXML_IS_NEW_LINE(c)) +#define HOXML_IS_ASCII_CHAR(c) (c >= 0x21 && c <= 0x7F) +#define HOXML_IS_CHAR_DATA(c) (c != '<' && c != '&') +#define HOXML_IS_ALPHA(c) ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) +#define HOXML_IS_NUMERIC(c) (c >= '0' && c <= '9') +#define HOXML_IS_NAME_START_CHAR(c) (HOXML_IS_ALPHA(c) || c == ':' || c == '_' || (c >= 0xC0 && c <= 0xD6) || \ + (c >= 0xD8 && c <= 0xF6) || c >= 0xF8) +#define HOXML_IS_NAME_CHAR(c) (HOXML_IS_NAME_START_CHAR(c) || c == '-' || c == '.'|| HOXML_IS_NUMERIC(c)) +#define HOXML_IS_HEX_CHAR(c) (HOXML_IS_NUMERIC(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')) +#define HOXML_IS_VALUE_CHAR_DATA(f, c) (HOXML_IS_CHAR_DATA(c) && ((f & HOXML_FLAG_DOUBLE_QUOTE && c != '"') || \ + c != '\'')) + +void hoxml_push_stack(hoxml_context_t* context); +void hoxml_pop_stack(hoxml_context_t* context); +void hoxml_append_character(hoxml_context_t* context, hoxml_character_t c); +void hoxml_append_terminator(hoxml_context_t* context); +void hoxml_end_reference(hoxml_context_t* context, int type); +void hoxml_begin_tag(hoxml_context_t* context); +hoxml_code_t hoxml_end_tag(hoxml_context_t* context); +int hoxml_post_state_cleanup(hoxml_context_t* context); +hoxml_character_t hoxml_decode_character(const char* str, size_t str_length, int encoding); +hoxml_character_t hoxml_encode_character(unsigned codepoint, int encoding); +char* hoxml_to_ascii(const char* str, int encoding); +size_t hoxml_strlen(const char* str, int encoding); +int hoxml_strcmp(const char* str1, int encoding1, const char* str2, int encoding2, int sensitivity); +const char* hoxml_strstr(const char* haystack, int haystack_encoding, const char* needle, int needle_encoding, + int sensitivity); +#ifdef HOXML_DEBUG + #include <stdio.h> /* printf() */ + #define HOXML_LOG_STATE(s) printf("%s\n", s); +#else + #define HOXML_LOG_STATE(s) +#endif + +HOXML_DECL void hoxml_init(hoxml_context_t* context, void* buffer, size_t buffer_length) { + if (context == NULL || buffer == NULL || buffer_length <= 0) + return; + + memset(context, 0, sizeof(hoxml_context_t)); /* Assign all values of the context to zero */ + context->buffer = (char*)buffer; /* Use the provided buffer */ + context->buffer_length = buffer_length; /* Remember the length of the provided buffer */ + context->line = 1; /* This is meant to be human-readable and humans begin counting at one */ + context->is_initialized = 1; + memset(buffer, 0, buffer_length); /* Fill the buffer with zeroes */ +} + +HOXML_DECL void hoxml_realloc(hoxml_context_t* context, void* buffer, size_t buffer_length) { + if (context == NULL || context->is_initialized == 0 || buffer == NULL || buffer_length <= context->buffer_length) + return; + + /* Reassign the end and parent pointers of each node, beginning at the tail and iterate to the head */ + hoxml_node_t* node = HOXML_STACK; + while (node != NULL) { + hoxml_node_t* parent = node->parent; + node->end = (char*)buffer + (node->end - context->buffer); + if (node->parent != NULL) + node->parent = (hoxml_node_t*)((char*)buffer + ((char*)node->parent - context->buffer)); + node = parent; + } + + /* Use offsets from the original buffer pointer to reassign pointers such that they now point to the new buffer */ + if (context->tag != NULL) + context->tag = (char*)buffer + (context->tag - context->buffer); + if (context->attribute != NULL) + context->attribute = (char*)buffer + (context->attribute - context->buffer); + if (context->value != NULL) + context->value = (char*)buffer + (context->value - context->buffer); + if (context->content != NULL) + context->content = (char*)buffer + (context->content - context->buffer); + if (context->reference_start != NULL) + context->reference_start = (char*)buffer + (context->reference_start - context->buffer); + if (context->stack != NULL) + context->stack = (char*)buffer + (context->stack - context->buffer); + + memset(buffer, 0, buffer_length); /* Fill the new buffer with zeroes */ + memcpy(buffer, context->buffer, context->buffer_length); /* Copy the entire, current buffer to the new buffer */ + context->buffer = (char*)buffer; + context->buffer_length = buffer_length; + + if (context->state == HOXML_STATE_ERROR_INSUFFICIENT_MEMORY) { + context->state = context->error_return_state; + context->error_return_state = HOXML_STATE_NONE; + } +} + +HOXML_DECL hoxml_code_t hoxml_parse(hoxml_context_t* context, const char* xml, const size_t xml_length) { + if (context == NULL || context->is_initialized == 0 || xml == NULL || xml_length == 0) + return HOXML_ERROR_INVALID_INPUT; + + if (HOXML_STACK != NULL) { + if (HOXML_STACK->flags & HOXML_FLAG_INCREMENT_DEPTH) { /* If an element began, increasing nesting */ + context->depth += 1; + HOXML_STACK->flags &= ~HOXML_FLAG_INCREMENT_DEPTH; /* Clear the flag */ + } + + if (HOXML_STACK->flags & HOXML_FLAG_DECREMENT_DEPTH) { /* If an element ended, decreasing nesting */ + context->depth -= 1; + HOXML_STACK->flags &= ~HOXML_FLAG_DECREMENT_DEPTH; /* Clear the flag */ + } + } + + switch (context->state) { + /* Two errors are recoverable: HOXML_ERROR_INSUFFICIENT_MEMORY and HOXML_ERROR_UNEXPECTED_EOF. The former can */ + /* be recovered by assigning a new buffer with hoxml_realloc(). The latter can be recovered by passing a new */ + /* XML content string to hoxml_parse() so we'll check for one before concluding we're still in error. */ + case HOXML_STATE_ERROR_UNEXPECTED_EOF: { + /* Try to decode a character, or remainder of a character, at the beginning of this hopefully-new string */ + unsigned long stream = context->stream; + /* Calculate the number of bytes to copy into the 'stream' variable from the hopefully-new string. We */ + /* want 4 bytes, or whatever is available. */ + size_t bytes_to_copy = 4; + if (bytes_to_copy > xml_length) + bytes_to_copy = xml_length; + if (context->stream_length > 0) { + /* Adjust the number of bytes to copy to account for possible bytes from a previous string */ + bytes_to_copy -= context->stream_length; + /* Append the new bytes to the previous one(s) */ + memcpy((char*)&stream + context->stream_length, xml, bytes_to_copy); + } + else { + /* Copy to the 'stream' under the assumption that all of it can be overwritten */ + memcpy(&stream, xml, bytes_to_copy); + } + hoxml_character_t c = hoxml_decode_character((const char*)&stream, xml_length, context->encoding); + /* If the character is the equivalent of a null terminator or there was not enough data */ + if (c.codepoint == 0 || c.codepoint == UINT32_MAX) + return HOXML_ERROR_UNEXPECTED_EOF; + context->state = context->error_return_state; + context->error_return_state = HOXML_STATE_NONE; + /* Note: there is a check for a change in the input pointer a little further down */ + } break; + case HOXML_STATE_DONE: return HOXML_END_OF_DOCUMENT; + case HOXML_STATE_ERROR_INTERNAL: return HOXML_ERROR_INTERNAL; + case HOXML_STATE_ERROR_INSUFFICIENT_MEMORY: return HOXML_ERROR_INSUFFICIENT_MEMORY; + case HOXML_STATE_ERROR_SYNTAX: return HOXML_ERROR_SYNTAX; + case HOXML_STATE_ERROR_ENCODING: return HOXML_ERROR_ENCODING; + case HOXML_STATE_ERROR_TAG_MISMATCH: return HOXML_ERROR_TAG_MISMATCH; + case HOXML_STATE_ERROR_INVALID_DOCUMENT_DECLARATION: return HOXML_ERROR_INVALID_DOCUMENT_DECLARATION; + } + + /* A handful of cases leave the context in an intermediary state. This allows the caller to have access to things */ + /* like the tag's name, an attribute's value, etc. but that old data may now need to be cleaned up. */ + if (hoxml_post_state_cleanup(context)) /* If the cleanup process found the document ended */ + return HOXML_END_OF_DOCUMENT; + + /* If the pointer to the XML content string has changed */ + if (context->xml != xml) { + /* A few variables are now invalid: the pointer to the content, its length, and the iterator */ + context->xml = xml; + context->xml_length = xml_length; + context->iterator = xml; + } + + /* Remember some context variables in case we hit an unexpected EoF and need to undo an iteration */ + const char* previous_iterator = context->iterator; + size_t previous_stream_length = context->stream_length; + while (context->state >= HOXML_STATE_NONE && context->state <= HOXML_STATE_DONE) { + /* About half of the parsing states assume the stack is non-null. */ + /* If parsing is currently in one of those states and the stack (head) pointer is null. */ + if (((context->state >= HOXML_STATE_TAG_BEGIN && context->state <= HOXML_STATE_OPEN_TAG) || + (context->state >= HOXML_STATE_REFERENCE_BEGIN && context->state <= HOXML_STATE_REFERENCE_HEX)) && + context->stack == NULL) { + /* Some unforseen bug has led us to a state in which continuing would cause an illegal memory access. */ + /* Parsing must halt. There is no way to recover. */ + context->state = HOXML_STATE_ERROR_INTERNAL; + return HOXML_ERROR_INTERNAL; + } + + /* Calculate the number of bytes remaining in the current XML content string */ + size_t bytes_remaining = (size_t)(context->xml_length - (context->iterator - context->xml)); + /* Calculate the number of bytes to copy into the 'stream' variable. We want 4 bytes, or whatever is left. */ + size_t bytes_to_copy = 4; + if (bytes_to_copy > bytes_remaining) + bytes_to_copy = bytes_remaining; + if (context->stream_length > 0) { + /* Adjust the number of bytes to copy to account for possible bytes from a previous XML content string. */ + /* This will be non-zero in the rare case where content is being given in parts. */ + bytes_to_copy -= context->stream_length; + /* Append the new bytes to the previous one(s) */ + memcpy((char*)&(context->stream) + context->stream_length, context->iterator, bytes_to_copy); + } + else { + /* Copy to the 'stream' under the assumption that all of it can be overwritten */ + memcpy(&(context->stream), context->iterator, bytes_to_copy); + } + hoxml_character_t c = hoxml_decode_character((const char*)&(context->stream), bytes_remaining, + context->encoding); + + /* If the character is the equivalent of a null terminator or there was not enough data to decode the value */ + if (c.codepoint == 0 || c.codepoint == UINT32_MAX) { + context->stream_length = bytes_to_copy; + context->error_return_state = context->state; + context->state = HOXML_STATE_ERROR_UNEXPECTED_EOF; + return HOXML_ERROR_UNEXPECTED_EOF; + } else if (HOXML_IS_NEW_LINE(c.codepoint)) { + if (context->newline_character == 0) /* If this is the first newline */ + context->newline_character = c.codepoint; /* Remember this as the character to use for increments */ + if (c.codepoint == context->newline_character) /* Avoid incrementing twice for files with \r\n endings */ + context->line++; + context->column = 0; + } else + context->column++; + + /* Iterate up to four bytes into the XML content string. The idea is to jump forward by the number of bytes */ + /* that were just decoded as a single character. The number of bytes varies from one to four bytes depending */ + /* on the character encoding and character's codepoint. We also need to consider the case in which some of */ + /* this character's bytes were carried over from a previous XML content string. Those bytes would have been */ + /* stashed in the context's 'stream' variable where 'stream_length' tells us the number of said bytes. */ + previous_iterator = context->iterator; + previous_stream_length = context->stream_length; + context->iterator += c.bytes - context->stream_length; + context->stream_length = 0; + + #ifdef HOXML_DEBUG + char debugCodepoint = HOXML_IS_NEW_LINE(c.codepoint) ? ' ' : c.codepoint; + printf(" %c [%08X] [L%02dC%02d] -> ", debugCodepoint, c.codepoint, context->line, context->column); + #endif + + switch(context->state) { + case HOXML_STATE_NONE: /* The first state immediately following initialization, or a document declaration */ + HOXML_LOG_STATE("HOXML_STATE_NONE") + if (c.codepoint == '<') + hoxml_begin_tag(context); + else if (c.encoded == 0xEF) { /* UTF-8 Byte Order Marker (BOM) is [EF] BB BF, as hex bytes */ + context->state = HOXML_STATE_UTF8_BOM1; + context->column--; /* Don't count this as a column */ + } else if (c.encoded == 0xFE) { /* UTF-16BE BOM is [FE] FF, as hex bytes */ + context->state = HOXML_STATE_UTF16BE_BOM; + context->column--; /* Don't count this as a column */ + } else if (c.encoded == 0xFF) { /* UTF-16LE BOM is [FF] FE, as hex bytes */ + context->state = HOXML_STATE_UTF16LE_BOM; + context->column--; /* Don't count this as a column */ + } else if (!HOXML_IS_WHITESPACE(c.codepoint)) + context->state = HOXML_STATE_ERROR_SYNTAX; + break; + case HOXML_STATE_UTF8_BOM1: /* The first byte of a UTF-8 byte order marker was found */ + HOXML_LOG_STATE("HOXML_STATE_UTF8_BOM1") + context->column--; /* Don't count this as a column */ + if (c.encoded == 0xBB) /* UTF-8 BOM is EF [BB] BF, as hex bytes */ + context->state = HOXML_STATE_UTF8_BOM2; + else + context->state = HOXML_STATE_ERROR_SYNTAX; + break; + case HOXML_STATE_UTF8_BOM2: /* The second byte of a UTF-8 byte order marker was found */ + HOXML_LOG_STATE("HOXML_STATE_UTF8_BOM2") + context->column--; /* Don't count this as a column */ + if (c.encoded == 0xBF) { /* UTF-8 BOM is EF BB [BF], as hex bytes */ + context->state = HOXML_STATE_NONE; + context->encoding = HOXML_ENC_UTF_8; + } else + context->state = HOXML_STATE_ERROR_SYNTAX; + break; + case HOXML_STATE_UTF16BE_BOM: /* The first byte of a UTF-16BE byte order marker was found */ + HOXML_LOG_STATE("HOXML_STATE_UTF16BE_BOM") + context->column--; /* Don't count this as a column */ + if (c.encoded == 0xFF) { /* UTF-16BE BOM is FE [FF], as hex bytes */ + context->state = HOXML_STATE_NONE; + context->encoding = HOXML_ENC_UTF_16_BE; + } else + context->state = HOXML_STATE_ERROR_SYNTAX; + break; + case HOXML_STATE_UTF16LE_BOM: /* The first byte of a UTF-16LE byte order marker was found */ + HOXML_LOG_STATE("HOXML_STATE_UTF16LE_BOM") + context->column--; /* Don't count this as a column */ + if (c.encoded == 0xFE) { /* UTF-16LE BOM is FF [FE], as hex bytes */ + context->state = HOXML_STATE_NONE; + context->encoding = HOXML_ENC_UTF_16_LE; + } else + context->state = HOXML_STATE_ERROR_SYNTAX; + break; + case HOXML_STATE_TAG_BEGIN: /* A new tag was started (a '<' was found) and a new node has been pushed */ + HOXML_LOG_STATE("HOXML_STATE_TAG_BEGIN") + if (c.codepoint == '?') { /* "<?" begins a processing instruction */ + context->state = HOXML_STATE_PROCESSING_INSTRUCTION_BEGIN; + HOXML_STACK->flags |= HOXML_FLAG_PROCESSING_INSTRUCTION; /* Apply the PI flag to this node */ + } else if (c.codepoint == '/') /* "</" begins an end tag */ + HOXML_STACK->flags |= HOXML_FLAG_END_TAG; /* Apply the end tag flag to this node */ + else if (c.codepoint == '!') /* "<!--" = comment, "<![CDATA[" = CDATA, and "<!DOCTYPE" = DTD */ + context->state = HOXML_STATE_COMMENT_CDATA_OR_DTD_BEGIN; + else if (HOXML_IS_NAME_START_CHAR(c.codepoint)) { + hoxml_append_character(context, c); + if (context->state >= HOXML_STATE_NONE) { /* If appending the character was successful */ + context->state = HOXML_STATE_ELEMENT_NAME1; + context->tag = &(HOXML_STACK->tag); /* The tag's name string will begin here */ + } + } else + context->state = HOXML_STATE_ERROR_SYNTAX; + break; + case HOXML_STATE_ELEMENT_NAME1: /* A name start character was found after '<' (e.g. the 't' in "<tag>") */ + HOXML_LOG_STATE("HOXML_STATE_ELEMENT_NAME1") + if (c.codepoint == '>') { + hoxml_append_terminator(context); + if (context->state >= HOXML_STATE_NONE) /* If appending the terminator was successful */ + return hoxml_end_tag(context); + } else if (c.codepoint == '/') { /* The tag is an empty element, AKA self-closed tag (e.g. "<tag/>") */ + if (HOXML_STACK->flags & HOXML_FLAG_END_TAG) /* If it's also a regular close tag (e.g. "</tag/>") */ + context->state = HOXML_STATE_ERROR_SYNTAX; + else { + hoxml_append_terminator(context); + if (context->state >= HOXML_STATE_NONE) { /* If appending the terminator was successful */ + HOXML_STACK->flags |= HOXML_FLAG_EMPTY_ELEMENT; /* Apply the empty element flag */ + return HOXML_ELEMENT_BEGIN; + } + } + } else if (HOXML_IS_WHITESPACE(c.codepoint)) { /* If whitespace ended the element name (e.g. "<tag ") */ + hoxml_append_terminator(context); + if (context->state >= HOXML_STATE_NONE) { /* If appending the terminator was successful */ + context->state = HOXML_STATE_ELEMENT_NAME2; + HOXML_STACK->flags |= HOXML_FLAG_BEGUN; /* Indicate "element begun" has already been returned */ + return HOXML_ELEMENT_BEGIN; + } + } else if (HOXML_IS_NAME_CHAR(c.codepoint)) + hoxml_append_character(context, c); + else + context->state = HOXML_STATE_ERROR_SYNTAX; + break; + case HOXML_STATE_ELEMENT_NAME2: /* Whitespace was found after a tag name (e.g. "<tag >") */ + HOXML_LOG_STATE("HOXML_STATE_ELEMENT_NAME2") + if (c.codepoint == '>') { + hoxml_append_terminator(context); + if (context->state >= HOXML_STATE_NONE) { /* If appending the terminator was successful */ + /* If "element begun" has already been returned for this element and this element is not */ + /* self-closing (i.e. the element has the form "<tag ... />" where "..." may be attributes) */ + if (HOXML_STACK->flags & HOXML_FLAG_BEGUN && + !(HOXML_STACK->flags & HOXML_FLAG_EMPTY_ELEMENT)) { + hoxml_end_tag(context); /* Do not return, "element begun" was returned when the name ended */ + hoxml_post_state_cleanup(context); /* Because hoxml_parse() won't be called, clean up now */ + } else + return hoxml_end_tag(context); + } + } else if (c.codepoint == '/') { /* The tag is an empty element, AKA self-closed tag (e.g. "<tag/>") */ + if (HOXML_STACK->flags & HOXML_FLAG_END_TAG) /* If it's also a regular close tag (e.g. "</tag/>") */ + context->state = HOXML_STATE_ERROR_SYNTAX; + else + HOXML_STACK->flags |= HOXML_FLAG_EMPTY_ELEMENT; /* Apply the empty element flag to this node */ + } else if (HOXML_IS_NAME_START_CHAR(c.codepoint)) { /* First letter of an attribute name */ + hoxml_append_character(context, c); + if (context->state >= HOXML_STATE_NONE) { /* If appending the character was successful */ + context->state = HOXML_STATE_ATTRIBUTE_NAME1; + context->attribute = HOXML_STACK->end; /* The attribute's name string began here */ + } + } else if (!HOXML_IS_WHITESPACE(c.codepoint)) + context->state = HOXML_STATE_ERROR_SYNTAX; + break; + case HOXML_STATE_ATTRIBUTE_NAME1: /* A name start character was found inside a tag after whitespace */ + HOXML_LOG_STATE("HOXML_STATE_ATTRIBUTE_NAME1") + if (c.codepoint == '=') { /* The name was immediately followed by '=' */ + hoxml_append_terminator(context); + if (context->state >= HOXML_STATE_NONE) /* If appending the terminator was successful */ + context->state = HOXML_STATE_ATTRIBUTE_ASSIGNMENT; + } else if (HOXML_IS_NAME_CHAR(c.codepoint)) + hoxml_append_character(context, c); + else if (HOXML_IS_WHITESPACE(c.codepoint)) { /* Whitespace after the name, only '=' is allowed next */ + hoxml_append_terminator(context); + if (context->state >= HOXML_STATE_NONE) /* If appending the terminator was successful */ + context->state = HOXML_STATE_ATTRIBUTE_NAME2; + } else + context->state = HOXML_STATE_ERROR_SYNTAX; + break; + case HOXML_STATE_ATTRIBUTE_NAME2: /* Whitespace was found after an attribute name, look for '=' */ + HOXML_LOG_STATE("HOXML_STATE_ATTRIBUTE_NAME2") + if (c.codepoint == '=') + context->state = HOXML_STATE_ATTRIBUTE_ASSIGNMENT; + else if (!HOXML_IS_WHITESPACE(c.codepoint)) /* Only '=' and whitespace are allowed after an attribute name */ + context->state = HOXML_STATE_ERROR_SYNTAX; + break; + case HOXML_STATE_ATTRIBUTE_ASSIGNMENT: /* Found a ' =' after an attribute name, look for quotes or whitespace */ + HOXML_LOG_STATE("HOXML_STATE_ATTRIBUTE_ASSIGNMENT") + if (c.codepoint == '"' || c.codepoint == '\'') { + context->state = HOXML_STATE_ATTRIBUTE_VALUE; + if (c.codepoint == '"') + HOXML_STACK->flags |= HOXML_FLAG_DOUBLE_QUOTE; /* Apply the double quote flag to this node */ + else + HOXML_STACK->flags &= ~HOXML_FLAG_DOUBLE_QUOTE; /* Remove the double quote flag from this node */ + context->value = HOXML_STACK->end + 1; /* The attribute's value string will begin here */ + } + else if (!HOXML_IS_WHITESPACE(c.codepoint)) + context->state = HOXML_STATE_ERROR_SYNTAX; + break; + case HOXML_STATE_ATTRIBUTE_VALUE: /* A quotation, single or double, was found after an attribute name and '=' */ + HOXML_LOG_STATE("HOXML_STATE_ATTRIBUTE_VALUE") + if ((HOXML_STACK->flags & HOXML_FLAG_DOUBLE_QUOTE && c.codepoint == '"') || (!(HOXML_STACK->flags & + HOXML_FLAG_DOUBLE_QUOTE) && c.codepoint == '\'')) { /* The quotation marks match, value is done */ + hoxml_append_terminator(context); + if (context->state >= HOXML_STATE_NONE) { /* If appending the terminator was successful */ + context->state = HOXML_STATE_ELEMENT_NAME2; + context->post_state = HOXML_POST_STATE_ATTRIBUTE_END; /* Clean up some attribute things next call */ + return HOXML_ATTRIBUTE; + } + } else if (c.codepoint == '&') { + context->state = HOXML_STATE_REFERENCE_BEGIN; + context->return_state = HOXML_STATE_ATTRIBUTE_VALUE; /* Return to this attribute value state later */ + } else if (HOXML_IS_VALUE_CHAR_DATA(HOXML_STACK->flags, c.codepoint)) + hoxml_append_character(context, c); + else + context->state = HOXML_STATE_ERROR_SYNTAX; + break; + case HOXML_STATE_OPEN_TAG: /* Found a '>' and now inside an open tag, looking for multiple things */ + HOXML_LOG_STATE("HOXML_STATE_OPEN_TAG") + if (c.codepoint == '<') + hoxml_begin_tag(context); + else if (c.codepoint == '&') { + context->state = HOXML_STATE_REFERENCE_BEGIN; + context->return_state = HOXML_STATE_OPEN_TAG; /* Return to this open tag state later */ + } else if (HOXML_IS_CHAR_DATA(c.codepoint)) + hoxml_append_character(context, c); + else + context->state = HOXML_STATE_ERROR_SYNTAX; + break; + case HOXML_STATE_COMMENT_CDATA_OR_DTD_BEGIN: /* Found "<!", looking for a '-', '[', or 'D' */ + HOXML_LOG_STATE("HOXML_STATE_COMMENT_CDATA_OR_DTD_BEGIN") + if (c.codepoint == '-') /* Possible beginning of a comment (i.e. "<!--") */ + context->state = HOXML_STATE_COMMENT_BEGIN; + else if (c.codepoint == '[') /* Possible beginning of a CDATA section (i.e. "<![CDATA[") */ + context->state = HOXML_STATE_CDATA_BEGIN1; + else if (c.codepoint == 'D') { /* Possible beginning of a DTD (i.e. "<!DOCTYPE") */ + if (context->return_state != HOXML_STATE_NONE) { /* If this DTD was found after a root element */ + context->state = HOXML_STATE_ERROR_INVALID_DOCUMENT_TYPE_DECLARATION; + return HOXML_ERROR_INVALID_DOCUMENT_TYPE_DECLARATION; + } else + context->state = HOXML_STATE_DTD_BEGIN1; + } else + context->state = HOXML_STATE_ERROR_SYNTAX; + break; + case HOXML_STATE_COMMENT_BEGIN: /* Found a '-' was found after "<!", looking for a '-' beginning a comment */ + HOXML_LOG_STATE("HOXML_STATE_COMMENT_BEGIN") + hoxml_pop_stack(context); /* The preceeding '<' triggered a new node. Undo it. */ + if (c.codepoint == '-') + context->state = HOXML_STATE_COMMENT; + else + context->state = HOXML_STATE_ERROR_SYNTAX; + break; + case HOXML_STATE_COMMENT: /* Found a second '-' and now in a comment, looking for '-' */ + HOXML_LOG_STATE("HOXML_STATE_COMMENT") + if (c.codepoint == '-') + context->state = HOXML_STATE_COMMENT_END1; + else + context->state = HOXML_STATE_COMMENT; + break; + case HOXML_STATE_COMMENT_END1: /* Found a '-' while in a comment, looking for a second '-' */ + HOXML_LOG_STATE("HOXML_STATE_COMMENT_END1") + if (c.codepoint == '-') + context->state = HOXML_STATE_COMMENT_END2; + else + context->state = HOXML_STATE_COMMENT; + break; + case HOXML_STATE_COMMENT_END2: /* Found a second '-' while in a comment, looking for '>' to end the comment */ + HOXML_LOG_STATE("HOXML_STATE_COMMENT_END2") + if (c.codepoint == '>') + context->state = context->return_state; /* Return to the original state at the time '<' was found */ + else + context->state = HOXML_STATE_ERROR_SYNTAX; + break; + case HOXML_STATE_CDATA_BEGIN1: /* Found a '[' after "<!", looking for 'C' */ + HOXML_LOG_STATE("HOXML_STATE_CDATA_BEGIN1") + hoxml_pop_stack(context); /* The preceeding '<' triggered a new node. Undo it. */ + if (c.codepoint == 'C') + context->state = HOXML_STATE_CDATA_BEGIN2; + else + context->state = HOXML_STATE_ERROR_SYNTAX; + break; + case HOXML_STATE_CDATA_BEGIN2: /* Found a 'C' after "<![", looking for 'D' */ + HOXML_LOG_STATE("HOXML_STATE_CDATA_BEGIN2") + if (c.codepoint == 'D') + context->state = HOXML_STATE_CDATA_BEGIN3; + else + context->state = HOXML_STATE_ERROR_SYNTAX; + break; + case HOXML_STATE_CDATA_BEGIN3: /* Found a 'D' after "<![C", looking for 'A' */ + HOXML_LOG_STATE("HOXML_STATE_CDATA_BEGIN3") + if (c.codepoint == 'A') + context->state = HOXML_STATE_CDATA_BEGIN4; + else + context->state = HOXML_STATE_ERROR_SYNTAX; + break; + case HOXML_STATE_CDATA_BEGIN4: /* Found an 'A' after "<![CD", looking for 'T' */ + HOXML_LOG_STATE("HOXML_STATE_CDATA_BEGIN4") + if (c.codepoint == 'T') + context->state = HOXML_STATE_CDATA_BEGIN5; + else + context->state = HOXML_STATE_ERROR_SYNTAX; + break; + case HOXML_STATE_CDATA_BEGIN5: /* Found a 'T' after "<![CDA", looking for 'A' */ + HOXML_LOG_STATE("HOXML_STATE_CDATA_BEGIN5") + if (c.codepoint == 'A') + context->state = HOXML_STATE_CDATA_BEGIN6; + else + context->state = HOXML_STATE_ERROR_SYNTAX; + break; + case HOXML_STATE_CDATA_BEGIN6: /* Found an 'A' after "<![CDAT", looking for '[' */ + HOXML_LOG_STATE("HOXML_STATE_CDATA_BEGIN6") + if (c.codepoint == '[') + context->state = HOXML_STATE_CDATA_CONTENT; + else + context->state = HOXML_STATE_ERROR_SYNTAX; + break; + case HOXML_STATE_CDATA_CONTENT: /* Found a '[' after "<![CDATA" and now in a CDATA section, looking for ']' */ + HOXML_LOG_STATE("HOXML_STATE_CDATA_CONTENT") + hoxml_append_character(context, c); + if (context->state >= HOXML_STATE_NONE) { /* If appending the character was successful */ + if (c.codepoint == ']') + context->state = HOXML_STATE_CDATA_END1; + } break; + case HOXML_STATE_CDATA_END1: /* Found a ']' while in a CDATA section, looking for a second ']' */ + HOXML_LOG_STATE("HOXML_STATE_CDATA_END1") + hoxml_append_character(context, c); + if (context->state >= HOXML_STATE_NONE) { /* If appending the character was successful */ + if (c.codepoint == ']') + context->state = HOXML_STATE_CDATA_END2; + else + context->state = HOXML_STATE_CDATA_CONTENT; + } break; + case HOXML_STATE_CDATA_END2: /* Found a second ']' while in a CDATA section, looking for '>' */ + HOXML_LOG_STATE("HOXML_STATE_CDATA_END2") + if (c.codepoint == '>') { + context->state = HOXML_STATE_OPEN_TAG; + /* We couldn't be sure the CDATA section had ended until now so two ']' characters were appended. */ + /* If the document is encoded with UTF-16, four bytes need to be removed. Two bytes otherwise. */ + size_t bytes = context->encoding >= HOXML_ENC_UTF_16_BE ? 4 : 2; + /* The 'end' pointer is currently pointing at the last byte, the second ']' or its latter half if */ + /* using UTF-16. To remove the "]]" we replace them with zeroes. */ + memset(HOXML_STACK->end - bytes + 1, 0, bytes); + HOXML_STACK->end -= bytes; + } else { + hoxml_append_character(context, c); + if (context->state >= HOXML_STATE_NONE) /* If appending the character was successful */ + context->state = HOXML_STATE_CDATA_CONTENT; + } break; + case HOXML_STATE_REFERENCE_BEGIN: /* Found an '&' in content or a value, looking for '#', ';', or characters */ + HOXML_LOG_STATE("HOXML_STATE_REFERENCE_BEGIN") + context->reference_start = HOXML_STACK->end + 1; /* Point to the first byte for comparisons later */ + if (c.codepoint == '#') + context->state = HOXML_STATE_REFERENCE_NUMERIC; + /* The predefined entities are "amp", "lt", "gt", "quot", and "apos". Check for just their first letters. */ + else if (c.codepoint == 'a' || c.codepoint == 'g' || c.codepoint == 'l' || c.codepoint == 'q') { + hoxml_append_character(context, c); + if (context->state >= HOXML_STATE_NONE) /* If appending the character was successful */ + context->state = HOXML_STATE_REFERENCE_ENTITY; + } else + context->state = HOXML_STATE_ERROR_SYNTAX; + break; + case HOXML_STATE_REFERENCE_ENTITY: /* Looking for "[a]mp", "[l]t", "[g]t", "[q]uot", or "apos" */ + HOXML_LOG_STATE("HOXML_STATE_REFERENCE_ENTITY") + if (c.codepoint == ';') + hoxml_end_reference(context, HOXML_REF_TYPE_ENTITY); + /* Predefined escapes only use a subset of lower case English characters. For now, we'll check for ASCII. */ + else if (HOXML_IS_ASCII_CHAR(c.codepoint)) + hoxml_append_character(context, c); + else + context->state = HOXML_STATE_ERROR_SYNTAX; + break; + case HOXML_STATE_REFERENCE_NUMERIC: /* Found a '#' in a reference, looking for 'x', ';', or chars */ + HOXML_LOG_STATE("HOXML_STATE_REFERENCE_NUMERIC") + if (c.codepoint == 'x') + context->state = HOXML_STATE_REFERENCE_HEX; + else if (c.codepoint == ';') + hoxml_end_reference(context, HOXML_REF_TYPE_NUMERIC); + else if (HOXML_IS_NUMERIC(c.codepoint)) + hoxml_append_character(context, c); + else + context->state = HOXML_STATE_ERROR_SYNTAX; + break; + case HOXML_STATE_REFERENCE_HEX: /* Found an 'x' in a reference after '#', looking for chars or ';' */ + HOXML_LOG_STATE("HOXML_STATE_REFERENCE_HEX") + if (c.codepoint == ';') + hoxml_end_reference(context, HOXML_REF_TYPE_HEX); + else if (HOXML_IS_HEX_CHAR(c.codepoint)) + hoxml_append_character(context, c); + else + context->state = HOXML_STATE_ERROR_SYNTAX; + break; + case HOXML_STATE_PROCESSING_INSTRUCTION_BEGIN: /* Found a '?' after a '<' and now in a processing instruction */ + HOXML_LOG_STATE("HOXML_STATE_PROCESSING_INSTRUCTION_BEGIN") + if (HOXML_IS_NAME_START_CHAR(c.codepoint)) { + hoxml_append_character(context, c); + if (context->state >= HOXML_STATE_NONE) { /* If appending the character was successful */ + context->state = HOXML_STATE_PROCESSING_INSTRUCTION_TARGET1; + context->tag = &(HOXML_STACK->tag); /* The processing instruction's target string began here */ + } + } else + context->state = HOXML_STATE_ERROR_SYNTAX; + break; + case HOXML_STATE_PROCESSING_INSTRUCTION_TARGET1: /* Found a name char after "<?", looking for more name chars */ + HOXML_LOG_STATE("HOXML_STATE_PROCESSING_INSTRUCTION_TARGET1") + if (HOXML_IS_WHITESPACE(c.codepoint)) { /* A whitespace marks an end of a target and beginning of content */ + if (hoxml_strcmp(&(HOXML_STACK->tag), context->encoding, "xml", HOXML_ENC_UNKNOWN, + HOXML_CASE_INSENSITIVE) && HOXML_STACK->parent != NULL) { + /* The document declaration (e.g. <?xml encoding="UTF-8"?>) must come before the first element */ + context->state = HOXML_STATE_ERROR_INVALID_DOCUMENT_DECLARATION; + return HOXML_ERROR_INVALID_DOCUMENT_DECLARATION; + } + hoxml_append_terminator(context); + if (context->state >= HOXML_STATE_NONE) { /* If appending the terminator was successful */ + context->state = HOXML_STATE_PROCESSING_INSTRUCTION_CONTENT; + return HOXML_PROCESSING_INSTRUCTION_BEGIN; + } + } else if (c.codepoint == '?') { /* A '?' (or "?>") marks the end of the target and PI */ + hoxml_append_terminator(context); + if (context->state >= HOXML_STATE_NONE) /* If appending the terminator was successful */ + context->state = HOXML_STATE_PROCESSING_INSTRUCTION_END; + } else if (HOXML_IS_NAME_CHAR(c.codepoint)) + hoxml_append_character(context, c); + else + context->state = HOXML_STATE_ERROR_SYNTAX; + break; + case HOXML_STATE_PROCESSING_INSTRUCTION_CONTENT: /* Found space after a PI name, looking for '?' or chars */ + HOXML_LOG_STATE("HOXML_STATE_PROCESSING_INSTRUCTION_CONTENT") + if (c.codepoint == '?') { /* "?>" marks the end of a processing instruction */ + const char* declaration; + if ((declaration = hoxml_strstr(context->content, context->encoding, "encoding=", HOXML_ENC_UNKNOWN, + HOXML_CASE_SENSITIVE)) != NULL) { + const char* encoding; + if ((encoding = hoxml_strstr(declaration, context->encoding, "\"", HOXML_ENC_UNKNOWN, + HOXML_CASE_SENSITIVE)) != NULL || (encoding = hoxml_strstr(declaration, context->encoding, + "'", HOXML_ENC_UNKNOWN, HOXML_CASE_SENSITIVE)) != NULL) { + switch (context->encoding) { + case HOXML_ENC_UNKNOWN: /* The document did not begin with a byte order marker (BOM) */ + if (hoxml_strcmp(encoding, context->encoding, "\"UTF-8\"", HOXML_ENC_UNKNOWN, + HOXML_CASE_INSENSITIVE) != 0 || hoxml_strcmp(encoding, context->encoding, + "'UTF-8'", HOXML_ENC_UNKNOWN, HOXML_CASE_INSENSITIVE) != 0) { + context->encoding = HOXML_ENC_UTF_8; + } else if (hoxml_strcmp(encoding, context->encoding, "\"UTF-16\"", HOXML_ENC_UNKNOWN, + HOXML_CASE_INSENSITIVE) != 0 || hoxml_strcmp(encoding, context->encoding, + "'UTF-16'", HOXML_ENC_UNKNOWN, HOXML_CASE_INSENSITIVE) != 0) { + /* UTF-16 encoded documents require one of the UTF-16 BOMs so this is an error */ + context->state = HOXML_STATE_ERROR_ENCODING; + return HOXML_ERROR_ENCODING; + } break; + case HOXML_ENC_UTF_8: /* The UTF-8 BOM was found at the beginning of the document */ + if (hoxml_strcmp(encoding, context->encoding, "\"UTF-8\"", HOXML_ENC_UNKNOWN, + HOXML_CASE_INSENSITIVE) == 0 && hoxml_strcmp(encoding, context->encoding, + "'UTF-8'", HOXML_ENC_UNKNOWN, HOXML_CASE_INSENSITIVE) == 0) { + /* If the UTF-8 BOM was found but the encoding declaration was not "UTF-8" then we */ + /* have a contradiction and, therefore, an error */ + context->state = HOXML_STATE_ERROR_ENCODING; + return HOXML_ERROR_ENCODING; + } break; + case HOXML_ENC_UTF_16_LE: /* The UTF-16LE BOM was found at the beginning of the document */ + case HOXML_ENC_UTF_16_BE: /* The UTF-16BE BOM was found at the beginning of the document */ + if (hoxml_strcmp(encoding, context->encoding, "\"UTF-16\"", HOXML_ENC_UNKNOWN, + HOXML_CASE_INSENSITIVE) == 0 && hoxml_strcmp(encoding, context->encoding, + "'UTF-16'", HOXML_ENC_UNKNOWN, HOXML_CASE_INSENSITIVE) == 0) + return HOXML_ERROR_ENCODING; + break; + } + } + } + hoxml_append_terminator(context); + if (context->state >= HOXML_STATE_NONE) /* If appending the terminator was successful */ + context->state = HOXML_STATE_PROCESSING_INSTRUCTION_END; + } else { + if (context->content == NULL) /* If this is the first character of the PI's content */ + context->content = HOXML_STACK->end + 1; /* The PI's content string will begin here */ + hoxml_append_character(context, c); + } break; + case HOXML_STATE_DTD_BEGIN1: /* Found a 'D' after "<!", looking for 'O' */ + HOXML_LOG_STATE("HOXML_STATE_DTD_BEGIN1") + hoxml_pop_stack(context); /* The preceeding '<' triggered a new node. Undo it. */ + if (c.codepoint == 'O') + context->state = HOXML_STATE_DTD_BEGIN2; + else + context->state = HOXML_STATE_ERROR_SYNTAX; + break; + case HOXML_STATE_DTD_BEGIN2: /* Found an 'O' after "<!D", looking for 'C' */ + HOXML_LOG_STATE("HOXML_STATE_DTD_BEGIN2") + if (c.codepoint == 'C') + context->state = HOXML_STATE_DTD_BEGIN3; + else + context->state = HOXML_STATE_ERROR_SYNTAX; + break; + case HOXML_STATE_DTD_BEGIN3: /* Found a 'C' after "<!DO", looking for 'T' */ + HOXML_LOG_STATE("HOXML_STATE_DTD_BEGIN3") + if (c.codepoint == 'T') + context->state = HOXML_STATE_DTD_BEGIN4; + else + context->state = HOXML_STATE_ERROR_SYNTAX; + break; + case HOXML_STATE_DTD_BEGIN4: /* Found a 'T' after "<!DOC", looking for 'Y' */ + HOXML_LOG_STATE("HOXML_STATE_DTD_BEGIN4") + if (c.codepoint == 'Y') + context->state = HOXML_STATE_DTD_BEGIN5; + else + context->state = HOXML_STATE_ERROR_SYNTAX; + break; + case HOXML_STATE_DTD_BEGIN5: /* Found a 'Y' after "<!DOCT", looking for 'P' */ + HOXML_LOG_STATE("HOXML_STATE_DTD_BEGIN5") + if (c.codepoint == 'P') + context->state = HOXML_STATE_DTD_BEGIN6; + else + context->state = HOXML_STATE_ERROR_SYNTAX; + break; + case HOXML_STATE_DTD_BEGIN6: /* Found a 'P' after "<!DOCTY", looking for 'E' */ + HOXML_LOG_STATE("HOXML_STATE_DTD_BEGIN6") + if (c.codepoint == 'E') + context->state = HOXML_STATE_DTD_BEGIN7; + else + context->state = HOXML_STATE_ERROR_SYNTAX; + break; + case HOXML_STATE_DTD_BEGIN7: /* Found an 'E' after "<!DOCTYP", looking for whitespace */ + HOXML_LOG_STATE("HOXML_STATE_DTD_BEGIN7") + if (HOXML_IS_WHITESPACE(c.codepoint)) + context->state = HOXML_STATE_DTD_BEGIN8; + else + context->state = HOXML_STATE_ERROR_SYNTAX; + break; + case HOXML_STATE_DTD_BEGIN8: /* Found space after "<!DOCTYPE", looking for more or a name start character */ + HOXML_LOG_STATE("HOXML_STATE_DTD_BEGIN8") + if (HOXML_IS_NAME_START_CHAR(c.codepoint)) + context->state = HOXML_STATE_DTD_NAME; + else if (!HOXML_IS_WHITESPACE(c.codepoint)) + context->state = HOXML_STATE_ERROR_SYNTAX; + break; + case HOXML_STATE_DTD_NAME: /* Found a name start character, looking for whitespace or name characters */ + HOXML_LOG_STATE("HOXML_STATE_DTD_NAME") + if (HOXML_IS_WHITESPACE(c.codepoint)) + context->state = HOXML_STATE_DTD_CONTENT; + else if (!HOXML_IS_NAME_CHAR(c.codepoint)) + context->state = HOXML_STATE_ERROR_SYNTAX; + break; + case HOXML_STATE_DTD_CONTENT: /* Found a DTD name and now looking for mostly anything but mainly '[' or '>' */ + HOXML_LOG_STATE("HOXML_STATE_DTD_CONTENT") + /* We support Document Type Declarations (DTDs) insofar as they do not cause problems and DTD names may */ + /* be recognized as invalid. Beyond that, the content is ignored just as comments are. That said, some */ + /* checks are done here and the "open bracket" state because they're easy. */ + if (c.codepoint == '[') + context->state = HOXML_STATE_DTD_OPEN_BRACKET; + else if (c.codepoint == '>') + context->state = HOXML_STATE_NONE; /* Return to the "before root element" state, the only one allowed */ + else if (!HOXML_IS_CHAR_DATA(c.codepoint)) + context->state = HOXML_STATE_ERROR_SYNTAX; + break; + case HOXML_STATE_DTD_OPEN_BRACKET: /* Found a '[' within DTD content, looking for a closing ']' */ + HOXML_LOG_STATE("HOXML_STATE_DTD_OPEN_BRACKET") + /* Some additional characters are allowed between '[' and ']' brackets, namely markup declaration */ + /* characters like '<' and '>'. We'll just allow anything to keep things simple. */ + if (c.codepoint == ']') + context->state = HOXML_STATE_DTD_CONTENT; + break; + case HOXML_STATE_PROCESSING_INSTRUCTION_END: /* Found a '?' after PI content, looking for '>' */ + HOXML_LOG_STATE("HOXML_STATE_PROCESSING_INSTRUCTION_END") + if (c.codepoint == '>') + return hoxml_end_tag(context); + else + context->state = HOXML_STATE_ERROR_SYNTAX; + break; + } + } /* while (context->state >= HOXML_STATE_NONE && context->state <= HOXML_STATE_DONE) */ + + /* A dozen or so states may try to add data to the buffer, new nodes, characters, or terminators. It's possible */ + /* there was not enough space left in the buffer for this putting us into an error state where parsing cannot */ + /* continue. In these cases, the state will have been set appropriately. */ + if (context->state == HOXML_STATE_ERROR_INSUFFICIENT_MEMORY) { + /* Because the character leading to this error state could not be used, we'll undo the iteration in the hopes */ + /* that we recover from this error (one of two errors that can be recovered, by hoxml_realloc() in this case) */ + /* and parsing can continue on the next call to hoxml_parse() */ + context->iterator = previous_iterator; + context->stream_length = previous_stream_length; + context->column--; /* If recovered, parsing will continue with the same character so don't count this one */ + return HOXML_ERROR_INSUFFICIENT_MEMORY; + } + + /* Any other error case not yet covered by previous checks is due to incorrect syntax in the document */ + return HOXML_ERROR_SYNTAX; +} + +/* Attempt to push a new node to the stack as a child of the current head node */ +void hoxml_push_stack(hoxml_context_t* context) { + /* If "allocating" a new node would overflow the buffer */ + if ((context->stack == NULL && sizeof(hoxml_node_t) >= context->buffer_length) || (context->stack != NULL && + HOXML_STACK->end + 1 + sizeof(hoxml_node_t) >= context->buffer + context->buffer_length)) { + context->error_return_state = context->state; + context->state = HOXML_STATE_ERROR_INSUFFICIENT_MEMORY; + return; + } + + hoxml_node_t* node; + if (context->stack == NULL)/* If pushing the root node */ + node = (hoxml_node_t*) context->buffer; /* Place the new node at the beginning of the buffer */ + else + node = (hoxml_node_t*)(HOXML_STACK->end + 1); + if (node != NULL) { + /* Assign initial values to the node */ + node->parent = HOXML_STACK; /* This new node's parent is the previous stack node */ + node->end = &(node->tag) - 1; /* Point to the last byte of the node, -1 because no tag has been copied yet */ + } + context->stack = (char*)node; +} + +/* Pop the head node from the stack */ +void hoxml_pop_stack(hoxml_context_t* context) { + if (context->stack == NULL) + return; + + /* Reassign the stack (head) pointer so that it now points to the parent of the node about to be popped */ + hoxml_node_t* popped_node = HOXML_STACK; + context->stack = (char*)popped_node->parent; + + /* Overwrite the memory used by this node with zeroes */ + context->tag = context->attribute = context->value = context->content = NULL; /* TODO: move somewhere else */ + memset(popped_node, 0, popped_node->end - (char*)popped_node + 1); +} + +/* Attempt to add the given character to the end of the stack's current head node */ +void hoxml_append_character(hoxml_context_t* context, hoxml_character_t c) { + HOXML_STACK->flags &= ~HOXML_FLAG_TERMINATED; + + if (HOXML_STACK->end + c.bytes >= context->buffer + context->buffer_length) { + context->error_return_state = context->state; + context->state = HOXML_STATE_ERROR_INSUFFICIENT_MEMORY; + return; + } + + memcpy(HOXML_STACK->end + 1, &(c.encoded), c.bytes); /* Copy the character to the stack */ + HOXML_STACK->end += c.bytes; /* Redirect the end pointer to the new end just after the appended character */ +} + +/* Attempt to add a null terminator to the end of the stack's current head node */ +void hoxml_append_terminator(hoxml_context_t* context) { + if (HOXML_STACK->flags & HOXML_FLAG_TERMINATED) /* If the node's current string is already terminated */ + return; /* To avoid adding additional terminators and using more bytes than expected, do nothing */ + HOXML_STACK->flags |= HOXML_FLAG_TERMINATED; + + /* If the document is encoded with UTF-16, two bytes will be appended. One byte otherwise. */ + size_t bytes = context->encoding >= HOXML_ENC_UTF_16_BE ? 2 : 1; + if (HOXML_STACK->end + bytes >= context->buffer + context->buffer_length) { + context->error_return_state = context->state; + context->state = HOXML_STATE_ERROR_INSUFFICIENT_MEMORY; + return; + } + + memset(HOXML_STACK->end + 1, '\0', bytes); /* Copy the terminator to the stack */ + HOXML_STACK->end += bytes; /* Redirect the end pointer to the new end just after the appended terminator */ +} + +/* Perform the steps needed to decode and clean up after a character or entity reference given the context obect and */ +/* the type of reference. There are three types defined in an enumeration. */ +void hoxml_end_reference(hoxml_context_t* context, int type) { + hoxml_character_t c; + c.codepoint = c.encoded = 0; + c.bytes = 0; + unsigned long value; /* Integer value of numeric or hexadecimal reference */ + + switch (type) { + case HOXML_REF_TYPE_ENTITY: + if (hoxml_strcmp(context->reference_start, context->encoding, "lt", + HOXML_ENC_UNKNOWN, HOXML_CASE_SENSITIVE) != 0) { + c = hoxml_encode_character('<', context->encoding); + } else if (hoxml_strcmp(context->reference_start, context->encoding, "gt", + HOXML_ENC_UNKNOWN, HOXML_CASE_SENSITIVE) != 0) { + c = hoxml_encode_character('>', context->encoding); + } else if (hoxml_strcmp(context->reference_start, context->encoding, "amp", HOXML_ENC_UNKNOWN, + HOXML_CASE_SENSITIVE) != 0) { + c = hoxml_encode_character('&', context->encoding); + } else if (hoxml_strcmp(context->reference_start, context->encoding, "apos", HOXML_ENC_UNKNOWN, + HOXML_CASE_SENSITIVE) != 0) { + c = hoxml_encode_character('\'', context->encoding); + } else if (hoxml_strcmp(context->reference_start, context->encoding, "quot", HOXML_ENC_UNKNOWN, + HOXML_CASE_SENSITIVE) != 0) { + c = hoxml_encode_character('"', context->encoding); + } else + context->state = HOXML_STATE_ERROR_SYNTAX; + break; + case HOXML_REF_TYPE_NUMERIC: + value = strtoul(hoxml_to_ascii(context->reference_start, context->encoding), NULL, 10); + if (value != 0) /* If the reference string could be converted as a base-ten integer */ + c = hoxml_encode_character(value, context->encoding); + else + context->state = HOXML_STATE_ERROR_SYNTAX; + break; + case HOXML_REF_TYPE_HEX: + value = strtoul(hoxml_to_ascii(context->reference_start, context->encoding), NULL, 16); + if (value != 0) /* If the reference string could be converted as a base-16 integer */ + c = hoxml_encode_character(value, context->encoding); + else + context->state = HOXML_STATE_ERROR_SYNTAX; + break; + } + + /* If the reference could not be turned into a character */ + if (c.bytes == 0) + return; + + /* Remove the reference's string from the buffer. For example, "<" would result in "lt" being stored so it */ + /* could be parsed here. It should now be removed from the buffer. */ + memset(context->reference_start, 0, HOXML_STACK->end - context->reference_start + 1); + HOXML_STACK->end = context->reference_start - 1; + context->reference_start = NULL; + hoxml_append_character(context, c); /* Append the character being referenced */ + /* No need for any checks against the buffer length. In all cases, more bytes were removed just now than added. */ + context->state = context->return_state; /* Either HOXML_STATE_OPEN_TAG or HOXML_STATE_ATTRIBUTE_VALUE */ + context->return_state = HOXML_STATE_NONE; +} + +void hoxml_begin_tag(hoxml_context_t* context) { + hoxml_push_stack(context); + if (context->state >= HOXML_STATE_NONE) { /* If pushing a new node was successful */ + context->return_state = context->state; /* For comments and references, so we know which state to return to */ + context->state = HOXML_STATE_TAG_BEGIN; + } +} + +hoxml_code_t hoxml_end_tag(hoxml_context_t* context) { + context->state = HOXML_STATE_OPEN_TAG; + context->post_state = HOXML_POST_STATE_TAG_END; /* Common to three of the four possible cases */ + hoxml_node_t* node = HOXML_STACK; + hoxml_node_t* parent = node->parent; + if (node->flags & HOXML_FLAG_END_TAG) { /* True for e.g. </tag> but not <tag/> */ + if (parent == NULL || hoxml_strcmp(&(node->tag), context->encoding, &(parent->tag), context->encoding, + HOXML_CASE_SENSITIVE) == 0) { /* If there was preceeding open tag or there is but it doesn't match */ + context->state = HOXML_STATE_ERROR_TAG_MISMATCH; + return HOXML_ERROR_TAG_MISMATCH; + } else { /* If an element successfully closed a matching open tag */ + hoxml_pop_stack(context); /* Pop the end tag (e.g. "</tag>") */ + context->tag = &(parent->tag); + /* Element content is placed, in memory, after the tag and its terminator... */ + context->content = context->tag + hoxml_strlen(context->tag, context->encoding); + /* ...which may be either one or two bytes, depending on encoding */ + context->content += (context->encoding >= HOXML_ENC_UTF_16_BE ? 2 : 1); + /* Closing an element means one less level of nesting so decrement the depth after returning */ + HOXML_STACK->flags |= HOXML_FLAG_DECREMENT_DEPTH; + return HOXML_ELEMENT_END; + } + } else if (node->flags & HOXML_FLAG_EMPTY_ELEMENT) /* Self-closing/empty element (e.g. "<tag/>") */ + return HOXML_ELEMENT_END; + else if (node->flags & HOXML_FLAG_PROCESSING_INSTRUCTION) /* Processing instruction (e.g. "<?xml?>") */ + return HOXML_PROCESSING_INSTRUCTION_END; + /* The only remaining case is an open tag (e.g. "<tag>") and we expect a matching close tag later */ + context->post_state = HOXML_STATE_NONE; /* For this fourth case, of four possible, there is no clean up */ + /* Opening an element means one more level of nesting so increment the depth after returning */ + HOXML_STACK->flags |= HOXML_FLAG_INCREMENT_DEPTH; + return HOXML_ELEMENT_BEGIN; +} + +int hoxml_post_state_cleanup(hoxml_context_t* context) { + if (context->post_state != HOXML_STATE_NONE) { + switch (context->post_state) { + case HOXML_POST_STATE_TAG_END: { /* Clean up after a close tag, empty element, or processing instruction */ + int was_document_or_document_type_declaration = 0; + /* If the processing instruction flag is applied (i.e. this is a PI) and the PI's target is the reserved */ + /* "xml" target, or some other case variant of it */ + if (HOXML_STACK->flags & HOXML_FLAG_PROCESSING_INSTRUCTION && hoxml_strcmp(&(HOXML_STACK->tag), + context->encoding, "xml", HOXML_ENC_UNKNOWN, HOXML_CASE_INSENSITIVE)) { + context->state = HOXML_STATE_NONE; /* Return to the initial state as if nothing happened */ + was_document_or_document_type_declaration = 1; + } + hoxml_pop_stack(context); /* Pop a start or self-closed tag (<tag> or <tag/> or <?pi?>)*/ + if (context->stack == NULL && was_document_or_document_type_declaration == 0) + return 1; /* hoxml_parse() should return HOXML_END_OF_DOCUMENT */ + break; + } case HOXML_POST_STATE_ATTRIBUTE_END: /* Remove the most recent attribute and value strings from the buffer */ + /* Zero the memory from the end pointer to the byte at which the attribute's name begins */ + memset(context->attribute, 0, HOXML_STACK->end - (char*)context->attribute + 1); + HOXML_STACK->end = context->attribute - 1; + /* With these public properties now pointing to zeroes, nullify them so there's no confusion */ + context->attribute = context->value = NULL; + break; + } + context->post_state = HOXML_STATE_NONE; + } + + return 0; /* hoxml_parse() should not return */ +} + +/* Decode the given character with the given encoding to the its equivalent value */ +hoxml_character_t hoxml_decode_character(const char* str, size_t str_length, int encoding) { + hoxml_character_t c; + c.encoded = c.codepoint = 0; /* These default values are not valid so pausing will cease if returned */ + c.bytes = 0; + + switch (encoding) { + case HOXML_ENC_UNKNOWN: + c.bytes = 1; + break; + case HOXML_ENC_UTF_8: + /* The first byte of a UTF-8 character can can begin with one of four bit patterns, each indicating the */ + /* number of remaining bytes: 0XXXXXXX = 1 byte, 110XXXXX = 2 bytes, 1110XXXX = 3 bytes, 11110XXX = 4 bytes. */ + /* NOTE: UTF-8 is *big* endian. */ + if (((str[0] >> 7) & 0x01) == 0x00) + c.bytes = 1; + else if (((str[0] >> 5) & 0x07) == 0x06) + c.bytes = 2; + else if (((str[0] >> 4) & 0x0F) == 0x0E) + c.bytes = 3; + else if (((str[0] >> 3) & 0x1F) == 0x1E) + c.bytes = 4; + break; + case HOXML_ENC_UTF_16_BE: + /* UTF-16 characters are either two bytes or four bytes where the four-byte characters are encoded such that */ + /* the first two bytes begin with 110110XX and the second with 110111XX. The rest are two-byte characters. */ + if (((str[0] >> 2) & 0x3F) == 0x36 && ((str[2] >> 2) & 0x3F) == 0x37) + c.bytes = 4; + else + c.bytes = 2; + break; + case HOXML_ENC_UTF_16_LE: + /* UTF-16LE (Little Endian) is just like UTF-16BE (Big Endian) but the most and least significant bytes in */ + /* any 16-bit sequence are swapped. (Technically, a byte isn't defined as eight bits but it is in practice.) */ + if (((str[1] >> 2) & 0x3F) == 0x36 && ((str[3] >> 2) & 0x3F) == 0x37) + c.bytes = 4; + else + c.bytes = 2; + break; + } + + /* If the string doesn't have enough bytes in it to decode this character */ + if (c.bytes > str_length) { + /* Set the decoded value to the maximum possible to indicate a failure, zero the rest, and return early */ + c.codepoint = UINT32_MAX; + c.encoded = 0; + c.bytes = 0; + return c; + } + + switch (encoding) { + case HOXML_ENC_UNKNOWN: + c.codepoint = str[0]; + break; + case HOXML_ENC_UTF_8: + if (c.bytes == 1) { + /* One-byte UTF-8 characters are encoded as 0XXXXXXX where the Xs represent the bits of the character's */ + /* value. For all decoding, we want to grab only those bits and transform them into an integer. */ + /* The method here takes one byte from the string, uses a mask to zero out any bit that is not part of */ + /* resulting value, casts the masked byte to an unsigned 32-bit integer, shifts those bits to the left to */ + /* place them at the indexes they're expected in the value, and then bitwise ORs these components into a */ + /* single unsigned 32-bit integer. This one-byte case does not need any shift but the remaining cases do. */ + c.codepoint = (unsigned)(str[0] & 0x7F); + } else if (c.bytes == 2) { + /* Two-byte UTF-8 characters are encoded as 110XXXXX 10XXXXXX */ + c.codepoint = ((unsigned)(str[0] & 0x1F) << 6) | (unsigned)(str[1] & 0x3F); + } else if (c.bytes == 3) { + /* Three-byte UTF-8 characters are encoded as 1110XXXX 10XXXXXX 10XXXXXX */ + c.codepoint = ((unsigned)(str[0] & 0x0F) << 12) | ((unsigned)(str[1] & 0x3F) << 6) | + ((unsigned)(str[2] & 0x3F) << 0); + } else if (c.bytes == 4) { + /* Four-byte UTF-8 characters are encoded as 11110XXX 10XXXXXX 10XXXXXX 10XXXXXX */ + c.codepoint = ((unsigned)(str[0] & 0x07) << 18) | ((unsigned)(str[1] & 0x3F) << 12) | + ((unsigned)(str[2] & 0x3F) << 6) | (unsigned)(str[3] & 0x3F); + } break; + case HOXML_ENC_UTF_16_BE: + if (c.bytes == 2) { + /* Concatenate the two bytes together to retrieve the original value */ + c.codepoint = ((unsigned)str[0] << 8) | ((unsigned)str[1] << 0); + } else if (c.bytes == 4) { + /* Four-byte UTF-16 characters are encoded as 110110XX XXXXXXXX 110111XX XXXXXXXX after first subtracting */ + /* 0x00010000 from the value. Here, that subtracted value is reconstructed and 0x00010000 is added back. */ + c.codepoint = (((unsigned)(str[0] & 0x03) << 18) | ((unsigned)str[1] << 16) | + ((unsigned)(str[2] & 0x03) << 8) | ((unsigned)str[3] << 0)) + 0x00010000; + } + break; + case HOXML_ENC_UTF_16_LE: + if (c.bytes == 2) + c.codepoint = ((unsigned)str[1] << 8) | ((unsigned)str[0] << 0); + else if (c.bytes == 4) { + c.codepoint = (((unsigned)(str[1] & 0x03) << 18) | ((unsigned)str[0] << 16) | + ((unsigned)(str[3] & 0x03) << 8) | ((unsigned)str[2] << 0)) + 0x00010000; + } + break; + } + + memcpy(&(c.encoded), str, c.bytes); /* Copy the bytes of the character from the pointed-to string into c.encoded */ + + return c; +} + +/* Encode the given character codepoint to the given character encoding */ +hoxml_character_t hoxml_encode_character(unsigned codepoint, int encoding) { + hoxml_character_t c; + c.codepoint = codepoint; + c.encoded = 0; + c.bytes = 0; + + /* This variable will make it easier to assign values to 'c.endoded' without difficult-to-read casts */ + char* str = (char*)&(c.encoded); + + switch (encoding) { + case HOXML_ENC_UNKNOWN: /* If the encoding is somehow not specified, assume UTF-8 */ + case HOXML_ENC_UTF_8: + if (codepoint <= 0x0000007F) { /* If the codepoint will fit into one byte */ + c.encoded = codepoint; + c.bytes = 1; + } else if (codepoint >= 0x000080 && codepoint <= 0x000007FF) { /* If the codepoint will fit into two bytes */ + /* For codepoints with bits XXXXXAAA AABBBBBB, we want to transform them to the form 110AAAAA 10BBBBBB. */ + /* The method here treats c.encoded as an array of unsigned, eight-bit integers. This is done to assign */ + /* bytes individually for the sake of endianness where UTF-8 is big endian. The codepoint is masked in */ + /* order to zero any bits that are not used in the byte being assigned, then shifted all the way to the */ + /* right. The prefixed "0xC0" and "0x80" bitwise ORs prepend the UTF-8 markers 110 and 10, respectively. */ + str[0] = 0xC0 | (char)((codepoint & 0x0000007C0) >> 6); /* 110AAAAAA */ + str[1] = 0x80 | (char)((codepoint & 0x0000000FF) >> 0); /* 10BBBBBB */ + c.bytes = 2; + } else if ((codepoint >= 0x00000800 && codepoint <= 0x0000D7FF) || + (codepoint >= 0x0000E000 && codepoint <= 0x0000FFFF)) { + /* For a codepoint with bits AAAABBBB BBCCCCCC we want 1110AAAA 10BBBBBB 10CCCCCC */ + str[0] = 0xE0 | (char)((codepoint & 0x0000F000) >> 12); /* 1110AAAA */ + str[1] = 0x80 | (char)((codepoint & 0x00000FC0) >> 6); /* 10BBBBBB */ + str[2] = 0x80 | (char)((codepoint & 0x0000003F) >> 0); /* 10CCCCCC */ + c.bytes = 3; + } else if (codepoint >= 0x00010000 && codepoint <= 0x0010FFFF) { + /* For a codepoint with bits XXXAAABB BBBBCCCC CCDDDDDD we want 11110AAA 10BBBBBB 10CCCCCC 10DDDDDD */ + str[0] = 0xF0 | (char)((codepoint & 0x001C0000) >> 18); /* 11110AAA */ + str[1] = 0x80 | (char)((codepoint & 0x0003F000) >> 12); /* 10BBBBBB */ + str[2] = 0x80 | (char)((codepoint & 0x00000FC0) >> 6); /* 10CCCCCC */ + str[3] = 0x80 | (char)((codepoint & 0x0000003F) >> 0); /* 10DDDDDD */ + c.bytes = 4; + } else /* If the codepoint is not valid */ + c.bytes = 0; /* Don't even try */ + break; + case HOXML_ENC_UTF_16_BE: + if (codepoint <= 0x0000D7FF || (codepoint >= 0x0000E000 && codepoint <= 0x0000FFFF)) { /* Fits in two bytes */ + str[0] = (char)((codepoint & 0x0000FF00) >> 8); + str[1] = (char) (codepoint & 0x000000FF); + c.bytes = 2; + } else if (codepoint >= 0x00010000 && codepoint <= 0x0010FFFF) { /* If the codepoint fits in four bytes */ + /* For codepoint - 0x00010000 with bits XXXXXXXX XXXXAABB BBBBBBCC DDDDDDDD we want to transform the bits */ + /* to the form 110110AA BBBBBBBB 110111CC DDDDDDDD. When decoded, as per UTF-16, 0x00010000 is added. */ + /* The prefixed "0xD8" and "0xDC" bitwise ORs prepend the UTF-16 markers 110110 and 110111, respectively. */ + codepoint -= 0x00010000; + str[0] = 0xD8 | (char)((codepoint & 0x000C0000) >> 20); /* 110110AA */ + str[1] = (char)((codepoint & 0x0003FC00) >> 18); /* BBBBBBBB */ + str[2] = 0xDC | (char)((codepoint & 0x00000300) >> 8); /* 110111CC */ + str[3] = (char)((codepoint & 0x000000FF) >> 0); /* DDDDDDDD */ + c.bytes = 4; + } else /* If the codepoint is not valid */ + c.bytes = 0; /* Don't even try */ + break; + case HOXML_ENC_UTF_16_LE: + /* UTF-16LE (Little Endian) is just like UTF-16BE (Big Endian) with the reverse endianness meaning that the */ + /* operations here are identical to those above but the indexes have been changed to reflect endianness */ + if (codepoint <= 0x0000D7FF || (codepoint >= 0x0000E000 && codepoint <= 0x0000FFFF)) { + str[1] = (char)((codepoint & 0x0000FF00) >> 8); + str[0] = (char)((codepoint & 0x000000FF) >> 0); + c.bytes = 2; + } else if (codepoint >= 0x00010000 && codepoint <= 0x0010FFFF) { + codepoint -= 0x00010000; + str[3] = 0xD8 | (char)((codepoint & 0x000C0000) >> 20); /* 110110AA */ + str[2] = (char)((codepoint & 0x0003FC00) >> 18); /* BBBBBBBB */ + str[1] = 0xDC | (char)((codepoint & 0x00000300) >> 8); /* 110111CC */ + str[0] = (char)((codepoint & 0x000000FF) >> 0); /* DDDDDDDD */ + c.bytes = 4; + } else + c.bytes = 0; + break; + } + + return c; +} + +/* Given a reference string with the given encoding, return an equivalent string encoding using ASCII */ +char* hoxml_to_ascii(const char* str, int encoding) { + /* This is only for references which have a maximum length so this static array will always be large enough */ + static char ascii[16]; + memset(ascii, 0, sizeof(ascii)); + + size_t ascii_index = 0; /* Current index in the ASCII string */ + const char* it = str; + hoxml_character_t c = hoxml_decode_character(it, 65535, encoding); + + /* While we haven't iterated up to a null terminator AND not beyond the size of the result array */ + while (c.codepoint != '\0' && ascii_index < sizeof(ascii)) { + /* Assign the decoded value of the source to the equivalent index in the result array. In practice, this is */ + /* effectively only necessary for UTF-16 content where the encoded values differ from decoded ones for */ + /* characters also included in ASCII. For UTF-8, this whole function call could be skipped. */ + ascii[ascii_index++] = c.codepoint; + + /* Iterate to the next character in the string */ + it += c.bytes; + c = hoxml_decode_character(it, 65535, encoding); + } + + return ascii; +} + +/* Get the length, in bytes not characters, of the given string with the given encoding */ +size_t hoxml_strlen(const char* str, int encoding) { + size_t length = 0; + const char* it = str; + hoxml_character_t c = hoxml_decode_character(it++, 65535, encoding); + + while (c.codepoint != '\0') { /* While we haven't iterated to a null terminator */ + length++; + c = hoxml_decode_character(it++, 65535, encoding); + } + + return length; +} + +/* Compare the given strings with the given encodings for equality with an additional parameter for case sensitivity. */ +/* The return value is 0 if the strings are not equal. All other return values mean the strings are equal. */ +int hoxml_strcmp(const char* str1, int encoding1, const char* str2, const int encoding2, int sensitivity) { + const char* it1 = str1; + const char* it2 = str2; + hoxml_character_t c1 = hoxml_decode_character(it1, 65535, encoding1); + hoxml_character_t c2 = hoxml_decode_character(it2, 65535, encoding2); + + while (c1.codepoint != '\0' && c2.codepoint != '\0') { /* While neither iterator has reached a null terminator */ + /* If, accounting for sensitivity, the charcters are not equal */ + if ((sensitivity == HOXML_CASE_INSENSITIVE && HOXML_TO_LOWER(c1.codepoint) != HOXML_TO_LOWER(c2.codepoint)) || + (sensitivity == HOXML_CASE_SENSITIVE && c1.codepoint != c2.codepoint)) { + return 0; + } + + /* Continue iterating through both strings one character at a time */ + it1 += c1.bytes; + c1 = hoxml_decode_character(it1, 65535, encoding1); + it2 += c2.bytes; + c2 = hoxml_decode_character(it2, 65535, encoding2); + } + + return c2.codepoint == '\0'; +} + +/* Search for a given string, needle, within another string, haystack. The return value is a pointer to the byte at */ +/* which the string, needle, first appears or NULL if it cannot be found. */ +const char* hoxml_strstr(const char* haystack, int haystack_encoding, const char* needle, int needle_encoding, + int sensitivity) { + const char* it_haystack = haystack; + const char* it_needle = needle; + hoxml_character_t c_haystack = hoxml_decode_character(it_haystack, 65535, haystack_encoding); + hoxml_character_t c_needle = hoxml_decode_character(it_needle, 65535, needle_encoding); + + while (c_haystack.codepoint != '\0') { /* While we haven't iterated to a null terminator */ + /* If the current character in the haystack equals the first character in the needle AND the whole needle */ + /* string follows this character */ + if (c_haystack.codepoint == c_needle.codepoint && + hoxml_strcmp(it_haystack, haystack_encoding, it_needle, needle_encoding, sensitivity) != 0) { + /* Return a pointer to the location the needle (first) appeared at */ + return it_haystack; + } + + /* Continue iterating through characters in the haystack string */ + it_haystack += c_haystack.bytes; + c_haystack = hoxml_decode_character(it_haystack, 65535, haystack_encoding); + } + + return NULL; /* The needle was not found in the haystack */ +} + +#endif /* HOXML_IMPLEMENTATION */ + +#endif /* HOXML_H */ diff --git a/include/raylib.h b/include/raylib.h new file mode 100644 index 0000000..a26b8ce --- /dev/null +++ b/include/raylib.h @@ -0,0 +1,1708 @@ +/********************************************************************************************** +* +* raylib v5.5 - A simple and easy-to-use library to enjoy videogames programming (www.raylib.com) +* +* FEATURES: +* - NO external dependencies, all required libraries included with raylib +* - Multiplatform: Windows, Linux, FreeBSD, OpenBSD, NetBSD, DragonFly, +* MacOS, Haiku, Android, Raspberry Pi, DRM native, HTML5. +* - Written in plain C code (C99) in PascalCase/camelCase notation +* - Hardware accelerated with OpenGL (1.1, 2.1, 3.3, 4.3, ES2, ES3 - choose at compile) +* - Unique OpenGL abstraction layer (usable as standalone module): [rlgl] +* - Multiple Fonts formats supported (TTF, OTF, FNT, BDF, Sprite fonts) +* - Outstanding texture formats support, including compressed formats (DXT, ETC, ASTC) +* - Full 3d support for 3d Shapes, Models, Billboards, Heightmaps and more! +* - Flexible Materials system, supporting classic maps and PBR maps +* - Animated 3D models supported (skeletal bones animation) (IQM, M3D, GLTF) +* - Shaders support, including Model shaders and Postprocessing shaders +* - Powerful math module for Vector, Matrix and Quaternion operations: [raymath] +* - Audio loading and playing with streaming support (WAV, OGG, MP3, FLAC, QOA, XM, MOD) +* - VR stereo rendering with configurable HMD device parameters +* - Bindings to multiple programming languages available! +* +* NOTES: +* - One default Font is loaded on InitWindow()->LoadFontDefault() [core, text] +* - One default Texture2D is loaded on rlglInit(), 1x1 white pixel R8G8B8A8 [rlgl] (OpenGL 3.3 or ES2) +* - One default Shader is loaded on rlglInit()->rlLoadShaderDefault() [rlgl] (OpenGL 3.3 or ES2) +* - One default RenderBatch is loaded on rlglInit()->rlLoadRenderBatch() [rlgl] (OpenGL 3.3 or ES2) +* +* DEPENDENCIES (included): +* [rcore][GLFW] rglfw (Camilla Löwy - github.com/glfw/glfw) for window/context management and input +* [rcore][RGFW] rgfw (ColleagueRiley - github.com/ColleagueRiley/RGFW) for window/context management and input +* [rlgl] glad/glad_gles2 (David Herberth - github.com/Dav1dde/glad) for OpenGL 3.3 extensions loading +* [raudio] miniaudio (David Reid - github.com/mackron/miniaudio) for audio device/context management +* +* OPTIONAL DEPENDENCIES (included): +* [rcore] msf_gif (Miles Fogle) for GIF recording +* [rcore] sinfl (Micha Mettke) for DEFLATE decompression algorithm +* [rcore] sdefl (Micha Mettke) for DEFLATE compression algorithm +* [rcore] rprand (Ramon Snatamaria) for pseudo-random numbers generation +* [rtextures] qoi (Dominic Szablewski - https://phoboslab.org) for QOI image manage +* [rtextures] stb_image (Sean Barret) for images loading (BMP, TGA, PNG, JPEG, HDR...) +* [rtextures] stb_image_write (Sean Barret) for image writing (BMP, TGA, PNG, JPG) +* [rtextures] stb_image_resize2 (Sean Barret) for image resizing algorithms +* [rtextures] stb_perlin (Sean Barret) for Perlin Noise image generation +* [rtext] stb_truetype (Sean Barret) for ttf fonts loading +* [rtext] stb_rect_pack (Sean Barret) for rectangles packing +* [rmodels] par_shapes (Philip Rideout) for parametric 3d shapes generation +* [rmodels] tinyobj_loader_c (Syoyo Fujita) for models loading (OBJ, MTL) +* [rmodels] cgltf (Johannes Kuhlmann) for models loading (glTF) +* [rmodels] m3d (bzt) for models loading (M3D, https://bztsrc.gitlab.io/model3d) +* [rmodels] vox_loader (Johann Nadalutti) for models loading (VOX) +* [raudio] dr_wav (David Reid) for WAV audio file loading +* [raudio] dr_flac (David Reid) for FLAC audio file loading +* [raudio] dr_mp3 (David Reid) for MP3 audio file loading +* [raudio] stb_vorbis (Sean Barret) for OGG audio loading +* [raudio] jar_xm (Joshua Reisenauer) for XM audio module loading +* [raudio] jar_mod (Joshua Reisenauer) for MOD audio module loading +* [raudio] qoa (Dominic Szablewski - https://phoboslab.org) for QOA audio manage +* +* +* LICENSE: zlib/libpng +* +* raylib is licensed under an unmodified zlib/libpng license, which is an OSI-certified, +* BSD-like license that allows static linking with closed source software: +* +* Copyright (c) 2013-2024 Ramon Santamaria (@raysan5) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#ifndef RAYLIB_H +#define RAYLIB_H + +#include <stdarg.h> // Required for: va_list - Only used by TraceLogCallback + +#define RAYLIB_VERSION_MAJOR 5 +#define RAYLIB_VERSION_MINOR 5 +#define RAYLIB_VERSION_PATCH 0 +#define RAYLIB_VERSION "5.5" + +// Function specifiers in case library is build/used as a shared library +// NOTE: Microsoft specifiers to tell compiler that symbols are imported/exported from a .dll +// NOTE: visibility("default") attribute makes symbols "visible" when compiled with -fvisibility=hidden +#if defined(_WIN32) + #if defined(__TINYC__) + #define __declspec(x) __attribute__((x)) + #endif + #if defined(BUILD_LIBTYPE_SHARED) + #define RLAPI __declspec(dllexport) // We are building the library as a Win32 shared library (.dll) + #elif defined(USE_LIBTYPE_SHARED) + #define RLAPI __declspec(dllimport) // We are using the library as a Win32 shared library (.dll) + #endif +#else + #if defined(BUILD_LIBTYPE_SHARED) + #define RLAPI __attribute__((visibility("default"))) // We are building as a Unix shared library (.so/.dylib) + #endif +#endif + +#ifndef RLAPI + #define RLAPI // Functions defined as 'extern' by default (implicit specifiers) +#endif + +//---------------------------------------------------------------------------------- +// Some basic Defines +//---------------------------------------------------------------------------------- +#ifndef PI + #define PI 3.14159265358979323846f +#endif +#ifndef DEG2RAD + #define DEG2RAD (PI/180.0f) +#endif +#ifndef RAD2DEG + #define RAD2DEG (180.0f/PI) +#endif + +// Allow custom memory allocators +// NOTE: Require recompiling raylib sources +#ifndef RL_MALLOC + #define RL_MALLOC(sz) malloc(sz) +#endif +#ifndef RL_CALLOC + #define RL_CALLOC(n,sz) calloc(n,sz) +#endif +#ifndef RL_REALLOC + #define RL_REALLOC(ptr,sz) realloc(ptr,sz) +#endif +#ifndef RL_FREE + #define RL_FREE(ptr) free(ptr) +#endif + +// NOTE: MSVC C++ compiler does not support compound literals (C99 feature) +// Plain structures in C++ (without constructors) can be initialized with { } +// This is called aggregate initialization (C++11 feature) +#if defined(__cplusplus) + #define CLITERAL(type) type +#else + #define CLITERAL(type) (type) +#endif + +// Some compilers (mostly macos clang) default to C++98, +// where aggregate initialization can't be used +// So, give a more clear error stating how to fix this +#if !defined(_MSC_VER) && (defined(__cplusplus) && __cplusplus < 201103L) + #error "C++11 or later is required. Add -std=c++11" +#endif + +// NOTE: We set some defines with some data types declared by raylib +// Other modules (raymath, rlgl) also require some of those types, so, +// to be able to use those other modules as standalone (not depending on raylib) +// this defines are very useful for internal check and avoid type (re)definitions +#define RL_COLOR_TYPE +#define RL_RECTANGLE_TYPE +#define RL_VECTOR2_TYPE +#define RL_VECTOR3_TYPE +#define RL_VECTOR4_TYPE +#define RL_QUATERNION_TYPE +#define RL_MATRIX_TYPE + +// Some Basic Colors +// NOTE: Custom raylib color palette for amazing visuals on WHITE background +#define LIGHTGRAY CLITERAL(Color){ 200, 200, 200, 255 } // Light Gray +#define GRAY CLITERAL(Color){ 130, 130, 130, 255 } // Gray +#define DARKGRAY CLITERAL(Color){ 80, 80, 80, 255 } // Dark Gray +#define YELLOW CLITERAL(Color){ 253, 249, 0, 255 } // Yellow +#define GOLD CLITERAL(Color){ 255, 203, 0, 255 } // Gold +#define ORANGE CLITERAL(Color){ 255, 161, 0, 255 } // Orange +#define PINK CLITERAL(Color){ 255, 109, 194, 255 } // Pink +#define RED CLITERAL(Color){ 230, 41, 55, 255 } // Red +#define MAROON CLITERAL(Color){ 190, 33, 55, 255 } // Maroon +#define GREEN CLITERAL(Color){ 0, 228, 48, 255 } // Green +#define LIME CLITERAL(Color){ 0, 158, 47, 255 } // Lime +#define DARKGREEN CLITERAL(Color){ 0, 117, 44, 255 } // Dark Green +#define SKYBLUE CLITERAL(Color){ 102, 191, 255, 255 } // Sky Blue +#define BLUE CLITERAL(Color){ 0, 121, 241, 255 } // Blue +#define DARKBLUE CLITERAL(Color){ 0, 82, 172, 255 } // Dark Blue +#define PURPLE CLITERAL(Color){ 200, 122, 255, 255 } // Purple +#define VIOLET CLITERAL(Color){ 135, 60, 190, 255 } // Violet +#define DARKPURPLE CLITERAL(Color){ 112, 31, 126, 255 } // Dark Purple +#define BEIGE CLITERAL(Color){ 211, 176, 131, 255 } // Beige +#define BROWN CLITERAL(Color){ 127, 106, 79, 255 } // Brown +#define DARKBROWN CLITERAL(Color){ 76, 63, 47, 255 } // Dark Brown + +#define WHITE CLITERAL(Color){ 255, 255, 255, 255 } // White +#define BLACK CLITERAL(Color){ 0, 0, 0, 255 } // Black +#define BLANK CLITERAL(Color){ 0, 0, 0, 0 } // Blank (Transparent) +#define MAGENTA CLITERAL(Color){ 255, 0, 255, 255 } // Magenta +#define RAYWHITE CLITERAL(Color){ 245, 245, 245, 255 } // My own White (raylib logo) + +//---------------------------------------------------------------------------------- +// Structures Definition +//---------------------------------------------------------------------------------- +// Boolean type +#if (defined(__STDC__) && __STDC_VERSION__ >= 199901L) || (defined(_MSC_VER) && _MSC_VER >= 1800) + #include <stdbool.h> +#elif !defined(__cplusplus) && !defined(bool) + typedef enum bool { false = 0, true = !false } bool; + #define RL_BOOL_TYPE +#endif + +// Vector2, 2 components +typedef struct Vector2 { + float x; // Vector x component + float y; // Vector y component +} Vector2; + +// Vector3, 3 components +typedef struct Vector3 { + float x; // Vector x component + float y; // Vector y component + float z; // Vector z component +} Vector3; + +// Vector4, 4 components +typedef struct Vector4 { + float x; // Vector x component + float y; // Vector y component + float z; // Vector z component + float w; // Vector w component +} Vector4; + +// Quaternion, 4 components (Vector4 alias) +typedef Vector4 Quaternion; + +// Matrix, 4x4 components, column major, OpenGL style, right-handed +typedef struct Matrix { + float m0, m4, m8, m12; // Matrix first row (4 components) + float m1, m5, m9, m13; // Matrix second row (4 components) + float m2, m6, m10, m14; // Matrix third row (4 components) + float m3, m7, m11, m15; // Matrix fourth row (4 components) +} Matrix; + +// Color, 4 components, R8G8B8A8 (32bit) +typedef struct Color { + unsigned char r; // Color red value + unsigned char g; // Color green value + unsigned char b; // Color blue value + unsigned char a; // Color alpha value +} Color; + +// Rectangle, 4 components +typedef struct Rectangle { + float x; // Rectangle top-left corner position x + float y; // Rectangle top-left corner position y + float width; // Rectangle width + float height; // Rectangle height +} Rectangle; + +// Image, pixel data stored in CPU memory (RAM) +typedef struct Image { + void *data; // Image raw data + int width; // Image base width + int height; // Image base height + int mipmaps; // Mipmap levels, 1 by default + int format; // Data format (PixelFormat type) +} Image; + +// Texture, tex data stored in GPU memory (VRAM) +typedef struct Texture { + unsigned int id; // OpenGL texture id + int width; // Texture base width + int height; // Texture base height + int mipmaps; // Mipmap levels, 1 by default + int format; // Data format (PixelFormat type) +} Texture; + +// Texture2D, same as Texture +typedef Texture Texture2D; + +// TextureCubemap, same as Texture +typedef Texture TextureCubemap; + +// RenderTexture, fbo for texture rendering +typedef struct RenderTexture { + unsigned int id; // OpenGL framebuffer object id + Texture texture; // Color buffer attachment texture + Texture depth; // Depth buffer attachment texture +} RenderTexture; + +// RenderTexture2D, same as RenderTexture +typedef RenderTexture RenderTexture2D; + +// NPatchInfo, n-patch layout info +typedef struct NPatchInfo { + Rectangle source; // Texture source rectangle + int left; // Left border offset + int top; // Top border offset + int right; // Right border offset + int bottom; // Bottom border offset + int layout; // Layout of the n-patch: 3x3, 1x3 or 3x1 +} NPatchInfo; + +// GlyphInfo, font characters glyphs info +typedef struct GlyphInfo { + int value; // Character value (Unicode) + int offsetX; // Character offset X when drawing + int offsetY; // Character offset Y when drawing + int advanceX; // Character advance position X + Image image; // Character image data +} GlyphInfo; + +// Font, font texture and GlyphInfo array data +typedef struct Font { + int baseSize; // Base size (default chars height) + int glyphCount; // Number of glyph characters + int glyphPadding; // Padding around the glyph characters + Texture2D texture; // Texture atlas containing the glyphs + Rectangle *recs; // Rectangles in texture for the glyphs + GlyphInfo *glyphs; // Glyphs info data +} Font; + +// Camera, defines position/orientation in 3d space +typedef struct Camera3D { + Vector3 position; // Camera position + Vector3 target; // Camera target it looks-at + Vector3 up; // Camera up vector (rotation over its axis) + float fovy; // Camera field-of-view aperture in Y (degrees) in perspective, used as near plane width in orthographic + int projection; // Camera projection: CAMERA_PERSPECTIVE or CAMERA_ORTHOGRAPHIC +} Camera3D; + +typedef Camera3D Camera; // Camera type fallback, defaults to Camera3D + +// Camera2D, defines position/orientation in 2d space +typedef struct Camera2D { + Vector2 offset; // Camera offset (displacement from target) + Vector2 target; // Camera target (rotation and zoom origin) + float rotation; // Camera rotation in degrees + float zoom; // Camera zoom (scaling), should be 1.0f by default +} Camera2D; + +// Mesh, vertex data and vao/vbo +typedef struct Mesh { + int vertexCount; // Number of vertices stored in arrays + int triangleCount; // Number of triangles stored (indexed or not) + + // Vertex attributes data + float *vertices; // Vertex position (XYZ - 3 components per vertex) (shader-location = 0) + float *texcoords; // Vertex texture coordinates (UV - 2 components per vertex) (shader-location = 1) + float *texcoords2; // Vertex texture second coordinates (UV - 2 components per vertex) (shader-location = 5) + float *normals; // Vertex normals (XYZ - 3 components per vertex) (shader-location = 2) + float *tangents; // Vertex tangents (XYZW - 4 components per vertex) (shader-location = 4) + unsigned char *colors; // Vertex colors (RGBA - 4 components per vertex) (shader-location = 3) + unsigned short *indices; // Vertex indices (in case vertex data comes indexed) + + // Animation vertex data + float *animVertices; // Animated vertex positions (after bones transformations) + float *animNormals; // Animated normals (after bones transformations) + unsigned char *boneIds; // Vertex bone ids, max 255 bone ids, up to 4 bones influence by vertex (skinning) (shader-location = 6) + float *boneWeights; // Vertex bone weight, up to 4 bones influence by vertex (skinning) (shader-location = 7) + Matrix *boneMatrices; // Bones animated transformation matrices + int boneCount; // Number of bones + + // OpenGL identifiers + unsigned int vaoId; // OpenGL Vertex Array Object id + unsigned int *vboId; // OpenGL Vertex Buffer Objects id (default vertex data) +} Mesh; + +// Shader +typedef struct Shader { + unsigned int id; // Shader program id + int *locs; // Shader locations array (RL_MAX_SHADER_LOCATIONS) +} Shader; + +// MaterialMap +typedef struct MaterialMap { + Texture2D texture; // Material map texture + Color color; // Material map color + float value; // Material map value +} MaterialMap; + +// Material, includes shader and maps +typedef struct Material { + Shader shader; // Material shader + MaterialMap *maps; // Material maps array (MAX_MATERIAL_MAPS) + float params[4]; // Material generic parameters (if required) +} Material; + +// Transform, vertex transformation data +typedef struct Transform { + Vector3 translation; // Translation + Quaternion rotation; // Rotation + Vector3 scale; // Scale +} Transform; + +// Bone, skeletal animation bone +typedef struct BoneInfo { + char name[32]; // Bone name + int parent; // Bone parent +} BoneInfo; + +// Model, meshes, materials and animation data +typedef struct Model { + Matrix transform; // Local transform matrix + + int meshCount; // Number of meshes + int materialCount; // Number of materials + Mesh *meshes; // Meshes array + Material *materials; // Materials array + int *meshMaterial; // Mesh material number + + // Animation data + int boneCount; // Number of bones + BoneInfo *bones; // Bones information (skeleton) + Transform *bindPose; // Bones base transformation (pose) +} Model; + +// ModelAnimation +typedef struct ModelAnimation { + int boneCount; // Number of bones + int frameCount; // Number of animation frames + BoneInfo *bones; // Bones information (skeleton) + Transform **framePoses; // Poses array by frame + char name[32]; // Animation name +} ModelAnimation; + +// Ray, ray for raycasting +typedef struct Ray { + Vector3 position; // Ray position (origin) + Vector3 direction; // Ray direction (normalized) +} Ray; + +// RayCollision, ray hit information +typedef struct RayCollision { + bool hit; // Did the ray hit something? + float distance; // Distance to the nearest hit + Vector3 point; // Point of the nearest hit + Vector3 normal; // Surface normal of hit +} RayCollision; + +// BoundingBox +typedef struct BoundingBox { + Vector3 min; // Minimum vertex box-corner + Vector3 max; // Maximum vertex box-corner +} BoundingBox; + +// Wave, audio wave data +typedef struct Wave { + unsigned int frameCount; // Total number of frames (considering channels) + unsigned int sampleRate; // Frequency (samples per second) + unsigned int sampleSize; // Bit depth (bits per sample): 8, 16, 32 (24 not supported) + unsigned int channels; // Number of channels (1-mono, 2-stereo, ...) + void *data; // Buffer data pointer +} Wave; + +// Opaque structs declaration +// NOTE: Actual structs are defined internally in raudio module +typedef struct rAudioBuffer rAudioBuffer; +typedef struct rAudioProcessor rAudioProcessor; + +// AudioStream, custom audio stream +typedef struct AudioStream { + rAudioBuffer *buffer; // Pointer to internal data used by the audio system + rAudioProcessor *processor; // Pointer to internal data processor, useful for audio effects + + unsigned int sampleRate; // Frequency (samples per second) + unsigned int sampleSize; // Bit depth (bits per sample): 8, 16, 32 (24 not supported) + unsigned int channels; // Number of channels (1-mono, 2-stereo, ...) +} AudioStream; + +// Sound +typedef struct Sound { + AudioStream stream; // Audio stream + unsigned int frameCount; // Total number of frames (considering channels) +} Sound; + +// Music, audio stream, anything longer than ~10 seconds should be streamed +typedef struct Music { + AudioStream stream; // Audio stream + unsigned int frameCount; // Total number of frames (considering channels) + bool looping; // Music looping enable + + int ctxType; // Type of music context (audio filetype) + void *ctxData; // Audio context data, depends on type +} Music; + +// VrDeviceInfo, Head-Mounted-Display device parameters +typedef struct VrDeviceInfo { + int hResolution; // Horizontal resolution in pixels + int vResolution; // Vertical resolution in pixels + float hScreenSize; // Horizontal size in meters + float vScreenSize; // Vertical size in meters + float eyeToScreenDistance; // Distance between eye and display in meters + float lensSeparationDistance; // Lens separation distance in meters + float interpupillaryDistance; // IPD (distance between pupils) in meters + float lensDistortionValues[4]; // Lens distortion constant parameters + float chromaAbCorrection[4]; // Chromatic aberration correction parameters +} VrDeviceInfo; + +// VrStereoConfig, VR stereo rendering configuration for simulator +typedef struct VrStereoConfig { + Matrix projection[2]; // VR projection matrices (per eye) + Matrix viewOffset[2]; // VR view offset matrices (per eye) + float leftLensCenter[2]; // VR left lens center + float rightLensCenter[2]; // VR right lens center + float leftScreenCenter[2]; // VR left screen center + float rightScreenCenter[2]; // VR right screen center + float scale[2]; // VR distortion scale + float scaleIn[2]; // VR distortion scale in +} VrStereoConfig; + +// File path list +typedef struct FilePathList { + unsigned int capacity; // Filepaths max entries + unsigned int count; // Filepaths entries count + char **paths; // Filepaths entries +} FilePathList; + +// Automation event +typedef struct AutomationEvent { + unsigned int frame; // Event frame + unsigned int type; // Event type (AutomationEventType) + int params[4]; // Event parameters (if required) +} AutomationEvent; + +// Automation event list +typedef struct AutomationEventList { + unsigned int capacity; // Events max entries (MAX_AUTOMATION_EVENTS) + unsigned int count; // Events entries count + AutomationEvent *events; // Events entries +} AutomationEventList; + +//---------------------------------------------------------------------------------- +// Enumerators Definition +//---------------------------------------------------------------------------------- +// System/Window config flags +// NOTE: Every bit registers one state (use it with bit masks) +// By default all flags are set to 0 +typedef enum { + FLAG_VSYNC_HINT = 0x00000040, // Set to try enabling V-Sync on GPU + FLAG_FULLSCREEN_MODE = 0x00000002, // Set to run program in fullscreen + FLAG_WINDOW_RESIZABLE = 0x00000004, // Set to allow resizable window + FLAG_WINDOW_UNDECORATED = 0x00000008, // Set to disable window decoration (frame and buttons) + FLAG_WINDOW_HIDDEN = 0x00000080, // Set to hide window + FLAG_WINDOW_MINIMIZED = 0x00000200, // Set to minimize window (iconify) + FLAG_WINDOW_MAXIMIZED = 0x00000400, // Set to maximize window (expanded to monitor) + FLAG_WINDOW_UNFOCUSED = 0x00000800, // Set to window non focused + FLAG_WINDOW_TOPMOST = 0x00001000, // Set to window always on top + FLAG_WINDOW_ALWAYS_RUN = 0x00000100, // Set to allow windows running while minimized + FLAG_WINDOW_TRANSPARENT = 0x00000010, // Set to allow transparent framebuffer + FLAG_WINDOW_HIGHDPI = 0x00002000, // Set to support HighDPI + FLAG_WINDOW_MOUSE_PASSTHROUGH = 0x00004000, // Set to support mouse passthrough, only supported when FLAG_WINDOW_UNDECORATED + FLAG_BORDERLESS_WINDOWED_MODE = 0x00008000, // Set to run program in borderless windowed mode + FLAG_MSAA_4X_HINT = 0x00000020, // Set to try enabling MSAA 4X + FLAG_INTERLACED_HINT = 0x00010000 // Set to try enabling interlaced video format (for V3D) +} ConfigFlags; + +// Trace log level +// NOTE: Organized by priority level +typedef enum { + LOG_ALL = 0, // Display all logs + LOG_TRACE, // Trace logging, intended for internal use only + LOG_DEBUG, // Debug logging, used for internal debugging, it should be disabled on release builds + LOG_INFO, // Info logging, used for program execution info + LOG_WARNING, // Warning logging, used on recoverable failures + LOG_ERROR, // Error logging, used on unrecoverable failures + LOG_FATAL, // Fatal logging, used to abort program: exit(EXIT_FAILURE) + LOG_NONE // Disable logging +} TraceLogLevel; + +// Keyboard keys (US keyboard layout) +// NOTE: Use GetKeyPressed() to allow redefining +// required keys for alternative layouts +typedef enum { + KEY_NULL = 0, // Key: NULL, used for no key pressed + // Alphanumeric keys + KEY_APOSTROPHE = 39, // Key: ' + KEY_COMMA = 44, // Key: , + KEY_MINUS = 45, // Key: - + KEY_PERIOD = 46, // Key: . + KEY_SLASH = 47, // Key: / + KEY_ZERO = 48, // Key: 0 + KEY_ONE = 49, // Key: 1 + KEY_TWO = 50, // Key: 2 + KEY_THREE = 51, // Key: 3 + KEY_FOUR = 52, // Key: 4 + KEY_FIVE = 53, // Key: 5 + KEY_SIX = 54, // Key: 6 + KEY_SEVEN = 55, // Key: 7 + KEY_EIGHT = 56, // Key: 8 + KEY_NINE = 57, // Key: 9 + KEY_SEMICOLON = 59, // Key: ; + KEY_EQUAL = 61, // Key: = + KEY_A = 65, // Key: A | a + KEY_B = 66, // Key: B | b + KEY_C = 67, // Key: C | c + KEY_D = 68, // Key: D | d + KEY_E = 69, // Key: E | e + KEY_F = 70, // Key: F | f + KEY_G = 71, // Key: G | g + KEY_H = 72, // Key: H | h + KEY_I = 73, // Key: I | i + KEY_J = 74, // Key: J | j + KEY_K = 75, // Key: K | k + KEY_L = 76, // Key: L | l + KEY_M = 77, // Key: M | m + KEY_N = 78, // Key: N | n + KEY_O = 79, // Key: O | o + KEY_P = 80, // Key: P | p + KEY_Q = 81, // Key: Q | q + KEY_R = 82, // Key: R | r + KEY_S = 83, // Key: S | s + KEY_T = 84, // Key: T | t + KEY_U = 85, // Key: U | u + KEY_V = 86, // Key: V | v + KEY_W = 87, // Key: W | w + KEY_X = 88, // Key: X | x + KEY_Y = 89, // Key: Y | y + KEY_Z = 90, // Key: Z | z + KEY_LEFT_BRACKET = 91, // Key: [ + KEY_BACKSLASH = 92, // Key: '\' + KEY_RIGHT_BRACKET = 93, // Key: ] + KEY_GRAVE = 96, // Key: ` + // Function keys + KEY_SPACE = 32, // Key: Space + KEY_ESCAPE = 256, // Key: Esc + KEY_ENTER = 257, // Key: Enter + KEY_TAB = 258, // Key: Tab + KEY_BACKSPACE = 259, // Key: Backspace + KEY_INSERT = 260, // Key: Ins + KEY_DELETE = 261, // Key: Del + KEY_RIGHT = 262, // Key: Cursor right + KEY_LEFT = 263, // Key: Cursor left + KEY_DOWN = 264, // Key: Cursor down + KEY_UP = 265, // Key: Cursor up + KEY_PAGE_UP = 266, // Key: Page up + KEY_PAGE_DOWN = 267, // Key: Page down + KEY_HOME = 268, // Key: Home + KEY_END = 269, // Key: End + KEY_CAPS_LOCK = 280, // Key: Caps lock + KEY_SCROLL_LOCK = 281, // Key: Scroll down + KEY_NUM_LOCK = 282, // Key: Num lock + KEY_PRINT_SCREEN = 283, // Key: Print screen + KEY_PAUSE = 284, // Key: Pause + KEY_F1 = 290, // Key: F1 + KEY_F2 = 291, // Key: F2 + KEY_F3 = 292, // Key: F3 + KEY_F4 = 293, // Key: F4 + KEY_F5 = 294, // Key: F5 + KEY_F6 = 295, // Key: F6 + KEY_F7 = 296, // Key: F7 + KEY_F8 = 297, // Key: F8 + KEY_F9 = 298, // Key: F9 + KEY_F10 = 299, // Key: F10 + KEY_F11 = 300, // Key: F11 + KEY_F12 = 301, // Key: F12 + KEY_LEFT_SHIFT = 340, // Key: Shift left + KEY_LEFT_CONTROL = 341, // Key: Control left + KEY_LEFT_ALT = 342, // Key: Alt left + KEY_LEFT_SUPER = 343, // Key: Super left + KEY_RIGHT_SHIFT = 344, // Key: Shift right + KEY_RIGHT_CONTROL = 345, // Key: Control right + KEY_RIGHT_ALT = 346, // Key: Alt right + KEY_RIGHT_SUPER = 347, // Key: Super right + KEY_KB_MENU = 348, // Key: KB menu + // Keypad keys + KEY_KP_0 = 320, // Key: Keypad 0 + KEY_KP_1 = 321, // Key: Keypad 1 + KEY_KP_2 = 322, // Key: Keypad 2 + KEY_KP_3 = 323, // Key: Keypad 3 + KEY_KP_4 = 324, // Key: Keypad 4 + KEY_KP_5 = 325, // Key: Keypad 5 + KEY_KP_6 = 326, // Key: Keypad 6 + KEY_KP_7 = 327, // Key: Keypad 7 + KEY_KP_8 = 328, // Key: Keypad 8 + KEY_KP_9 = 329, // Key: Keypad 9 + KEY_KP_DECIMAL = 330, // Key: Keypad . + KEY_KP_DIVIDE = 331, // Key: Keypad / + KEY_KP_MULTIPLY = 332, // Key: Keypad * + KEY_KP_SUBTRACT = 333, // Key: Keypad - + KEY_KP_ADD = 334, // Key: Keypad + + KEY_KP_ENTER = 335, // Key: Keypad Enter + KEY_KP_EQUAL = 336, // Key: Keypad = + // Android key buttons + KEY_BACK = 4, // Key: Android back button + KEY_MENU = 5, // Key: Android menu button + KEY_VOLUME_UP = 24, // Key: Android volume up button + KEY_VOLUME_DOWN = 25 // Key: Android volume down button +} KeyboardKey; + +// Add backwards compatibility support for deprecated names +#define MOUSE_LEFT_BUTTON MOUSE_BUTTON_LEFT +#define MOUSE_RIGHT_BUTTON MOUSE_BUTTON_RIGHT +#define MOUSE_MIDDLE_BUTTON MOUSE_BUTTON_MIDDLE + +// Mouse buttons +typedef enum { + MOUSE_BUTTON_LEFT = 0, // Mouse button left + MOUSE_BUTTON_RIGHT = 1, // Mouse button right + MOUSE_BUTTON_MIDDLE = 2, // Mouse button middle (pressed wheel) + MOUSE_BUTTON_SIDE = 3, // Mouse button side (advanced mouse device) + MOUSE_BUTTON_EXTRA = 4, // Mouse button extra (advanced mouse device) + MOUSE_BUTTON_FORWARD = 5, // Mouse button forward (advanced mouse device) + MOUSE_BUTTON_BACK = 6, // Mouse button back (advanced mouse device) +} MouseButton; + +// Mouse cursor +typedef enum { + MOUSE_CURSOR_DEFAULT = 0, // Default pointer shape + MOUSE_CURSOR_ARROW = 1, // Arrow shape + MOUSE_CURSOR_IBEAM = 2, // Text writing cursor shape + MOUSE_CURSOR_CROSSHAIR = 3, // Cross shape + MOUSE_CURSOR_POINTING_HAND = 4, // Pointing hand cursor + MOUSE_CURSOR_RESIZE_EW = 5, // Horizontal resize/move arrow shape + MOUSE_CURSOR_RESIZE_NS = 6, // Vertical resize/move arrow shape + MOUSE_CURSOR_RESIZE_NWSE = 7, // Top-left to bottom-right diagonal resize/move arrow shape + MOUSE_CURSOR_RESIZE_NESW = 8, // The top-right to bottom-left diagonal resize/move arrow shape + MOUSE_CURSOR_RESIZE_ALL = 9, // The omnidirectional resize/move cursor shape + MOUSE_CURSOR_NOT_ALLOWED = 10 // The operation-not-allowed shape +} MouseCursor; + +// Gamepad buttons +typedef enum { + GAMEPAD_BUTTON_UNKNOWN = 0, // Unknown button, just for error checking + GAMEPAD_BUTTON_LEFT_FACE_UP, // Gamepad left DPAD up button + GAMEPAD_BUTTON_LEFT_FACE_RIGHT, // Gamepad left DPAD right button + GAMEPAD_BUTTON_LEFT_FACE_DOWN, // Gamepad left DPAD down button + GAMEPAD_BUTTON_LEFT_FACE_LEFT, // Gamepad left DPAD left button + GAMEPAD_BUTTON_RIGHT_FACE_UP, // Gamepad right button up (i.e. PS3: Triangle, Xbox: Y) + GAMEPAD_BUTTON_RIGHT_FACE_RIGHT, // Gamepad right button right (i.e. PS3: Circle, Xbox: B) + GAMEPAD_BUTTON_RIGHT_FACE_DOWN, // Gamepad right button down (i.e. PS3: Cross, Xbox: A) + GAMEPAD_BUTTON_RIGHT_FACE_LEFT, // Gamepad right button left (i.e. PS3: Square, Xbox: X) + GAMEPAD_BUTTON_LEFT_TRIGGER_1, // Gamepad top/back trigger left (first), it could be a trailing button + GAMEPAD_BUTTON_LEFT_TRIGGER_2, // Gamepad top/back trigger left (second), it could be a trailing button + GAMEPAD_BUTTON_RIGHT_TRIGGER_1, // Gamepad top/back trigger right (first), it could be a trailing button + GAMEPAD_BUTTON_RIGHT_TRIGGER_2, // Gamepad top/back trigger right (second), it could be a trailing button + GAMEPAD_BUTTON_MIDDLE_LEFT, // Gamepad center buttons, left one (i.e. PS3: Select) + GAMEPAD_BUTTON_MIDDLE, // Gamepad center buttons, middle one (i.e. PS3: PS, Xbox: XBOX) + GAMEPAD_BUTTON_MIDDLE_RIGHT, // Gamepad center buttons, right one (i.e. PS3: Start) + GAMEPAD_BUTTON_LEFT_THUMB, // Gamepad joystick pressed button left + GAMEPAD_BUTTON_RIGHT_THUMB // Gamepad joystick pressed button right +} GamepadButton; + +// Gamepad axis +typedef enum { + GAMEPAD_AXIS_LEFT_X = 0, // Gamepad left stick X axis + GAMEPAD_AXIS_LEFT_Y = 1, // Gamepad left stick Y axis + GAMEPAD_AXIS_RIGHT_X = 2, // Gamepad right stick X axis + GAMEPAD_AXIS_RIGHT_Y = 3, // Gamepad right stick Y axis + GAMEPAD_AXIS_LEFT_TRIGGER = 4, // Gamepad back trigger left, pressure level: [1..-1] + GAMEPAD_AXIS_RIGHT_TRIGGER = 5 // Gamepad back trigger right, pressure level: [1..-1] +} GamepadAxis; + +// Material map index +typedef enum { + MATERIAL_MAP_ALBEDO = 0, // Albedo material (same as: MATERIAL_MAP_DIFFUSE) + MATERIAL_MAP_METALNESS, // Metalness material (same as: MATERIAL_MAP_SPECULAR) + MATERIAL_MAP_NORMAL, // Normal material + MATERIAL_MAP_ROUGHNESS, // Roughness material + MATERIAL_MAP_OCCLUSION, // Ambient occlusion material + MATERIAL_MAP_EMISSION, // Emission material + MATERIAL_MAP_HEIGHT, // Heightmap material + MATERIAL_MAP_CUBEMAP, // Cubemap material (NOTE: Uses GL_TEXTURE_CUBE_MAP) + MATERIAL_MAP_IRRADIANCE, // Irradiance material (NOTE: Uses GL_TEXTURE_CUBE_MAP) + MATERIAL_MAP_PREFILTER, // Prefilter material (NOTE: Uses GL_TEXTURE_CUBE_MAP) + MATERIAL_MAP_BRDF // Brdf material +} MaterialMapIndex; + +#define MATERIAL_MAP_DIFFUSE MATERIAL_MAP_ALBEDO +#define MATERIAL_MAP_SPECULAR MATERIAL_MAP_METALNESS + +// Shader location index +typedef enum { + SHADER_LOC_VERTEX_POSITION = 0, // Shader location: vertex attribute: position + SHADER_LOC_VERTEX_TEXCOORD01, // Shader location: vertex attribute: texcoord01 + SHADER_LOC_VERTEX_TEXCOORD02, // Shader location: vertex attribute: texcoord02 + SHADER_LOC_VERTEX_NORMAL, // Shader location: vertex attribute: normal + SHADER_LOC_VERTEX_TANGENT, // Shader location: vertex attribute: tangent + SHADER_LOC_VERTEX_COLOR, // Shader location: vertex attribute: color + SHADER_LOC_MATRIX_MVP, // Shader location: matrix uniform: model-view-projection + SHADER_LOC_MATRIX_VIEW, // Shader location: matrix uniform: view (camera transform) + SHADER_LOC_MATRIX_PROJECTION, // Shader location: matrix uniform: projection + SHADER_LOC_MATRIX_MODEL, // Shader location: matrix uniform: model (transform) + SHADER_LOC_MATRIX_NORMAL, // Shader location: matrix uniform: normal + SHADER_LOC_VECTOR_VIEW, // Shader location: vector uniform: view + SHADER_LOC_COLOR_DIFFUSE, // Shader location: vector uniform: diffuse color + SHADER_LOC_COLOR_SPECULAR, // Shader location: vector uniform: specular color + SHADER_LOC_COLOR_AMBIENT, // Shader location: vector uniform: ambient color + SHADER_LOC_MAP_ALBEDO, // Shader location: sampler2d texture: albedo (same as: SHADER_LOC_MAP_DIFFUSE) + SHADER_LOC_MAP_METALNESS, // Shader location: sampler2d texture: metalness (same as: SHADER_LOC_MAP_SPECULAR) + SHADER_LOC_MAP_NORMAL, // Shader location: sampler2d texture: normal + SHADER_LOC_MAP_ROUGHNESS, // Shader location: sampler2d texture: roughness + SHADER_LOC_MAP_OCCLUSION, // Shader location: sampler2d texture: occlusion + SHADER_LOC_MAP_EMISSION, // Shader location: sampler2d texture: emission + SHADER_LOC_MAP_HEIGHT, // Shader location: sampler2d texture: height + SHADER_LOC_MAP_CUBEMAP, // Shader location: samplerCube texture: cubemap + SHADER_LOC_MAP_IRRADIANCE, // Shader location: samplerCube texture: irradiance + SHADER_LOC_MAP_PREFILTER, // Shader location: samplerCube texture: prefilter + SHADER_LOC_MAP_BRDF, // Shader location: sampler2d texture: brdf + SHADER_LOC_VERTEX_BONEIDS, // Shader location: vertex attribute: boneIds + SHADER_LOC_VERTEX_BONEWEIGHTS, // Shader location: vertex attribute: boneWeights + SHADER_LOC_BONE_MATRICES // Shader location: array of matrices uniform: boneMatrices +} ShaderLocationIndex; + +#define SHADER_LOC_MAP_DIFFUSE SHADER_LOC_MAP_ALBEDO +#define SHADER_LOC_MAP_SPECULAR SHADER_LOC_MAP_METALNESS + +// Shader uniform data type +typedef enum { + SHADER_UNIFORM_FLOAT = 0, // Shader uniform type: float + SHADER_UNIFORM_VEC2, // Shader uniform type: vec2 (2 float) + SHADER_UNIFORM_VEC3, // Shader uniform type: vec3 (3 float) + SHADER_UNIFORM_VEC4, // Shader uniform type: vec4 (4 float) + SHADER_UNIFORM_INT, // Shader uniform type: int + SHADER_UNIFORM_IVEC2, // Shader uniform type: ivec2 (2 int) + SHADER_UNIFORM_IVEC3, // Shader uniform type: ivec3 (3 int) + SHADER_UNIFORM_IVEC4, // Shader uniform type: ivec4 (4 int) + SHADER_UNIFORM_SAMPLER2D // Shader uniform type: sampler2d +} ShaderUniformDataType; + +// Shader attribute data types +typedef enum { + SHADER_ATTRIB_FLOAT = 0, // Shader attribute type: float + SHADER_ATTRIB_VEC2, // Shader attribute type: vec2 (2 float) + SHADER_ATTRIB_VEC3, // Shader attribute type: vec3 (3 float) + SHADER_ATTRIB_VEC4 // Shader attribute type: vec4 (4 float) +} ShaderAttributeDataType; + +// Pixel formats +// NOTE: Support depends on OpenGL version and platform +typedef enum { + PIXELFORMAT_UNCOMPRESSED_GRAYSCALE = 1, // 8 bit per pixel (no alpha) + PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA, // 8*2 bpp (2 channels) + PIXELFORMAT_UNCOMPRESSED_R5G6B5, // 16 bpp + PIXELFORMAT_UNCOMPRESSED_R8G8B8, // 24 bpp + PIXELFORMAT_UNCOMPRESSED_R5G5B5A1, // 16 bpp (1 bit alpha) + PIXELFORMAT_UNCOMPRESSED_R4G4B4A4, // 16 bpp (4 bit alpha) + PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, // 32 bpp + PIXELFORMAT_UNCOMPRESSED_R32, // 32 bpp (1 channel - float) + PIXELFORMAT_UNCOMPRESSED_R32G32B32, // 32*3 bpp (3 channels - float) + PIXELFORMAT_UNCOMPRESSED_R32G32B32A32, // 32*4 bpp (4 channels - float) + PIXELFORMAT_UNCOMPRESSED_R16, // 16 bpp (1 channel - half float) + PIXELFORMAT_UNCOMPRESSED_R16G16B16, // 16*3 bpp (3 channels - half float) + PIXELFORMAT_UNCOMPRESSED_R16G16B16A16, // 16*4 bpp (4 channels - half float) + PIXELFORMAT_COMPRESSED_DXT1_RGB, // 4 bpp (no alpha) + PIXELFORMAT_COMPRESSED_DXT1_RGBA, // 4 bpp (1 bit alpha) + PIXELFORMAT_COMPRESSED_DXT3_RGBA, // 8 bpp + PIXELFORMAT_COMPRESSED_DXT5_RGBA, // 8 bpp + PIXELFORMAT_COMPRESSED_ETC1_RGB, // 4 bpp + PIXELFORMAT_COMPRESSED_ETC2_RGB, // 4 bpp + PIXELFORMAT_COMPRESSED_ETC2_EAC_RGBA, // 8 bpp + PIXELFORMAT_COMPRESSED_PVRT_RGB, // 4 bpp + PIXELFORMAT_COMPRESSED_PVRT_RGBA, // 4 bpp + PIXELFORMAT_COMPRESSED_ASTC_4x4_RGBA, // 8 bpp + PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA // 2 bpp +} PixelFormat; + +// Texture parameters: filter mode +// NOTE 1: Filtering considers mipmaps if available in the texture +// NOTE 2: Filter is accordingly set for minification and magnification +typedef enum { + TEXTURE_FILTER_POINT = 0, // No filter, just pixel approximation + TEXTURE_FILTER_BILINEAR, // Linear filtering + TEXTURE_FILTER_TRILINEAR, // Trilinear filtering (linear with mipmaps) + TEXTURE_FILTER_ANISOTROPIC_4X, // Anisotropic filtering 4x + TEXTURE_FILTER_ANISOTROPIC_8X, // Anisotropic filtering 8x + TEXTURE_FILTER_ANISOTROPIC_16X, // Anisotropic filtering 16x +} TextureFilter; + +// Texture parameters: wrap mode +typedef enum { + TEXTURE_WRAP_REPEAT = 0, // Repeats texture in tiled mode + TEXTURE_WRAP_CLAMP, // Clamps texture to edge pixel in tiled mode + TEXTURE_WRAP_MIRROR_REPEAT, // Mirrors and repeats the texture in tiled mode + TEXTURE_WRAP_MIRROR_CLAMP // Mirrors and clamps to border the texture in tiled mode +} TextureWrap; + +// Cubemap layouts +typedef enum { + CUBEMAP_LAYOUT_AUTO_DETECT = 0, // Automatically detect layout type + CUBEMAP_LAYOUT_LINE_VERTICAL, // Layout is defined by a vertical line with faces + CUBEMAP_LAYOUT_LINE_HORIZONTAL, // Layout is defined by a horizontal line with faces + CUBEMAP_LAYOUT_CROSS_THREE_BY_FOUR, // Layout is defined by a 3x4 cross with cubemap faces + CUBEMAP_LAYOUT_CROSS_FOUR_BY_THREE // Layout is defined by a 4x3 cross with cubemap faces +} CubemapLayout; + +// Font type, defines generation method +typedef enum { + FONT_DEFAULT = 0, // Default font generation, anti-aliased + FONT_BITMAP, // Bitmap font generation, no anti-aliasing + FONT_SDF // SDF font generation, requires external shader +} FontType; + +// Color blending modes (pre-defined) +typedef enum { + BLEND_ALPHA = 0, // Blend textures considering alpha (default) + BLEND_ADDITIVE, // Blend textures adding colors + BLEND_MULTIPLIED, // Blend textures multiplying colors + BLEND_ADD_COLORS, // Blend textures adding colors (alternative) + BLEND_SUBTRACT_COLORS, // Blend textures subtracting colors (alternative) + BLEND_ALPHA_PREMULTIPLY, // Blend premultiplied textures considering alpha + BLEND_CUSTOM, // Blend textures using custom src/dst factors (use rlSetBlendFactors()) + BLEND_CUSTOM_SEPARATE // Blend textures using custom rgb/alpha separate src/dst factors (use rlSetBlendFactorsSeparate()) +} BlendMode; + +// Gesture +// NOTE: Provided as bit-wise flags to enable only desired gestures +typedef enum { + GESTURE_NONE = 0, // No gesture + GESTURE_TAP = 1, // Tap gesture + GESTURE_DOUBLETAP = 2, // Double tap gesture + GESTURE_HOLD = 4, // Hold gesture + GESTURE_DRAG = 8, // Drag gesture + GESTURE_SWIPE_RIGHT = 16, // Swipe right gesture + GESTURE_SWIPE_LEFT = 32, // Swipe left gesture + GESTURE_SWIPE_UP = 64, // Swipe up gesture + GESTURE_SWIPE_DOWN = 128, // Swipe down gesture + GESTURE_PINCH_IN = 256, // Pinch in gesture + GESTURE_PINCH_OUT = 512 // Pinch out gesture +} Gesture; + +// Camera system modes +typedef enum { + CAMERA_CUSTOM = 0, // Camera custom, controlled by user (UpdateCamera() does nothing) + CAMERA_FREE, // Camera free mode + CAMERA_ORBITAL, // Camera orbital, around target, zoom supported + CAMERA_FIRST_PERSON, // Camera first person + CAMERA_THIRD_PERSON // Camera third person +} CameraMode; + +// Camera projection +typedef enum { + CAMERA_PERSPECTIVE = 0, // Perspective projection + CAMERA_ORTHOGRAPHIC // Orthographic projection +} CameraProjection; + +// N-patch layout +typedef enum { + NPATCH_NINE_PATCH = 0, // Npatch layout: 3x3 tiles + NPATCH_THREE_PATCH_VERTICAL, // Npatch layout: 1x3 tiles + NPATCH_THREE_PATCH_HORIZONTAL // Npatch layout: 3x1 tiles +} NPatchLayout; + +// Callbacks to hook some internal functions +// WARNING: These callbacks are intended for advanced users +typedef void (*TraceLogCallback)(int logLevel, const char *text, va_list args); // Logging: Redirect trace log messages +typedef unsigned char *(*LoadFileDataCallback)(const char *fileName, int *dataSize); // FileIO: Load binary data +typedef bool (*SaveFileDataCallback)(const char *fileName, void *data, int dataSize); // FileIO: Save binary data +typedef char *(*LoadFileTextCallback)(const char *fileName); // FileIO: Load text data +typedef bool (*SaveFileTextCallback)(const char *fileName, char *text); // FileIO: Save text data + +//------------------------------------------------------------------------------------ +// Global Variables Definition +//------------------------------------------------------------------------------------ +// It's lonely here... + +//------------------------------------------------------------------------------------ +// Window and Graphics Device Functions (Module: core) +//------------------------------------------------------------------------------------ + +#if defined(__cplusplus) +extern "C" { // Prevents name mangling of functions +#endif + +// Window-related functions +RLAPI void InitWindow(int width, int height, const char *title); // Initialize window and OpenGL context +RLAPI void CloseWindow(void); // Close window and unload OpenGL context +RLAPI bool WindowShouldClose(void); // Check if application should close (KEY_ESCAPE pressed or windows close icon clicked) +RLAPI bool IsWindowReady(void); // Check if window has been initialized successfully +RLAPI bool IsWindowFullscreen(void); // Check if window is currently fullscreen +RLAPI bool IsWindowHidden(void); // Check if window is currently hidden +RLAPI bool IsWindowMinimized(void); // Check if window is currently minimized +RLAPI bool IsWindowMaximized(void); // Check if window is currently maximized +RLAPI bool IsWindowFocused(void); // Check if window is currently focused +RLAPI bool IsWindowResized(void); // Check if window has been resized last frame +RLAPI bool IsWindowState(unsigned int flag); // Check if one specific window flag is enabled +RLAPI void SetWindowState(unsigned int flags); // Set window configuration state using flags +RLAPI void ClearWindowState(unsigned int flags); // Clear window configuration state flags +RLAPI void ToggleFullscreen(void); // Toggle window state: fullscreen/windowed, resizes monitor to match window resolution +RLAPI void ToggleBorderlessWindowed(void); // Toggle window state: borderless windowed, resizes window to match monitor resolution +RLAPI void MaximizeWindow(void); // Set window state: maximized, if resizable +RLAPI void MinimizeWindow(void); // Set window state: minimized, if resizable +RLAPI void RestoreWindow(void); // Set window state: not minimized/maximized +RLAPI void SetWindowIcon(Image image); // Set icon for window (single image, RGBA 32bit) +RLAPI void SetWindowIcons(Image *images, int count); // Set icon for window (multiple images, RGBA 32bit) +RLAPI void SetWindowTitle(const char *title); // Set title for window +RLAPI void SetWindowPosition(int x, int y); // Set window position on screen +RLAPI void SetWindowMonitor(int monitor); // Set monitor for the current window +RLAPI void SetWindowMinSize(int width, int height); // Set window minimum dimensions (for FLAG_WINDOW_RESIZABLE) +RLAPI void SetWindowMaxSize(int width, int height); // Set window maximum dimensions (for FLAG_WINDOW_RESIZABLE) +RLAPI void SetWindowSize(int width, int height); // Set window dimensions +RLAPI void SetWindowOpacity(float opacity); // Set window opacity [0.0f..1.0f] +RLAPI void SetWindowFocused(void); // Set window focused +RLAPI void *GetWindowHandle(void); // Get native window handle +RLAPI int GetScreenWidth(void); // Get current screen width +RLAPI int GetScreenHeight(void); // Get current screen height +RLAPI int GetRenderWidth(void); // Get current render width (it considers HiDPI) +RLAPI int GetRenderHeight(void); // Get current render height (it considers HiDPI) +RLAPI int GetMonitorCount(void); // Get number of connected monitors +RLAPI int GetCurrentMonitor(void); // Get current monitor where window is placed +RLAPI Vector2 GetMonitorPosition(int monitor); // Get specified monitor position +RLAPI int GetMonitorWidth(int monitor); // Get specified monitor width (current video mode used by monitor) +RLAPI int GetMonitorHeight(int monitor); // Get specified monitor height (current video mode used by monitor) +RLAPI int GetMonitorPhysicalWidth(int monitor); // Get specified monitor physical width in millimetres +RLAPI int GetMonitorPhysicalHeight(int monitor); // Get specified monitor physical height in millimetres +RLAPI int GetMonitorRefreshRate(int monitor); // Get specified monitor refresh rate +RLAPI Vector2 GetWindowPosition(void); // Get window position XY on monitor +RLAPI Vector2 GetWindowScaleDPI(void); // Get window scale DPI factor +RLAPI const char *GetMonitorName(int monitor); // Get the human-readable, UTF-8 encoded name of the specified monitor +RLAPI void SetClipboardText(const char *text); // Set clipboard text content +RLAPI const char *GetClipboardText(void); // Get clipboard text content +RLAPI Image GetClipboardImage(void); // Get clipboard image content +RLAPI void EnableEventWaiting(void); // Enable waiting for events on EndDrawing(), no automatic event polling +RLAPI void DisableEventWaiting(void); // Disable waiting for events on EndDrawing(), automatic events polling + +// Cursor-related functions +RLAPI void ShowCursor(void); // Shows cursor +RLAPI void HideCursor(void); // Hides cursor +RLAPI bool IsCursorHidden(void); // Check if cursor is not visible +RLAPI void EnableCursor(void); // Enables cursor (unlock cursor) +RLAPI void DisableCursor(void); // Disables cursor (lock cursor) +RLAPI bool IsCursorOnScreen(void); // Check if cursor is on the screen + +// Drawing-related functions +RLAPI void ClearBackground(Color color); // Set background color (framebuffer clear color) +RLAPI void BeginDrawing(void); // Setup canvas (framebuffer) to start drawing +RLAPI void EndDrawing(void); // End canvas drawing and swap buffers (double buffering) +RLAPI void BeginMode2D(Camera2D camera); // Begin 2D mode with custom camera (2D) +RLAPI void EndMode2D(void); // Ends 2D mode with custom camera +RLAPI void BeginMode3D(Camera3D camera); // Begin 3D mode with custom camera (3D) +RLAPI void EndMode3D(void); // Ends 3D mode and returns to default 2D orthographic mode +RLAPI void BeginTextureMode(RenderTexture2D target); // Begin drawing to render texture +RLAPI void EndTextureMode(void); // Ends drawing to render texture +RLAPI void BeginShaderMode(Shader shader); // Begin custom shader drawing +RLAPI void EndShaderMode(void); // End custom shader drawing (use default shader) +RLAPI void BeginBlendMode(int mode); // Begin blending mode (alpha, additive, multiplied, subtract, custom) +RLAPI void EndBlendMode(void); // End blending mode (reset to default: alpha blending) +RLAPI void BeginScissorMode(int x, int y, int width, int height); // Begin scissor mode (define screen area for following drawing) +RLAPI void EndScissorMode(void); // End scissor mode +RLAPI void BeginVrStereoMode(VrStereoConfig config); // Begin stereo rendering (requires VR simulator) +RLAPI void EndVrStereoMode(void); // End stereo rendering (requires VR simulator) + +// VR stereo config functions for VR simulator +RLAPI VrStereoConfig LoadVrStereoConfig(VrDeviceInfo device); // Load VR stereo config for VR simulator device parameters +RLAPI void UnloadVrStereoConfig(VrStereoConfig config); // Unload VR stereo config + +// Shader management functions +// NOTE: Shader functionality is not available on OpenGL 1.1 +RLAPI Shader LoadShader(const char *vsFileName, const char *fsFileName); // Load shader from files and bind default locations +RLAPI Shader LoadShaderFromMemory(const char *vsCode, const char *fsCode); // Load shader from code strings and bind default locations +RLAPI bool IsShaderValid(Shader shader); // Check if a shader is valid (loaded on GPU) +RLAPI int GetShaderLocation(Shader shader, const char *uniformName); // Get shader uniform location +RLAPI int GetShaderLocationAttrib(Shader shader, const char *attribName); // Get shader attribute location +RLAPI void SetShaderValue(Shader shader, int locIndex, const void *value, int uniformType); // Set shader uniform value +RLAPI void SetShaderValueV(Shader shader, int locIndex, const void *value, int uniformType, int count); // Set shader uniform value vector +RLAPI void SetShaderValueMatrix(Shader shader, int locIndex, Matrix mat); // Set shader uniform value (matrix 4x4) +RLAPI void SetShaderValueTexture(Shader shader, int locIndex, Texture2D texture); // Set shader uniform value for texture (sampler2d) +RLAPI void UnloadShader(Shader shader); // Unload shader from GPU memory (VRAM) + +// Screen-space-related functions +#define GetMouseRay GetScreenToWorldRay // Compatibility hack for previous raylib versions +RLAPI Ray GetScreenToWorldRay(Vector2 position, Camera camera); // Get a ray trace from screen position (i.e mouse) +RLAPI Ray GetScreenToWorldRayEx(Vector2 position, Camera camera, int width, int height); // Get a ray trace from screen position (i.e mouse) in a viewport +RLAPI Vector2 GetWorldToScreen(Vector3 position, Camera camera); // Get the screen space position for a 3d world space position +RLAPI Vector2 GetWorldToScreenEx(Vector3 position, Camera camera, int width, int height); // Get size position for a 3d world space position +RLAPI Vector2 GetWorldToScreen2D(Vector2 position, Camera2D camera); // Get the screen space position for a 2d camera world space position +RLAPI Vector2 GetScreenToWorld2D(Vector2 position, Camera2D camera); // Get the world space position for a 2d camera screen space position +RLAPI Matrix GetCameraMatrix(Camera camera); // Get camera transform matrix (view matrix) +RLAPI Matrix GetCameraMatrix2D(Camera2D camera); // Get camera 2d transform matrix + +// Timing-related functions +RLAPI void SetTargetFPS(int fps); // Set target FPS (maximum) +RLAPI float GetFrameTime(void); // Get time in seconds for last frame drawn (delta time) +RLAPI double GetTime(void); // Get elapsed time in seconds since InitWindow() +RLAPI int GetFPS(void); // Get current FPS + +// Custom frame control functions +// NOTE: Those functions are intended for advanced users that want full control over the frame processing +// By default EndDrawing() does this job: draws everything + SwapScreenBuffer() + manage frame timing + PollInputEvents() +// To avoid that behaviour and control frame processes manually, enable in config.h: SUPPORT_CUSTOM_FRAME_CONTROL +RLAPI void SwapScreenBuffer(void); // Swap back buffer with front buffer (screen drawing) +RLAPI void PollInputEvents(void); // Register all input events +RLAPI void WaitTime(double seconds); // Wait for some time (halt program execution) + +// Random values generation functions +RLAPI void SetRandomSeed(unsigned int seed); // Set the seed for the random number generator +RLAPI int GetRandomValue(int min, int max); // Get a random value between min and max (both included) +RLAPI int *LoadRandomSequence(unsigned int count, int min, int max); // Load random values sequence, no values repeated +RLAPI void UnloadRandomSequence(int *sequence); // Unload random values sequence + +// Misc. functions +RLAPI void TakeScreenshot(const char *fileName); // Takes a screenshot of current screen (filename extension defines format) +RLAPI void SetConfigFlags(unsigned int flags); // Setup init configuration flags (view FLAGS) +RLAPI void OpenURL(const char *url); // Open URL with default system browser (if available) + +// NOTE: Following functions implemented in module [utils] +//------------------------------------------------------------------ +RLAPI void TraceLog(int logLevel, const char *text, ...); // Show trace log messages (LOG_DEBUG, LOG_INFO, LOG_WARNING, LOG_ERROR...) +RLAPI void SetTraceLogLevel(int logLevel); // Set the current threshold (minimum) log level +RLAPI void *MemAlloc(unsigned int size); // Internal memory allocator +RLAPI void *MemRealloc(void *ptr, unsigned int size); // Internal memory reallocator +RLAPI void MemFree(void *ptr); // Internal memory free + +// Set custom callbacks +// WARNING: Callbacks setup is intended for advanced users +RLAPI void SetTraceLogCallback(TraceLogCallback callback); // Set custom trace log +RLAPI void SetLoadFileDataCallback(LoadFileDataCallback callback); // Set custom file binary data loader +RLAPI void SetSaveFileDataCallback(SaveFileDataCallback callback); // Set custom file binary data saver +RLAPI void SetLoadFileTextCallback(LoadFileTextCallback callback); // Set custom file text data loader +RLAPI void SetSaveFileTextCallback(SaveFileTextCallback callback); // Set custom file text data saver + +// Files management functions +RLAPI unsigned char *LoadFileData(const char *fileName, int *dataSize); // Load file data as byte array (read) +RLAPI void UnloadFileData(unsigned char *data); // Unload file data allocated by LoadFileData() +RLAPI bool SaveFileData(const char *fileName, void *data, int dataSize); // Save data to file from byte array (write), returns true on success +RLAPI bool ExportDataAsCode(const unsigned char *data, int dataSize, const char *fileName); // Export data to code (.h), returns true on success +RLAPI char *LoadFileText(const char *fileName); // Load text data from file (read), returns a '\0' terminated string +RLAPI void UnloadFileText(char *text); // Unload file text data allocated by LoadFileText() +RLAPI bool SaveFileText(const char *fileName, char *text); // Save text data to file (write), string must be '\0' terminated, returns true on success +//------------------------------------------------------------------ + +// File system functions +RLAPI bool FileExists(const char *fileName); // Check if file exists +RLAPI bool DirectoryExists(const char *dirPath); // Check if a directory path exists +RLAPI bool IsFileExtension(const char *fileName, const char *ext); // Check file extension (including point: .png, .wav) +RLAPI int GetFileLength(const char *fileName); // Get file length in bytes (NOTE: GetFileSize() conflicts with windows.h) +RLAPI const char *GetFileExtension(const char *fileName); // Get pointer to extension for a filename string (includes dot: '.png') +RLAPI const char *GetFileName(const char *filePath); // Get pointer to filename for a path string +RLAPI const char *GetFileNameWithoutExt(const char *filePath); // Get filename string without extension (uses static string) +RLAPI const char *GetDirectoryPath(const char *filePath); // Get full path for a given fileName with path (uses static string) +RLAPI const char *GetPrevDirectoryPath(const char *dirPath); // Get previous directory path for a given path (uses static string) +RLAPI const char *GetWorkingDirectory(void); // Get current working directory (uses static string) +RLAPI const char *GetApplicationDirectory(void); // Get the directory of the running application (uses static string) +RLAPI int MakeDirectory(const char *dirPath); // Create directories (including full path requested), returns 0 on success +RLAPI bool ChangeDirectory(const char *dir); // Change working directory, return true on success +RLAPI bool IsPathFile(const char *path); // Check if a given path is a file or a directory +RLAPI bool IsFileNameValid(const char *fileName); // Check if fileName is valid for the platform/OS +RLAPI FilePathList LoadDirectoryFiles(const char *dirPath); // Load directory filepaths +RLAPI FilePathList LoadDirectoryFilesEx(const char *basePath, const char *filter, bool scanSubdirs); // Load directory filepaths with extension filtering and recursive directory scan. Use 'DIR' in the filter string to include directories in the result +RLAPI void UnloadDirectoryFiles(FilePathList files); // Unload filepaths +RLAPI bool IsFileDropped(void); // Check if a file has been dropped into window +RLAPI FilePathList LoadDroppedFiles(void); // Load dropped filepaths +RLAPI void UnloadDroppedFiles(FilePathList files); // Unload dropped filepaths +RLAPI long GetFileModTime(const char *fileName); // Get file modification time (last write time) + +// Compression/Encoding functionality +RLAPI unsigned char *CompressData(const unsigned char *data, int dataSize, int *compDataSize); // Compress data (DEFLATE algorithm), memory must be MemFree() +RLAPI unsigned char *DecompressData(const unsigned char *compData, int compDataSize, int *dataSize); // Decompress data (DEFLATE algorithm), memory must be MemFree() +RLAPI char *EncodeDataBase64(const unsigned char *data, int dataSize, int *outputSize); // Encode data to Base64 string, memory must be MemFree() +RLAPI unsigned char *DecodeDataBase64(const unsigned char *data, int *outputSize); // Decode Base64 string data, memory must be MemFree() +RLAPI unsigned int ComputeCRC32(unsigned char *data, int dataSize); // Compute CRC32 hash code +RLAPI unsigned int *ComputeMD5(unsigned char *data, int dataSize); // Compute MD5 hash code, returns static int[4] (16 bytes) +RLAPI unsigned int *ComputeSHA1(unsigned char *data, int dataSize); // Compute SHA1 hash code, returns static int[5] (20 bytes) + + +// Automation events functionality +RLAPI AutomationEventList LoadAutomationEventList(const char *fileName); // Load automation events list from file, NULL for empty list, capacity = MAX_AUTOMATION_EVENTS +RLAPI void UnloadAutomationEventList(AutomationEventList list); // Unload automation events list from file +RLAPI bool ExportAutomationEventList(AutomationEventList list, const char *fileName); // Export automation events list as text file +RLAPI void SetAutomationEventList(AutomationEventList *list); // Set automation event list to record to +RLAPI void SetAutomationEventBaseFrame(int frame); // Set automation event internal base frame to start recording +RLAPI void StartAutomationEventRecording(void); // Start recording automation events (AutomationEventList must be set) +RLAPI void StopAutomationEventRecording(void); // Stop recording automation events +RLAPI void PlayAutomationEvent(AutomationEvent event); // Play a recorded automation event + +//------------------------------------------------------------------------------------ +// Input Handling Functions (Module: core) +//------------------------------------------------------------------------------------ + +// Input-related functions: keyboard +RLAPI bool IsKeyPressed(int key); // Check if a key has been pressed once +RLAPI bool IsKeyPressedRepeat(int key); // Check if a key has been pressed again +RLAPI bool IsKeyDown(int key); // Check if a key is being pressed +RLAPI bool IsKeyReleased(int key); // Check if a key has been released once +RLAPI bool IsKeyUp(int key); // Check if a key is NOT being pressed +RLAPI int GetKeyPressed(void); // Get key pressed (keycode), call it multiple times for keys queued, returns 0 when the queue is empty +RLAPI int GetCharPressed(void); // Get char pressed (unicode), call it multiple times for chars queued, returns 0 when the queue is empty +RLAPI void SetExitKey(int key); // Set a custom key to exit program (default is ESC) + +// Input-related functions: gamepads +RLAPI bool IsGamepadAvailable(int gamepad); // Check if a gamepad is available +RLAPI const char *GetGamepadName(int gamepad); // Get gamepad internal name id +RLAPI bool IsGamepadButtonPressed(int gamepad, int button); // Check if a gamepad button has been pressed once +RLAPI bool IsGamepadButtonDown(int gamepad, int button); // Check if a gamepad button is being pressed +RLAPI bool IsGamepadButtonReleased(int gamepad, int button); // Check if a gamepad button has been released once +RLAPI bool IsGamepadButtonUp(int gamepad, int button); // Check if a gamepad button is NOT being pressed +RLAPI int GetGamepadButtonPressed(void); // Get the last gamepad button pressed +RLAPI int GetGamepadAxisCount(int gamepad); // Get gamepad axis count for a gamepad +RLAPI float GetGamepadAxisMovement(int gamepad, int axis); // Get axis movement value for a gamepad axis +RLAPI int SetGamepadMappings(const char *mappings); // Set internal gamepad mappings (SDL_GameControllerDB) +RLAPI void SetGamepadVibration(int gamepad, float leftMotor, float rightMotor, float duration); // Set gamepad vibration for both motors (duration in seconds) + +// Input-related functions: mouse +RLAPI bool IsMouseButtonPressed(int button); // Check if a mouse button has been pressed once +RLAPI bool IsMouseButtonDown(int button); // Check if a mouse button is being pressed +RLAPI bool IsMouseButtonReleased(int button); // Check if a mouse button has been released once +RLAPI bool IsMouseButtonUp(int button); // Check if a mouse button is NOT being pressed +RLAPI int GetMouseX(void); // Get mouse position X +RLAPI int GetMouseY(void); // Get mouse position Y +RLAPI Vector2 GetMousePosition(void); // Get mouse position XY +RLAPI Vector2 GetMouseDelta(void); // Get mouse delta between frames +RLAPI void SetMousePosition(int x, int y); // Set mouse position XY +RLAPI void SetMouseOffset(int offsetX, int offsetY); // Set mouse offset +RLAPI void SetMouseScale(float scaleX, float scaleY); // Set mouse scaling +RLAPI float GetMouseWheelMove(void); // Get mouse wheel movement for X or Y, whichever is larger +RLAPI Vector2 GetMouseWheelMoveV(void); // Get mouse wheel movement for both X and Y +RLAPI void SetMouseCursor(int cursor); // Set mouse cursor + +// Input-related functions: touch +RLAPI int GetTouchX(void); // Get touch position X for touch point 0 (relative to screen size) +RLAPI int GetTouchY(void); // Get touch position Y for touch point 0 (relative to screen size) +RLAPI Vector2 GetTouchPosition(int index); // Get touch position XY for a touch point index (relative to screen size) +RLAPI int GetTouchPointId(int index); // Get touch point identifier for given index +RLAPI int GetTouchPointCount(void); // Get number of touch points + +//------------------------------------------------------------------------------------ +// Gestures and Touch Handling Functions (Module: rgestures) +//------------------------------------------------------------------------------------ +RLAPI void SetGesturesEnabled(unsigned int flags); // Enable a set of gestures using flags +RLAPI bool IsGestureDetected(unsigned int gesture); // Check if a gesture have been detected +RLAPI int GetGestureDetected(void); // Get latest detected gesture +RLAPI float GetGestureHoldDuration(void); // Get gesture hold time in seconds +RLAPI Vector2 GetGestureDragVector(void); // Get gesture drag vector +RLAPI float GetGestureDragAngle(void); // Get gesture drag angle +RLAPI Vector2 GetGesturePinchVector(void); // Get gesture pinch delta +RLAPI float GetGesturePinchAngle(void); // Get gesture pinch angle + +//------------------------------------------------------------------------------------ +// Camera System Functions (Module: rcamera) +//------------------------------------------------------------------------------------ +RLAPI void UpdateCamera(Camera *camera, int mode); // Update camera position for selected mode +RLAPI void UpdateCameraPro(Camera *camera, Vector3 movement, Vector3 rotation, float zoom); // Update camera movement/rotation + +//------------------------------------------------------------------------------------ +// Basic Shapes Drawing Functions (Module: shapes) +//------------------------------------------------------------------------------------ +// Set texture and rectangle to be used on shapes drawing +// NOTE: It can be useful when using basic shapes and one single font, +// defining a font char white rectangle would allow drawing everything in a single draw call +RLAPI void SetShapesTexture(Texture2D texture, Rectangle source); // Set texture and rectangle to be used on shapes drawing +RLAPI Texture2D GetShapesTexture(void); // Get texture that is used for shapes drawing +RLAPI Rectangle GetShapesTextureRectangle(void); // Get texture source rectangle that is used for shapes drawing + +// Basic shapes drawing functions +RLAPI void DrawPixel(int posX, int posY, Color color); // Draw a pixel using geometry [Can be slow, use with care] +RLAPI void DrawPixelV(Vector2 position, Color color); // Draw a pixel using geometry (Vector version) [Can be slow, use with care] +RLAPI void DrawLine(int startPosX, int startPosY, int endPosX, int endPosY, Color color); // Draw a line +RLAPI void DrawLineV(Vector2 startPos, Vector2 endPos, Color color); // Draw a line (using gl lines) +RLAPI void DrawLineEx(Vector2 startPos, Vector2 endPos, float thick, Color color); // Draw a line (using triangles/quads) +RLAPI void DrawLineStrip(const Vector2 *points, int pointCount, Color color); // Draw lines sequence (using gl lines) +RLAPI void DrawLineBezier(Vector2 startPos, Vector2 endPos, float thick, Color color); // Draw line segment cubic-bezier in-out interpolation +RLAPI void DrawCircle(int centerX, int centerY, float radius, Color color); // Draw a color-filled circle +RLAPI void DrawCircleSector(Vector2 center, float radius, float startAngle, float endAngle, int segments, Color color); // Draw a piece of a circle +RLAPI void DrawCircleSectorLines(Vector2 center, float radius, float startAngle, float endAngle, int segments, Color color); // Draw circle sector outline +RLAPI void DrawCircleGradient(int centerX, int centerY, float radius, Color inner, Color outer); // Draw a gradient-filled circle +RLAPI void DrawCircleV(Vector2 center, float radius, Color color); // Draw a color-filled circle (Vector version) +RLAPI void DrawCircleLines(int centerX, int centerY, float radius, Color color); // Draw circle outline +RLAPI void DrawCircleLinesV(Vector2 center, float radius, Color color); // Draw circle outline (Vector version) +RLAPI void DrawEllipse(int centerX, int centerY, float radiusH, float radiusV, Color color); // Draw ellipse +RLAPI void DrawEllipseLines(int centerX, int centerY, float radiusH, float radiusV, Color color); // Draw ellipse outline +RLAPI void DrawRing(Vector2 center, float innerRadius, float outerRadius, float startAngle, float endAngle, int segments, Color color); // Draw ring +RLAPI void DrawRingLines(Vector2 center, float innerRadius, float outerRadius, float startAngle, float endAngle, int segments, Color color); // Draw ring outline +RLAPI void DrawRectangle(int posX, int posY, int width, int height, Color color); // Draw a color-filled rectangle +RLAPI void DrawRectangleV(Vector2 position, Vector2 size, Color color); // Draw a color-filled rectangle (Vector version) +RLAPI void DrawRectangleRec(Rectangle rec, Color color); // Draw a color-filled rectangle +RLAPI void DrawRectanglePro(Rectangle rec, Vector2 origin, float rotation, Color color); // Draw a color-filled rectangle with pro parameters +RLAPI void DrawRectangleGradientV(int posX, int posY, int width, int height, Color top, Color bottom); // Draw a vertical-gradient-filled rectangle +RLAPI void DrawRectangleGradientH(int posX, int posY, int width, int height, Color left, Color right); // Draw a horizontal-gradient-filled rectangle +RLAPI void DrawRectangleGradientEx(Rectangle rec, Color topLeft, Color bottomLeft, Color topRight, Color bottomRight); // Draw a gradient-filled rectangle with custom vertex colors +RLAPI void DrawRectangleLines(int posX, int posY, int width, int height, Color color); // Draw rectangle outline +RLAPI void DrawRectangleLinesEx(Rectangle rec, float lineThick, Color color); // Draw rectangle outline with extended parameters +RLAPI void DrawRectangleRounded(Rectangle rec, float roundness, int segments, Color color); // Draw rectangle with rounded edges +RLAPI void DrawRectangleRoundedLines(Rectangle rec, float roundness, int segments, Color color); // Draw rectangle lines with rounded edges +RLAPI void DrawRectangleRoundedLinesEx(Rectangle rec, float roundness, int segments, float lineThick, Color color); // Draw rectangle with rounded edges outline +RLAPI void DrawTriangle(Vector2 v1, Vector2 v2, Vector2 v3, Color color); // Draw a color-filled triangle (vertex in counter-clockwise order!) +RLAPI void DrawTriangleLines(Vector2 v1, Vector2 v2, Vector2 v3, Color color); // Draw triangle outline (vertex in counter-clockwise order!) +RLAPI void DrawTriangleFan(const Vector2 *points, int pointCount, Color color); // Draw a triangle fan defined by points (first vertex is the center) +RLAPI void DrawTriangleStrip(const Vector2 *points, int pointCount, Color color); // Draw a triangle strip defined by points +RLAPI void DrawPoly(Vector2 center, int sides, float radius, float rotation, Color color); // Draw a regular polygon (Vector version) +RLAPI void DrawPolyLines(Vector2 center, int sides, float radius, float rotation, Color color); // Draw a polygon outline of n sides +RLAPI void DrawPolyLinesEx(Vector2 center, int sides, float radius, float rotation, float lineThick, Color color); // Draw a polygon outline of n sides with extended parameters + +// Splines drawing functions +RLAPI void DrawSplineLinear(const Vector2 *points, int pointCount, float thick, Color color); // Draw spline: Linear, minimum 2 points +RLAPI void DrawSplineBasis(const Vector2 *points, int pointCount, float thick, Color color); // Draw spline: B-Spline, minimum 4 points +RLAPI void DrawSplineCatmullRom(const Vector2 *points, int pointCount, float thick, Color color); // Draw spline: Catmull-Rom, minimum 4 points +RLAPI void DrawSplineBezierQuadratic(const Vector2 *points, int pointCount, float thick, Color color); // Draw spline: Quadratic Bezier, minimum 3 points (1 control point): [p1, c2, p3, c4...] +RLAPI void DrawSplineBezierCubic(const Vector2 *points, int pointCount, float thick, Color color); // Draw spline: Cubic Bezier, minimum 4 points (2 control points): [p1, c2, c3, p4, c5, c6...] +RLAPI void DrawSplineSegmentLinear(Vector2 p1, Vector2 p2, float thick, Color color); // Draw spline segment: Linear, 2 points +RLAPI void DrawSplineSegmentBasis(Vector2 p1, Vector2 p2, Vector2 p3, Vector2 p4, float thick, Color color); // Draw spline segment: B-Spline, 4 points +RLAPI void DrawSplineSegmentCatmullRom(Vector2 p1, Vector2 p2, Vector2 p3, Vector2 p4, float thick, Color color); // Draw spline segment: Catmull-Rom, 4 points +RLAPI void DrawSplineSegmentBezierQuadratic(Vector2 p1, Vector2 c2, Vector2 p3, float thick, Color color); // Draw spline segment: Quadratic Bezier, 2 points, 1 control point +RLAPI void DrawSplineSegmentBezierCubic(Vector2 p1, Vector2 c2, Vector2 c3, Vector2 p4, float thick, Color color); // Draw spline segment: Cubic Bezier, 2 points, 2 control points + +// Spline segment point evaluation functions, for a given t [0.0f .. 1.0f] +RLAPI Vector2 GetSplinePointLinear(Vector2 startPos, Vector2 endPos, float t); // Get (evaluate) spline point: Linear +RLAPI Vector2 GetSplinePointBasis(Vector2 p1, Vector2 p2, Vector2 p3, Vector2 p4, float t); // Get (evaluate) spline point: B-Spline +RLAPI Vector2 GetSplinePointCatmullRom(Vector2 p1, Vector2 p2, Vector2 p3, Vector2 p4, float t); // Get (evaluate) spline point: Catmull-Rom +RLAPI Vector2 GetSplinePointBezierQuad(Vector2 p1, Vector2 c2, Vector2 p3, float t); // Get (evaluate) spline point: Quadratic Bezier +RLAPI Vector2 GetSplinePointBezierCubic(Vector2 p1, Vector2 c2, Vector2 c3, Vector2 p4, float t); // Get (evaluate) spline point: Cubic Bezier + +// Basic shapes collision detection functions +RLAPI bool CheckCollisionRecs(Rectangle rec1, Rectangle rec2); // Check collision between two rectangles +RLAPI bool CheckCollisionCircles(Vector2 center1, float radius1, Vector2 center2, float radius2); // Check collision between two circles +RLAPI bool CheckCollisionCircleRec(Vector2 center, float radius, Rectangle rec); // Check collision between circle and rectangle +RLAPI bool CheckCollisionCircleLine(Vector2 center, float radius, Vector2 p1, Vector2 p2); // Check if circle collides with a line created betweeen two points [p1] and [p2] +RLAPI bool CheckCollisionPointRec(Vector2 point, Rectangle rec); // Check if point is inside rectangle +RLAPI bool CheckCollisionPointCircle(Vector2 point, Vector2 center, float radius); // Check if point is inside circle +RLAPI bool CheckCollisionPointTriangle(Vector2 point, Vector2 p1, Vector2 p2, Vector2 p3); // Check if point is inside a triangle +RLAPI bool CheckCollisionPointLine(Vector2 point, Vector2 p1, Vector2 p2, int threshold); // Check if point belongs to line created between two points [p1] and [p2] with defined margin in pixels [threshold] +RLAPI bool CheckCollisionPointPoly(Vector2 point, const Vector2 *points, int pointCount); // Check if point is within a polygon described by array of vertices +RLAPI bool CheckCollisionLines(Vector2 startPos1, Vector2 endPos1, Vector2 startPos2, Vector2 endPos2, Vector2 *collisionPoint); // Check the collision between two lines defined by two points each, returns collision point by reference +RLAPI Rectangle GetCollisionRec(Rectangle rec1, Rectangle rec2); // Get collision rectangle for two rectangles collision + +//------------------------------------------------------------------------------------ +// Texture Loading and Drawing Functions (Module: textures) +//------------------------------------------------------------------------------------ + +// Image loading functions +// NOTE: These functions do not require GPU access +RLAPI Image LoadImage(const char *fileName); // Load image from file into CPU memory (RAM) +RLAPI Image LoadImageRaw(const char *fileName, int width, int height, int format, int headerSize); // Load image from RAW file data +RLAPI Image LoadImageAnim(const char *fileName, int *frames); // Load image sequence from file (frames appended to image.data) +RLAPI Image LoadImageAnimFromMemory(const char *fileType, const unsigned char *fileData, int dataSize, int *frames); // Load image sequence from memory buffer +RLAPI Image LoadImageFromMemory(const char *fileType, const unsigned char *fileData, int dataSize); // Load image from memory buffer, fileType refers to extension: i.e. '.png' +RLAPI Image LoadImageFromTexture(Texture2D texture); // Load image from GPU texture data +RLAPI Image LoadImageFromScreen(void); // Load image from screen buffer and (screenshot) +RLAPI bool IsImageValid(Image image); // Check if an image is valid (data and parameters) +RLAPI void UnloadImage(Image image); // Unload image from CPU memory (RAM) +RLAPI bool ExportImage(Image image, const char *fileName); // Export image data to file, returns true on success +RLAPI unsigned char *ExportImageToMemory(Image image, const char *fileType, int *fileSize); // Export image to memory buffer +RLAPI bool ExportImageAsCode(Image image, const char *fileName); // Export image as code file defining an array of bytes, returns true on success + +// Image generation functions +RLAPI Image GenImageColor(int width, int height, Color color); // Generate image: plain color +RLAPI Image GenImageGradientLinear(int width, int height, int direction, Color start, Color end); // Generate image: linear gradient, direction in degrees [0..360], 0=Vertical gradient +RLAPI Image GenImageGradientRadial(int width, int height, float density, Color inner, Color outer); // Generate image: radial gradient +RLAPI Image GenImageGradientSquare(int width, int height, float density, Color inner, Color outer); // Generate image: square gradient +RLAPI Image GenImageChecked(int width, int height, int checksX, int checksY, Color col1, Color col2); // Generate image: checked +RLAPI Image GenImageWhiteNoise(int width, int height, float factor); // Generate image: white noise +RLAPI Image GenImagePerlinNoise(int width, int height, int offsetX, int offsetY, float scale); // Generate image: perlin noise +RLAPI Image GenImageCellular(int width, int height, int tileSize); // Generate image: cellular algorithm, bigger tileSize means bigger cells +RLAPI Image GenImageText(int width, int height, const char *text); // Generate image: grayscale image from text data + +// Image manipulation functions +RLAPI Image ImageCopy(Image image); // Create an image duplicate (useful for transformations) +RLAPI Image ImageFromImage(Image image, Rectangle rec); // Create an image from another image piece +RLAPI Image ImageFromChannel(Image image, int selectedChannel); // Create an image from a selected channel of another image (GRAYSCALE) +RLAPI Image ImageText(const char *text, int fontSize, Color color); // Create an image from text (default font) +RLAPI Image ImageTextEx(Font font, const char *text, float fontSize, float spacing, Color tint); // Create an image from text (custom sprite font) +RLAPI void ImageFormat(Image *image, int newFormat); // Convert image data to desired format +RLAPI void ImageToPOT(Image *image, Color fill); // Convert image to POT (power-of-two) +RLAPI void ImageCrop(Image *image, Rectangle crop); // Crop an image to a defined rectangle +RLAPI void ImageAlphaCrop(Image *image, float threshold); // Crop image depending on alpha value +RLAPI void ImageAlphaClear(Image *image, Color color, float threshold); // Clear alpha channel to desired color +RLAPI void ImageAlphaMask(Image *image, Image alphaMask); // Apply alpha mask to image +RLAPI void ImageAlphaPremultiply(Image *image); // Premultiply alpha channel +RLAPI void ImageBlurGaussian(Image *image, int blurSize); // Apply Gaussian blur using a box blur approximation +RLAPI void ImageKernelConvolution(Image *image, const float *kernel, int kernelSize); // Apply custom square convolution kernel to image +RLAPI void ImageResize(Image *image, int newWidth, int newHeight); // Resize image (Bicubic scaling algorithm) +RLAPI void ImageResizeNN(Image *image, int newWidth,int newHeight); // Resize image (Nearest-Neighbor scaling algorithm) +RLAPI void ImageResizeCanvas(Image *image, int newWidth, int newHeight, int offsetX, int offsetY, Color fill); // Resize canvas and fill with color +RLAPI void ImageMipmaps(Image *image); // Compute all mipmap levels for a provided image +RLAPI void ImageDither(Image *image, int rBpp, int gBpp, int bBpp, int aBpp); // Dither image data to 16bpp or lower (Floyd-Steinberg dithering) +RLAPI void ImageFlipVertical(Image *image); // Flip image vertically +RLAPI void ImageFlipHorizontal(Image *image); // Flip image horizontally +RLAPI void ImageRotate(Image *image, int degrees); // Rotate image by input angle in degrees (-359 to 359) +RLAPI void ImageRotateCW(Image *image); // Rotate image clockwise 90deg +RLAPI void ImageRotateCCW(Image *image); // Rotate image counter-clockwise 90deg +RLAPI void ImageColorTint(Image *image, Color color); // Modify image color: tint +RLAPI void ImageColorInvert(Image *image); // Modify image color: invert +RLAPI void ImageColorGrayscale(Image *image); // Modify image color: grayscale +RLAPI void ImageColorContrast(Image *image, float contrast); // Modify image color: contrast (-100 to 100) +RLAPI void ImageColorBrightness(Image *image, int brightness); // Modify image color: brightness (-255 to 255) +RLAPI void ImageColorReplace(Image *image, Color color, Color replace); // Modify image color: replace color +RLAPI Color *LoadImageColors(Image image); // Load color data from image as a Color array (RGBA - 32bit) +RLAPI Color *LoadImagePalette(Image image, int maxPaletteSize, int *colorCount); // Load colors palette from image as a Color array (RGBA - 32bit) +RLAPI void UnloadImageColors(Color *colors); // Unload color data loaded with LoadImageColors() +RLAPI void UnloadImagePalette(Color *colors); // Unload colors palette loaded with LoadImagePalette() +RLAPI Rectangle GetImageAlphaBorder(Image image, float threshold); // Get image alpha border rectangle +RLAPI Color GetImageColor(Image image, int x, int y); // Get image pixel color at (x, y) position + +// Image drawing functions +// NOTE: Image software-rendering functions (CPU) +RLAPI void ImageClearBackground(Image *dst, Color color); // Clear image background with given color +RLAPI void ImageDrawPixel(Image *dst, int posX, int posY, Color color); // Draw pixel within an image +RLAPI void ImageDrawPixelV(Image *dst, Vector2 position, Color color); // Draw pixel within an image (Vector version) +RLAPI void ImageDrawLine(Image *dst, int startPosX, int startPosY, int endPosX, int endPosY, Color color); // Draw line within an image +RLAPI void ImageDrawLineV(Image *dst, Vector2 start, Vector2 end, Color color); // Draw line within an image (Vector version) +RLAPI void ImageDrawLineEx(Image *dst, Vector2 start, Vector2 end, int thick, Color color); // Draw a line defining thickness within an image +RLAPI void ImageDrawCircle(Image *dst, int centerX, int centerY, int radius, Color color); // Draw a filled circle within an image +RLAPI void ImageDrawCircleV(Image *dst, Vector2 center, int radius, Color color); // Draw a filled circle within an image (Vector version) +RLAPI void ImageDrawCircleLines(Image *dst, int centerX, int centerY, int radius, Color color); // Draw circle outline within an image +RLAPI void ImageDrawCircleLinesV(Image *dst, Vector2 center, int radius, Color color); // Draw circle outline within an image (Vector version) +RLAPI void ImageDrawRectangle(Image *dst, int posX, int posY, int width, int height, Color color); // Draw rectangle within an image +RLAPI void ImageDrawRectangleV(Image *dst, Vector2 position, Vector2 size, Color color); // Draw rectangle within an image (Vector version) +RLAPI void ImageDrawRectangleRec(Image *dst, Rectangle rec, Color color); // Draw rectangle within an image +RLAPI void ImageDrawRectangleLines(Image *dst, Rectangle rec, int thick, Color color); // Draw rectangle lines within an image +RLAPI void ImageDrawTriangle(Image *dst, Vector2 v1, Vector2 v2, Vector2 v3, Color color); // Draw triangle within an image +RLAPI void ImageDrawTriangleEx(Image *dst, Vector2 v1, Vector2 v2, Vector2 v3, Color c1, Color c2, Color c3); // Draw triangle with interpolated colors within an image +RLAPI void ImageDrawTriangleLines(Image *dst, Vector2 v1, Vector2 v2, Vector2 v3, Color color); // Draw triangle outline within an image +RLAPI void ImageDrawTriangleFan(Image *dst, Vector2 *points, int pointCount, Color color); // Draw a triangle fan defined by points within an image (first vertex is the center) +RLAPI void ImageDrawTriangleStrip(Image *dst, Vector2 *points, int pointCount, Color color); // Draw a triangle strip defined by points within an image +RLAPI void ImageDraw(Image *dst, Image src, Rectangle srcRec, Rectangle dstRec, Color tint); // Draw a source image within a destination image (tint applied to source) +RLAPI void ImageDrawText(Image *dst, const char *text, int posX, int posY, int fontSize, Color color); // Draw text (using default font) within an image (destination) +RLAPI void ImageDrawTextEx(Image *dst, Font font, const char *text, Vector2 position, float fontSize, float spacing, Color tint); // Draw text (custom sprite font) within an image (destination) + +// Texture loading functions +// NOTE: These functions require GPU access +RLAPI Texture2D LoadTexture(const char *fileName); // Load texture from file into GPU memory (VRAM) +RLAPI Texture2D LoadTextureFromImage(Image image); // Load texture from image data +RLAPI TextureCubemap LoadTextureCubemap(Image image, int layout); // Load cubemap from image, multiple image cubemap layouts supported +RLAPI RenderTexture2D LoadRenderTexture(int width, int height); // Load texture for rendering (framebuffer) +RLAPI bool IsTextureValid(Texture2D texture); // Check if a texture is valid (loaded in GPU) +RLAPI void UnloadTexture(Texture2D texture); // Unload texture from GPU memory (VRAM) +RLAPI bool IsRenderTextureValid(RenderTexture2D target); // Check if a render texture is valid (loaded in GPU) +RLAPI void UnloadRenderTexture(RenderTexture2D target); // Unload render texture from GPU memory (VRAM) +RLAPI void UpdateTexture(Texture2D texture, const void *pixels); // Update GPU texture with new data +RLAPI void UpdateTextureRec(Texture2D texture, Rectangle rec, const void *pixels); // Update GPU texture rectangle with new data + +// Texture configuration functions +RLAPI void GenTextureMipmaps(Texture2D *texture); // Generate GPU mipmaps for a texture +RLAPI void SetTextureFilter(Texture2D texture, int filter); // Set texture scaling filter mode +RLAPI void SetTextureWrap(Texture2D texture, int wrap); // Set texture wrapping mode + +// Texture drawing functions +RLAPI void DrawTexture(Texture2D texture, int posX, int posY, Color tint); // Draw a Texture2D +RLAPI void DrawTextureV(Texture2D texture, Vector2 position, Color tint); // Draw a Texture2D with position defined as Vector2 +RLAPI void DrawTextureEx(Texture2D texture, Vector2 position, float rotation, float scale, Color tint); // Draw a Texture2D with extended parameters +RLAPI void DrawTextureRec(Texture2D texture, Rectangle source, Vector2 position, Color tint); // Draw a part of a texture defined by a rectangle +RLAPI void DrawTexturePro(Texture2D texture, Rectangle source, Rectangle dest, Vector2 origin, float rotation, Color tint); // Draw a part of a texture defined by a rectangle with 'pro' parameters +RLAPI void DrawTextureNPatch(Texture2D texture, NPatchInfo nPatchInfo, Rectangle dest, Vector2 origin, float rotation, Color tint); // Draws a texture (or part of it) that stretches or shrinks nicely + +// Color/pixel related functions +RLAPI bool ColorIsEqual(Color col1, Color col2); // Check if two colors are equal +RLAPI Color Fade(Color color, float alpha); // Get color with alpha applied, alpha goes from 0.0f to 1.0f +RLAPI int ColorToInt(Color color); // Get hexadecimal value for a Color (0xRRGGBBAA) +RLAPI Vector4 ColorNormalize(Color color); // Get Color normalized as float [0..1] +RLAPI Color ColorFromNormalized(Vector4 normalized); // Get Color from normalized values [0..1] +RLAPI Vector3 ColorToHSV(Color color); // Get HSV values for a Color, hue [0..360], saturation/value [0..1] +RLAPI Color ColorFromHSV(float hue, float saturation, float value); // Get a Color from HSV values, hue [0..360], saturation/value [0..1] +RLAPI Color ColorTint(Color color, Color tint); // Get color multiplied with another color +RLAPI Color ColorBrightness(Color color, float factor); // Get color with brightness correction, brightness factor goes from -1.0f to 1.0f +RLAPI Color ColorContrast(Color color, float contrast); // Get color with contrast correction, contrast values between -1.0f and 1.0f +RLAPI Color ColorAlpha(Color color, float alpha); // Get color with alpha applied, alpha goes from 0.0f to 1.0f +RLAPI Color ColorAlphaBlend(Color dst, Color src, Color tint); // Get src alpha-blended into dst color with tint +RLAPI Color ColorLerp(Color color1, Color color2, float factor); // Get color lerp interpolation between two colors, factor [0.0f..1.0f] +RLAPI Color GetColor(unsigned int hexValue); // Get Color structure from hexadecimal value +RLAPI Color GetPixelColor(void *srcPtr, int format); // Get Color from a source pixel pointer of certain format +RLAPI void SetPixelColor(void *dstPtr, Color color, int format); // Set color formatted into destination pixel pointer +RLAPI int GetPixelDataSize(int width, int height, int format); // Get pixel data size in bytes for certain format + +//------------------------------------------------------------------------------------ +// Font Loading and Text Drawing Functions (Module: text) +//------------------------------------------------------------------------------------ + +// Font loading/unloading functions +RLAPI Font GetFontDefault(void); // Get the default Font +RLAPI Font LoadFont(const char *fileName); // Load font from file into GPU memory (VRAM) +RLAPI Font LoadFontEx(const char *fileName, int fontSize, int *codepoints, int codepointCount); // Load font from file with extended parameters, use NULL for codepoints and 0 for codepointCount to load the default character set, font size is provided in pixels height +RLAPI Font LoadFontFromImage(Image image, Color key, int firstChar); // Load font from Image (XNA style) +RLAPI Font LoadFontFromMemory(const char *fileType, const unsigned char *fileData, int dataSize, int fontSize, int *codepoints, int codepointCount); // Load font from memory buffer, fileType refers to extension: i.e. '.ttf' +RLAPI bool IsFontValid(Font font); // Check if a font is valid (font data loaded, WARNING: GPU texture not checked) +RLAPI GlyphInfo *LoadFontData(const unsigned char *fileData, int dataSize, int fontSize, int *codepoints, int codepointCount, int type); // Load font data for further use +RLAPI Image GenImageFontAtlas(const GlyphInfo *glyphs, Rectangle **glyphRecs, int glyphCount, int fontSize, int padding, int packMethod); // Generate image font atlas using chars info +RLAPI void UnloadFontData(GlyphInfo *glyphs, int glyphCount); // Unload font chars info data (RAM) +RLAPI void UnloadFont(Font font); // Unload font from GPU memory (VRAM) +RLAPI bool ExportFontAsCode(Font font, const char *fileName); // Export font as code file, returns true on success + +// Text drawing functions +RLAPI void DrawFPS(int posX, int posY); // Draw current FPS +RLAPI void DrawText(const char *text, int posX, int posY, int fontSize, Color color); // Draw text (using default font) +RLAPI void DrawTextEx(Font font, const char *text, Vector2 position, float fontSize, float spacing, Color tint); // Draw text using font and additional parameters +RLAPI void DrawTextPro(Font font, const char *text, Vector2 position, Vector2 origin, float rotation, float fontSize, float spacing, Color tint); // Draw text using Font and pro parameters (rotation) +RLAPI void DrawTextCodepoint(Font font, int codepoint, Vector2 position, float fontSize, Color tint); // Draw one character (codepoint) +RLAPI void DrawTextCodepoints(Font font, const int *codepoints, int codepointCount, Vector2 position, float fontSize, float spacing, Color tint); // Draw multiple character (codepoint) + +// Text font info functions +RLAPI void SetTextLineSpacing(int spacing); // Set vertical line spacing when drawing with line-breaks +RLAPI int MeasureText(const char *text, int fontSize); // Measure string width for default font +RLAPI Vector2 MeasureTextEx(Font font, const char *text, float fontSize, float spacing); // Measure string size for Font +RLAPI int GetGlyphIndex(Font font, int codepoint); // Get glyph index position in font for a codepoint (unicode character), fallback to '?' if not found +RLAPI GlyphInfo GetGlyphInfo(Font font, int codepoint); // Get glyph font info data for a codepoint (unicode character), fallback to '?' if not found +RLAPI Rectangle GetGlyphAtlasRec(Font font, int codepoint); // Get glyph rectangle in font atlas for a codepoint (unicode character), fallback to '?' if not found + +// Text codepoints management functions (unicode characters) +RLAPI char *LoadUTF8(const int *codepoints, int length); // Load UTF-8 text encoded from codepoints array +RLAPI void UnloadUTF8(char *text); // Unload UTF-8 text encoded from codepoints array +RLAPI int *LoadCodepoints(const char *text, int *count); // Load all codepoints from a UTF-8 text string, codepoints count returned by parameter +RLAPI void UnloadCodepoints(int *codepoints); // Unload codepoints data from memory +RLAPI int GetCodepointCount(const char *text); // Get total number of codepoints in a UTF-8 encoded string +RLAPI int GetCodepoint(const char *text, int *codepointSize); // Get next codepoint in a UTF-8 encoded string, 0x3f('?') is returned on failure +RLAPI int GetCodepointNext(const char *text, int *codepointSize); // Get next codepoint in a UTF-8 encoded string, 0x3f('?') is returned on failure +RLAPI int GetCodepointPrevious(const char *text, int *codepointSize); // Get previous codepoint in a UTF-8 encoded string, 0x3f('?') is returned on failure +RLAPI const char *CodepointToUTF8(int codepoint, int *utf8Size); // Encode one codepoint into UTF-8 byte array (array length returned as parameter) + +// Text strings management functions (no UTF-8 strings, only byte chars) +// NOTE: Some strings allocate memory internally for returned strings, just be careful! +RLAPI int TextCopy(char *dst, const char *src); // Copy one string to another, returns bytes copied +RLAPI bool TextIsEqual(const char *text1, const char *text2); // Check if two text string are equal +RLAPI unsigned int TextLength(const char *text); // Get text length, checks for '\0' ending +RLAPI const char *TextFormat(const char *text, ...); // Text formatting with variables (sprintf() style) +RLAPI const char *TextSubtext(const char *text, int position, int length); // Get a piece of a text string +RLAPI char *TextReplace(const char *text, const char *replace, const char *by); // Replace text string (WARNING: memory must be freed!) +RLAPI char *TextInsert(const char *text, const char *insert, int position); // Insert text in a position (WARNING: memory must be freed!) +RLAPI const char *TextJoin(const char **textList, int count, const char *delimiter); // Join text strings with delimiter +RLAPI const char **TextSplit(const char *text, char delimiter, int *count); // Split text into multiple strings +RLAPI void TextAppend(char *text, const char *append, int *position); // Append text at specific position and move cursor! +RLAPI int TextFindIndex(const char *text, const char *find); // Find first text occurrence within a string +RLAPI const char *TextToUpper(const char *text); // Get upper case version of provided string +RLAPI const char *TextToLower(const char *text); // Get lower case version of provided string +RLAPI const char *TextToPascal(const char *text); // Get Pascal case notation version of provided string +RLAPI const char *TextToSnake(const char *text); // Get Snake case notation version of provided string +RLAPI const char *TextToCamel(const char *text); // Get Camel case notation version of provided string + +RLAPI int TextToInteger(const char *text); // Get integer value from text (negative values not supported) +RLAPI float TextToFloat(const char *text); // Get float value from text (negative values not supported) + +//------------------------------------------------------------------------------------ +// Basic 3d Shapes Drawing Functions (Module: models) +//------------------------------------------------------------------------------------ + +// Basic geometric 3D shapes drawing functions +RLAPI void DrawLine3D(Vector3 startPos, Vector3 endPos, Color color); // Draw a line in 3D world space +RLAPI void DrawPoint3D(Vector3 position, Color color); // Draw a point in 3D space, actually a small line +RLAPI void DrawCircle3D(Vector3 center, float radius, Vector3 rotationAxis, float rotationAngle, Color color); // Draw a circle in 3D world space +RLAPI void DrawTriangle3D(Vector3 v1, Vector3 v2, Vector3 v3, Color color); // Draw a color-filled triangle (vertex in counter-clockwise order!) +RLAPI void DrawTriangleStrip3D(const Vector3 *points, int pointCount, Color color); // Draw a triangle strip defined by points +RLAPI void DrawCube(Vector3 position, float width, float height, float length, Color color); // Draw cube +RLAPI void DrawCubeV(Vector3 position, Vector3 size, Color color); // Draw cube (Vector version) +RLAPI void DrawCubeWires(Vector3 position, float width, float height, float length, Color color); // Draw cube wires +RLAPI void DrawCubeWiresV(Vector3 position, Vector3 size, Color color); // Draw cube wires (Vector version) +RLAPI void DrawSphere(Vector3 centerPos, float radius, Color color); // Draw sphere +RLAPI void DrawSphereEx(Vector3 centerPos, float radius, int rings, int slices, Color color); // Draw sphere with extended parameters +RLAPI void DrawSphereWires(Vector3 centerPos, float radius, int rings, int slices, Color color); // Draw sphere wires +RLAPI void DrawCylinder(Vector3 position, float radiusTop, float radiusBottom, float height, int slices, Color color); // Draw a cylinder/cone +RLAPI void DrawCylinderEx(Vector3 startPos, Vector3 endPos, float startRadius, float endRadius, int sides, Color color); // Draw a cylinder with base at startPos and top at endPos +RLAPI void DrawCylinderWires(Vector3 position, float radiusTop, float radiusBottom, float height, int slices, Color color); // Draw a cylinder/cone wires +RLAPI void DrawCylinderWiresEx(Vector3 startPos, Vector3 endPos, float startRadius, float endRadius, int sides, Color color); // Draw a cylinder wires with base at startPos and top at endPos +RLAPI void DrawCapsule(Vector3 startPos, Vector3 endPos, float radius, int slices, int rings, Color color); // Draw a capsule with the center of its sphere caps at startPos and endPos +RLAPI void DrawCapsuleWires(Vector3 startPos, Vector3 endPos, float radius, int slices, int rings, Color color); // Draw capsule wireframe with the center of its sphere caps at startPos and endPos +RLAPI void DrawPlane(Vector3 centerPos, Vector2 size, Color color); // Draw a plane XZ +RLAPI void DrawRay(Ray ray, Color color); // Draw a ray line +RLAPI void DrawGrid(int slices, float spacing); // Draw a grid (centered at (0, 0, 0)) + +//------------------------------------------------------------------------------------ +// Model 3d Loading and Drawing Functions (Module: models) +//------------------------------------------------------------------------------------ + +// Model management functions +RLAPI Model LoadModel(const char *fileName); // Load model from files (meshes and materials) +RLAPI Model LoadModelFromMesh(Mesh mesh); // Load model from generated mesh (default material) +RLAPI bool IsModelValid(Model model); // Check if a model is valid (loaded in GPU, VAO/VBOs) +RLAPI void UnloadModel(Model model); // Unload model (including meshes) from memory (RAM and/or VRAM) +RLAPI BoundingBox GetModelBoundingBox(Model model); // Compute model bounding box limits (considers all meshes) + +// Model drawing functions +RLAPI void DrawModel(Model model, Vector3 position, float scale, Color tint); // Draw a model (with texture if set) +RLAPI void DrawModelEx(Model model, Vector3 position, Vector3 rotationAxis, float rotationAngle, Vector3 scale, Color tint); // Draw a model with extended parameters +RLAPI void DrawModelWires(Model model, Vector3 position, float scale, Color tint); // Draw a model wires (with texture if set) +RLAPI void DrawModelWiresEx(Model model, Vector3 position, Vector3 rotationAxis, float rotationAngle, Vector3 scale, Color tint); // Draw a model wires (with texture if set) with extended parameters +RLAPI void DrawModelPoints(Model model, Vector3 position, float scale, Color tint); // Draw a model as points +RLAPI void DrawModelPointsEx(Model model, Vector3 position, Vector3 rotationAxis, float rotationAngle, Vector3 scale, Color tint); // Draw a model as points with extended parameters +RLAPI void DrawBoundingBox(BoundingBox box, Color color); // Draw bounding box (wires) +RLAPI void DrawBillboard(Camera camera, Texture2D texture, Vector3 position, float scale, Color tint); // Draw a billboard texture +RLAPI void DrawBillboardRec(Camera camera, Texture2D texture, Rectangle source, Vector3 position, Vector2 size, Color tint); // Draw a billboard texture defined by source +RLAPI void DrawBillboardPro(Camera camera, Texture2D texture, Rectangle source, Vector3 position, Vector3 up, Vector2 size, Vector2 origin, float rotation, Color tint); // Draw a billboard texture defined by source and rotation + +// Mesh management functions +RLAPI void UploadMesh(Mesh *mesh, bool dynamic); // Upload mesh vertex data in GPU and provide VAO/VBO ids +RLAPI void UpdateMeshBuffer(Mesh mesh, int index, const void *data, int dataSize, int offset); // Update mesh vertex data in GPU for a specific buffer index +RLAPI void UnloadMesh(Mesh mesh); // Unload mesh data from CPU and GPU +RLAPI void DrawMesh(Mesh mesh, Material material, Matrix transform); // Draw a 3d mesh with material and transform +RLAPI void DrawMeshInstanced(Mesh mesh, Material material, const Matrix *transforms, int instances); // Draw multiple mesh instances with material and different transforms +RLAPI BoundingBox GetMeshBoundingBox(Mesh mesh); // Compute mesh bounding box limits +RLAPI void GenMeshTangents(Mesh *mesh); // Compute mesh tangents +RLAPI bool ExportMesh(Mesh mesh, const char *fileName); // Export mesh data to file, returns true on success +RLAPI bool ExportMeshAsCode(Mesh mesh, const char *fileName); // Export mesh as code file (.h) defining multiple arrays of vertex attributes + +// Mesh generation functions +RLAPI Mesh GenMeshPoly(int sides, float radius); // Generate polygonal mesh +RLAPI Mesh GenMeshPlane(float width, float length, int resX, int resZ); // Generate plane mesh (with subdivisions) +RLAPI Mesh GenMeshCube(float width, float height, float length); // Generate cuboid mesh +RLAPI Mesh GenMeshSphere(float radius, int rings, int slices); // Generate sphere mesh (standard sphere) +RLAPI Mesh GenMeshHemiSphere(float radius, int rings, int slices); // Generate half-sphere mesh (no bottom cap) +RLAPI Mesh GenMeshCylinder(float radius, float height, int slices); // Generate cylinder mesh +RLAPI Mesh GenMeshCone(float radius, float height, int slices); // Generate cone/pyramid mesh +RLAPI Mesh GenMeshTorus(float radius, float size, int radSeg, int sides); // Generate torus mesh +RLAPI Mesh GenMeshKnot(float radius, float size, int radSeg, int sides); // Generate trefoil knot mesh +RLAPI Mesh GenMeshHeightmap(Image heightmap, Vector3 size); // Generate heightmap mesh from image data +RLAPI Mesh GenMeshCubicmap(Image cubicmap, Vector3 cubeSize); // Generate cubes-based map mesh from image data + +// Material loading/unloading functions +RLAPI Material *LoadMaterials(const char *fileName, int *materialCount); // Load materials from model file +RLAPI Material LoadMaterialDefault(void); // Load default material (Supports: DIFFUSE, SPECULAR, NORMAL maps) +RLAPI bool IsMaterialValid(Material material); // Check if a material is valid (shader assigned, map textures loaded in GPU) +RLAPI void UnloadMaterial(Material material); // Unload material from GPU memory (VRAM) +RLAPI void SetMaterialTexture(Material *material, int mapType, Texture2D texture); // Set texture for a material map type (MATERIAL_MAP_DIFFUSE, MATERIAL_MAP_SPECULAR...) +RLAPI void SetModelMeshMaterial(Model *model, int meshId, int materialId); // Set material for a mesh + +// Model animations loading/unloading functions +RLAPI ModelAnimation *LoadModelAnimations(const char *fileName, int *animCount); // Load model animations from file +RLAPI void UpdateModelAnimation(Model model, ModelAnimation anim, int frame); // Update model animation pose (CPU) +RLAPI void UpdateModelAnimationBones(Model model, ModelAnimation anim, int frame); // Update model animation mesh bone matrices (GPU skinning) +RLAPI void UnloadModelAnimation(ModelAnimation anim); // Unload animation data +RLAPI void UnloadModelAnimations(ModelAnimation *animations, int animCount); // Unload animation array data +RLAPI bool IsModelAnimationValid(Model model, ModelAnimation anim); // Check model animation skeleton match + +// Collision detection functions +RLAPI bool CheckCollisionSpheres(Vector3 center1, float radius1, Vector3 center2, float radius2); // Check collision between two spheres +RLAPI bool CheckCollisionBoxes(BoundingBox box1, BoundingBox box2); // Check collision between two bounding boxes +RLAPI bool CheckCollisionBoxSphere(BoundingBox box, Vector3 center, float radius); // Check collision between box and sphere +RLAPI RayCollision GetRayCollisionSphere(Ray ray, Vector3 center, float radius); // Get collision info between ray and sphere +RLAPI RayCollision GetRayCollisionBox(Ray ray, BoundingBox box); // Get collision info between ray and box +RLAPI RayCollision GetRayCollisionMesh(Ray ray, Mesh mesh, Matrix transform); // Get collision info between ray and mesh +RLAPI RayCollision GetRayCollisionTriangle(Ray ray, Vector3 p1, Vector3 p2, Vector3 p3); // Get collision info between ray and triangle +RLAPI RayCollision GetRayCollisionQuad(Ray ray, Vector3 p1, Vector3 p2, Vector3 p3, Vector3 p4); // Get collision info between ray and quad + +//------------------------------------------------------------------------------------ +// Audio Loading and Playing Functions (Module: audio) +//------------------------------------------------------------------------------------ +typedef void (*AudioCallback)(void *bufferData, unsigned int frames); + +// Audio device management functions +RLAPI void InitAudioDevice(void); // Initialize audio device and context +RLAPI void CloseAudioDevice(void); // Close the audio device and context +RLAPI bool IsAudioDeviceReady(void); // Check if audio device has been initialized successfully +RLAPI void SetMasterVolume(float volume); // Set master volume (listener) +RLAPI float GetMasterVolume(void); // Get master volume (listener) + +// Wave/Sound loading/unloading functions +RLAPI Wave LoadWave(const char *fileName); // Load wave data from file +RLAPI Wave LoadWaveFromMemory(const char *fileType, const unsigned char *fileData, int dataSize); // Load wave from memory buffer, fileType refers to extension: i.e. '.wav' +RLAPI bool IsWaveValid(Wave wave); // Checks if wave data is valid (data loaded and parameters) +RLAPI Sound LoadSound(const char *fileName); // Load sound from file +RLAPI Sound LoadSoundFromWave(Wave wave); // Load sound from wave data +RLAPI Sound LoadSoundAlias(Sound source); // Create a new sound that shares the same sample data as the source sound, does not own the sound data +RLAPI bool IsSoundValid(Sound sound); // Checks if a sound is valid (data loaded and buffers initialized) +RLAPI void UpdateSound(Sound sound, const void *data, int sampleCount); // Update sound buffer with new data +RLAPI void UnloadWave(Wave wave); // Unload wave data +RLAPI void UnloadSound(Sound sound); // Unload sound +RLAPI void UnloadSoundAlias(Sound alias); // Unload a sound alias (does not deallocate sample data) +RLAPI bool ExportWave(Wave wave, const char *fileName); // Export wave data to file, returns true on success +RLAPI bool ExportWaveAsCode(Wave wave, const char *fileName); // Export wave sample data to code (.h), returns true on success + +// Wave/Sound management functions +RLAPI void PlaySound(Sound sound); // Play a sound +RLAPI void StopSound(Sound sound); // Stop playing a sound +RLAPI void PauseSound(Sound sound); // Pause a sound +RLAPI void ResumeSound(Sound sound); // Resume a paused sound +RLAPI bool IsSoundPlaying(Sound sound); // Check if a sound is currently playing +RLAPI void SetSoundVolume(Sound sound, float volume); // Set volume for a sound (1.0 is max level) +RLAPI void SetSoundPitch(Sound sound, float pitch); // Set pitch for a sound (1.0 is base level) +RLAPI void SetSoundPan(Sound sound, float pan); // Set pan for a sound (0.5 is center) +RLAPI Wave WaveCopy(Wave wave); // Copy a wave to a new wave +RLAPI void WaveCrop(Wave *wave, int initFrame, int finalFrame); // Crop a wave to defined frames range +RLAPI void WaveFormat(Wave *wave, int sampleRate, int sampleSize, int channels); // Convert wave data to desired format +RLAPI float *LoadWaveSamples(Wave wave); // Load samples data from wave as a 32bit float data array +RLAPI void UnloadWaveSamples(float *samples); // Unload samples data loaded with LoadWaveSamples() + +// Music management functions +RLAPI Music LoadMusicStream(const char *fileName); // Load music stream from file +RLAPI Music LoadMusicStreamFromMemory(const char *fileType, const unsigned char *data, int dataSize); // Load music stream from data +RLAPI bool IsMusicValid(Music music); // Checks if a music stream is valid (context and buffers initialized) +RLAPI void UnloadMusicStream(Music music); // Unload music stream +RLAPI void PlayMusicStream(Music music); // Start music playing +RLAPI bool IsMusicStreamPlaying(Music music); // Check if music is playing +RLAPI void UpdateMusicStream(Music music); // Updates buffers for music streaming +RLAPI void StopMusicStream(Music music); // Stop music playing +RLAPI void PauseMusicStream(Music music); // Pause music playing +RLAPI void ResumeMusicStream(Music music); // Resume playing paused music +RLAPI void SeekMusicStream(Music music, float position); // Seek music to a position (in seconds) +RLAPI void SetMusicVolume(Music music, float volume); // Set volume for music (1.0 is max level) +RLAPI void SetMusicPitch(Music music, float pitch); // Set pitch for a music (1.0 is base level) +RLAPI void SetMusicPan(Music music, float pan); // Set pan for a music (0.5 is center) +RLAPI float GetMusicTimeLength(Music music); // Get music time length (in seconds) +RLAPI float GetMusicTimePlayed(Music music); // Get current music time played (in seconds) + +// AudioStream management functions +RLAPI AudioStream LoadAudioStream(unsigned int sampleRate, unsigned int sampleSize, unsigned int channels); // Load audio stream (to stream raw audio pcm data) +RLAPI bool IsAudioStreamValid(AudioStream stream); // Checks if an audio stream is valid (buffers initialized) +RLAPI void UnloadAudioStream(AudioStream stream); // Unload audio stream and free memory +RLAPI void UpdateAudioStream(AudioStream stream, const void *data, int frameCount); // Update audio stream buffers with data +RLAPI bool IsAudioStreamProcessed(AudioStream stream); // Check if any audio stream buffers requires refill +RLAPI void PlayAudioStream(AudioStream stream); // Play audio stream +RLAPI void PauseAudioStream(AudioStream stream); // Pause audio stream +RLAPI void ResumeAudioStream(AudioStream stream); // Resume audio stream +RLAPI bool IsAudioStreamPlaying(AudioStream stream); // Check if audio stream is playing +RLAPI void StopAudioStream(AudioStream stream); // Stop audio stream +RLAPI void SetAudioStreamVolume(AudioStream stream, float volume); // Set volume for audio stream (1.0 is max level) +RLAPI void SetAudioStreamPitch(AudioStream stream, float pitch); // Set pitch for audio stream (1.0 is base level) +RLAPI void SetAudioStreamPan(AudioStream stream, float pan); // Set pan for audio stream (0.5 is centered) +RLAPI void SetAudioStreamBufferSizeDefault(int size); // Default size for new audio streams +RLAPI void SetAudioStreamCallback(AudioStream stream, AudioCallback callback); // Audio thread callback to request new data + +RLAPI void AttachAudioStreamProcessor(AudioStream stream, AudioCallback processor); // Attach audio stream processor to stream, receives the samples as 'float' +RLAPI void DetachAudioStreamProcessor(AudioStream stream, AudioCallback processor); // Detach audio stream processor from stream + +RLAPI void AttachAudioMixedProcessor(AudioCallback processor); // Attach audio stream processor to the entire audio pipeline, receives the samples as 'float' +RLAPI void DetachAudioMixedProcessor(AudioCallback processor); // Detach audio stream processor from the entire audio pipeline + +#if defined(__cplusplus) +} +#endif + +#endif // RAYLIB_H diff --git a/include/raymath.h b/include/raymath.h new file mode 100644 index 0000000..e522113 --- /dev/null +++ b/include/raymath.h @@ -0,0 +1,2941 @@ +/********************************************************************************************** +* +* raymath v2.0 - Math functions to work with Vector2, Vector3, Matrix and Quaternions +* +* CONVENTIONS: +* - Matrix structure is defined as row-major (memory layout) but parameters naming AND all +* math operations performed by the library consider the structure as it was column-major +* It is like transposed versions of the matrices are used for all the maths +* It benefits some functions making them cache-friendly and also avoids matrix +* transpositions sometimes required by OpenGL +* Example: In memory order, row0 is [m0 m4 m8 m12] but in semantic math row0 is [m0 m1 m2 m3] +* - Functions are always self-contained, no function use another raymath function inside, +* required code is directly re-implemented inside +* - Functions input parameters are always received by value (2 unavoidable exceptions) +* - Functions use always a "result" variable for return (except C++ operators) +* - Functions are always defined inline +* - Angles are always in radians (DEG2RAD/RAD2DEG macros provided for convenience) +* - No compound literals used to make sure libray is compatible with C++ +* +* CONFIGURATION: +* #define RAYMATH_IMPLEMENTATION +* Generates the implementation of the library into the included file. +* If not defined, the library is in header only mode and can be included in other headers +* or source files without problems. But only ONE file should hold the implementation. +* +* #define RAYMATH_STATIC_INLINE +* Define static inline functions code, so #include header suffices for use. +* This may use up lots of memory. +* +* #define RAYMATH_DISABLE_CPP_OPERATORS +* Disables C++ operator overloads for raymath types. +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2015-2024 Ramon Santamaria (@raysan5) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#ifndef RAYMATH_H +#define RAYMATH_H + +#if defined(RAYMATH_IMPLEMENTATION) && defined(RAYMATH_STATIC_INLINE) + #error "Specifying both RAYMATH_IMPLEMENTATION and RAYMATH_STATIC_INLINE is contradictory" +#endif + +// Function specifiers definition +#if defined(RAYMATH_IMPLEMENTATION) + #if defined(_WIN32) && defined(BUILD_LIBTYPE_SHARED) + #define RMAPI __declspec(dllexport) extern inline // We are building raylib as a Win32 shared library (.dll) + #elif defined(BUILD_LIBTYPE_SHARED) + #define RMAPI __attribute__((visibility("default"))) // We are building raylib as a Unix shared library (.so/.dylib) + #elif defined(_WIN32) && defined(USE_LIBTYPE_SHARED) + #define RMAPI __declspec(dllimport) // We are using raylib as a Win32 shared library (.dll) + #else + #define RMAPI extern inline // Provide external definition + #endif +#elif defined(RAYMATH_STATIC_INLINE) + #define RMAPI static inline // Functions may be inlined, no external out-of-line definition +#else + #if defined(__TINYC__) + #define RMAPI static inline // plain inline not supported by tinycc (See issue #435) + #else + #define RMAPI inline // Functions may be inlined or external definition used + #endif +#endif + + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +#ifndef PI + #define PI 3.14159265358979323846f +#endif + +#ifndef EPSILON + #define EPSILON 0.000001f +#endif + +#ifndef DEG2RAD + #define DEG2RAD (PI/180.0f) +#endif + +#ifndef RAD2DEG + #define RAD2DEG (180.0f/PI) +#endif + +// Get float vector for Matrix +#ifndef MatrixToFloat + #define MatrixToFloat(mat) (MatrixToFloatV(mat).v) +#endif + +// Get float vector for Vector3 +#ifndef Vector3ToFloat + #define Vector3ToFloat(vec) (Vector3ToFloatV(vec).v) +#endif + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- +#if !defined(RL_VECTOR2_TYPE) +// Vector2 type +typedef struct Vector2 { + float x; + float y; +} Vector2; +#define RL_VECTOR2_TYPE +#endif + +#if !defined(RL_VECTOR3_TYPE) +// Vector3 type +typedef struct Vector3 { + float x; + float y; + float z; +} Vector3; +#define RL_VECTOR3_TYPE +#endif + +#if !defined(RL_VECTOR4_TYPE) +// Vector4 type +typedef struct Vector4 { + float x; + float y; + float z; + float w; +} Vector4; +#define RL_VECTOR4_TYPE +#endif + +#if !defined(RL_QUATERNION_TYPE) +// Quaternion type +typedef Vector4 Quaternion; +#define RL_QUATERNION_TYPE +#endif + +#if !defined(RL_MATRIX_TYPE) +// Matrix type (OpenGL style 4x4 - right handed, column major) +typedef struct Matrix { + float m0, m4, m8, m12; // Matrix first row (4 components) + float m1, m5, m9, m13; // Matrix second row (4 components) + float m2, m6, m10, m14; // Matrix third row (4 components) + float m3, m7, m11, m15; // Matrix fourth row (4 components) +} Matrix; +#define RL_MATRIX_TYPE +#endif + +// NOTE: Helper types to be used instead of array return types for *ToFloat functions +typedef struct float3 { + float v[3]; +} float3; + +typedef struct float16 { + float v[16]; +} float16; + +#include <math.h> // Required for: sinf(), cosf(), tan(), atan2f(), sqrtf(), floor(), fminf(), fmaxf(), fabsf() + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Utils math +//---------------------------------------------------------------------------------- + +// Clamp float value +RMAPI float Clamp(float value, float min, float max) +{ + float result = (value < min)? min : value; + + if (result > max) result = max; + + return result; +} + +// Calculate linear interpolation between two floats +RMAPI float Lerp(float start, float end, float amount) +{ + float result = start + amount*(end - start); + + return result; +} + +// Normalize input value within input range +RMAPI float Normalize(float value, float start, float end) +{ + float result = (value - start)/(end - start); + + return result; +} + +// Remap input value within input range to output range +RMAPI float Remap(float value, float inputStart, float inputEnd, float outputStart, float outputEnd) +{ + float result = (value - inputStart)/(inputEnd - inputStart)*(outputEnd - outputStart) + outputStart; + + return result; +} + +// Wrap input value from min to max +RMAPI float Wrap(float value, float min, float max) +{ + float result = value - (max - min)*floorf((value - min)/(max - min)); + + return result; +} + +// Check whether two given floats are almost equal +RMAPI int FloatEquals(float x, float y) +{ +#if !defined(EPSILON) + #define EPSILON 0.000001f +#endif + + int result = (fabsf(x - y)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(x), fabsf(y)))); + + return result; +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Vector2 math +//---------------------------------------------------------------------------------- + +// Vector with components value 0.0f +RMAPI Vector2 Vector2Zero(void) +{ + Vector2 result = { 0.0f, 0.0f }; + + return result; +} + +// Vector with components value 1.0f +RMAPI Vector2 Vector2One(void) +{ + Vector2 result = { 1.0f, 1.0f }; + + return result; +} + +// Add two vectors (v1 + v2) +RMAPI Vector2 Vector2Add(Vector2 v1, Vector2 v2) +{ + Vector2 result = { v1.x + v2.x, v1.y + v2.y }; + + return result; +} + +// Add vector and float value +RMAPI Vector2 Vector2AddValue(Vector2 v, float add) +{ + Vector2 result = { v.x + add, v.y + add }; + + return result; +} + +// Subtract two vectors (v1 - v2) +RMAPI Vector2 Vector2Subtract(Vector2 v1, Vector2 v2) +{ + Vector2 result = { v1.x - v2.x, v1.y - v2.y }; + + return result; +} + +// Subtract vector by float value +RMAPI Vector2 Vector2SubtractValue(Vector2 v, float sub) +{ + Vector2 result = { v.x - sub, v.y - sub }; + + return result; +} + +// Calculate vector length +RMAPI float Vector2Length(Vector2 v) +{ + float result = sqrtf((v.x*v.x) + (v.y*v.y)); + + return result; +} + +// Calculate vector square length +RMAPI float Vector2LengthSqr(Vector2 v) +{ + float result = (v.x*v.x) + (v.y*v.y); + + return result; +} + +// Calculate two vectors dot product +RMAPI float Vector2DotProduct(Vector2 v1, Vector2 v2) +{ + float result = (v1.x*v2.x + v1.y*v2.y); + + return result; +} + +// Calculate distance between two vectors +RMAPI float Vector2Distance(Vector2 v1, Vector2 v2) +{ + float result = sqrtf((v1.x - v2.x)*(v1.x - v2.x) + (v1.y - v2.y)*(v1.y - v2.y)); + + return result; +} + +// Calculate square distance between two vectors +RMAPI float Vector2DistanceSqr(Vector2 v1, Vector2 v2) +{ + float result = ((v1.x - v2.x)*(v1.x - v2.x) + (v1.y - v2.y)*(v1.y - v2.y)); + + return result; +} + +// Calculate angle between two vectors +// NOTE: Angle is calculated from origin point (0, 0) +RMAPI float Vector2Angle(Vector2 v1, Vector2 v2) +{ + float result = 0.0f; + + float dot = v1.x*v2.x + v1.y*v2.y; + float det = v1.x*v2.y - v1.y*v2.x; + + result = atan2f(det, dot); + + return result; +} + +// Calculate angle defined by a two vectors line +// NOTE: Parameters need to be normalized +// Current implementation should be aligned with glm::angle +RMAPI float Vector2LineAngle(Vector2 start, Vector2 end) +{ + float result = 0.0f; + + // TODO(10/9/2023): Currently angles move clockwise, determine if this is wanted behavior + result = -atan2f(end.y - start.y, end.x - start.x); + + return result; +} + +// Scale vector (multiply by value) +RMAPI Vector2 Vector2Scale(Vector2 v, float scale) +{ + Vector2 result = { v.x*scale, v.y*scale }; + + return result; +} + +// Multiply vector by vector +RMAPI Vector2 Vector2Multiply(Vector2 v1, Vector2 v2) +{ + Vector2 result = { v1.x*v2.x, v1.y*v2.y }; + + return result; +} + +// Negate vector +RMAPI Vector2 Vector2Negate(Vector2 v) +{ + Vector2 result = { -v.x, -v.y }; + + return result; +} + +// Divide vector by vector +RMAPI Vector2 Vector2Divide(Vector2 v1, Vector2 v2) +{ + Vector2 result = { v1.x/v2.x, v1.y/v2.y }; + + return result; +} + +// Normalize provided vector +RMAPI Vector2 Vector2Normalize(Vector2 v) +{ + Vector2 result = { 0 }; + float length = sqrtf((v.x*v.x) + (v.y*v.y)); + + if (length > 0) + { + float ilength = 1.0f/length; + result.x = v.x*ilength; + result.y = v.y*ilength; + } + + return result; +} + +// Transforms a Vector2 by a given Matrix +RMAPI Vector2 Vector2Transform(Vector2 v, Matrix mat) +{ + Vector2 result = { 0 }; + + float x = v.x; + float y = v.y; + float z = 0; + + result.x = mat.m0*x + mat.m4*y + mat.m8*z + mat.m12; + result.y = mat.m1*x + mat.m5*y + mat.m9*z + mat.m13; + + return result; +} + +// Calculate linear interpolation between two vectors +RMAPI Vector2 Vector2Lerp(Vector2 v1, Vector2 v2, float amount) +{ + Vector2 result = { 0 }; + + result.x = v1.x + amount*(v2.x - v1.x); + result.y = v1.y + amount*(v2.y - v1.y); + + return result; +} + +// Calculate reflected vector to normal +RMAPI Vector2 Vector2Reflect(Vector2 v, Vector2 normal) +{ + Vector2 result = { 0 }; + + float dotProduct = (v.x*normal.x + v.y*normal.y); // Dot product + + result.x = v.x - (2.0f*normal.x)*dotProduct; + result.y = v.y - (2.0f*normal.y)*dotProduct; + + return result; +} + +// Get min value for each pair of components +RMAPI Vector2 Vector2Min(Vector2 v1, Vector2 v2) +{ + Vector2 result = { 0 }; + + result.x = fminf(v1.x, v2.x); + result.y = fminf(v1.y, v2.y); + + return result; +} + +// Get max value for each pair of components +RMAPI Vector2 Vector2Max(Vector2 v1, Vector2 v2) +{ + Vector2 result = { 0 }; + + result.x = fmaxf(v1.x, v2.x); + result.y = fmaxf(v1.y, v2.y); + + return result; +} + +// Rotate vector by angle +RMAPI Vector2 Vector2Rotate(Vector2 v, float angle) +{ + Vector2 result = { 0 }; + + float cosres = cosf(angle); + float sinres = sinf(angle); + + result.x = v.x*cosres - v.y*sinres; + result.y = v.x*sinres + v.y*cosres; + + return result; +} + +// Move Vector towards target +RMAPI Vector2 Vector2MoveTowards(Vector2 v, Vector2 target, float maxDistance) +{ + Vector2 result = { 0 }; + + float dx = target.x - v.x; + float dy = target.y - v.y; + float value = (dx*dx) + (dy*dy); + + if ((value == 0) || ((maxDistance >= 0) && (value <= maxDistance*maxDistance))) return target; + + float dist = sqrtf(value); + + result.x = v.x + dx/dist*maxDistance; + result.y = v.y + dy/dist*maxDistance; + + return result; +} + +// Invert the given vector +RMAPI Vector2 Vector2Invert(Vector2 v) +{ + Vector2 result = { 1.0f/v.x, 1.0f/v.y }; + + return result; +} + +// Clamp the components of the vector between +// min and max values specified by the given vectors +RMAPI Vector2 Vector2Clamp(Vector2 v, Vector2 min, Vector2 max) +{ + Vector2 result = { 0 }; + + result.x = fminf(max.x, fmaxf(min.x, v.x)); + result.y = fminf(max.y, fmaxf(min.y, v.y)); + + return result; +} + +// Clamp the magnitude of the vector between two min and max values +RMAPI Vector2 Vector2ClampValue(Vector2 v, float min, float max) +{ + Vector2 result = v; + + float length = (v.x*v.x) + (v.y*v.y); + if (length > 0.0f) + { + length = sqrtf(length); + + float scale = 1; // By default, 1 as the neutral element. + if (length < min) + { + scale = min/length; + } + else if (length > max) + { + scale = max/length; + } + + result.x = v.x*scale; + result.y = v.y*scale; + } + + return result; +} + +// Check whether two given vectors are almost equal +RMAPI int Vector2Equals(Vector2 p, Vector2 q) +{ +#if !defined(EPSILON) + #define EPSILON 0.000001f +#endif + + int result = ((fabsf(p.x - q.x)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.x), fabsf(q.x))))) && + ((fabsf(p.y - q.y)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.y), fabsf(q.y))))); + + return result; +} + +// Compute the direction of a refracted ray +// v: normalized direction of the incoming ray +// n: normalized normal vector of the interface of two optical media +// r: ratio of the refractive index of the medium from where the ray comes +// to the refractive index of the medium on the other side of the surface +RMAPI Vector2 Vector2Refract(Vector2 v, Vector2 n, float r) +{ + Vector2 result = { 0 }; + + float dot = v.x*n.x + v.y*n.y; + float d = 1.0f - r*r*(1.0f - dot*dot); + + if (d >= 0.0f) + { + d = sqrtf(d); + v.x = r*v.x - (r*dot + d)*n.x; + v.y = r*v.y - (r*dot + d)*n.y; + + result = v; + } + + return result; +} + + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Vector3 math +//---------------------------------------------------------------------------------- + +// Vector with components value 0.0f +RMAPI Vector3 Vector3Zero(void) +{ + Vector3 result = { 0.0f, 0.0f, 0.0f }; + + return result; +} + +// Vector with components value 1.0f +RMAPI Vector3 Vector3One(void) +{ + Vector3 result = { 1.0f, 1.0f, 1.0f }; + + return result; +} + +// Add two vectors +RMAPI Vector3 Vector3Add(Vector3 v1, Vector3 v2) +{ + Vector3 result = { v1.x + v2.x, v1.y + v2.y, v1.z + v2.z }; + + return result; +} + +// Add vector and float value +RMAPI Vector3 Vector3AddValue(Vector3 v, float add) +{ + Vector3 result = { v.x + add, v.y + add, v.z + add }; + + return result; +} + +// Subtract two vectors +RMAPI Vector3 Vector3Subtract(Vector3 v1, Vector3 v2) +{ + Vector3 result = { v1.x - v2.x, v1.y - v2.y, v1.z - v2.z }; + + return result; +} + +// Subtract vector by float value +RMAPI Vector3 Vector3SubtractValue(Vector3 v, float sub) +{ + Vector3 result = { v.x - sub, v.y - sub, v.z - sub }; + + return result; +} + +// Multiply vector by scalar +RMAPI Vector3 Vector3Scale(Vector3 v, float scalar) +{ + Vector3 result = { v.x*scalar, v.y*scalar, v.z*scalar }; + + return result; +} + +// Multiply vector by vector +RMAPI Vector3 Vector3Multiply(Vector3 v1, Vector3 v2) +{ + Vector3 result = { v1.x*v2.x, v1.y*v2.y, v1.z*v2.z }; + + return result; +} + +// Calculate two vectors cross product +RMAPI Vector3 Vector3CrossProduct(Vector3 v1, Vector3 v2) +{ + Vector3 result = { v1.y*v2.z - v1.z*v2.y, v1.z*v2.x - v1.x*v2.z, v1.x*v2.y - v1.y*v2.x }; + + return result; +} + +// Calculate one vector perpendicular vector +RMAPI Vector3 Vector3Perpendicular(Vector3 v) +{ + Vector3 result = { 0 }; + + float min = fabsf(v.x); + Vector3 cardinalAxis = {1.0f, 0.0f, 0.0f}; + + if (fabsf(v.y) < min) + { + min = fabsf(v.y); + Vector3 tmp = {0.0f, 1.0f, 0.0f}; + cardinalAxis = tmp; + } + + if (fabsf(v.z) < min) + { + Vector3 tmp = {0.0f, 0.0f, 1.0f}; + cardinalAxis = tmp; + } + + // Cross product between vectors + result.x = v.y*cardinalAxis.z - v.z*cardinalAxis.y; + result.y = v.z*cardinalAxis.x - v.x*cardinalAxis.z; + result.z = v.x*cardinalAxis.y - v.y*cardinalAxis.x; + + return result; +} + +// Calculate vector length +RMAPI float Vector3Length(const Vector3 v) +{ + float result = sqrtf(v.x*v.x + v.y*v.y + v.z*v.z); + + return result; +} + +// Calculate vector square length +RMAPI float Vector3LengthSqr(const Vector3 v) +{ + float result = v.x*v.x + v.y*v.y + v.z*v.z; + + return result; +} + +// Calculate two vectors dot product +RMAPI float Vector3DotProduct(Vector3 v1, Vector3 v2) +{ + float result = (v1.x*v2.x + v1.y*v2.y + v1.z*v2.z); + + return result; +} + +// Calculate distance between two vectors +RMAPI float Vector3Distance(Vector3 v1, Vector3 v2) +{ + float result = 0.0f; + + float dx = v2.x - v1.x; + float dy = v2.y - v1.y; + float dz = v2.z - v1.z; + result = sqrtf(dx*dx + dy*dy + dz*dz); + + return result; +} + +// Calculate square distance between two vectors +RMAPI float Vector3DistanceSqr(Vector3 v1, Vector3 v2) +{ + float result = 0.0f; + + float dx = v2.x - v1.x; + float dy = v2.y - v1.y; + float dz = v2.z - v1.z; + result = dx*dx + dy*dy + dz*dz; + + return result; +} + +// Calculate angle between two vectors +RMAPI float Vector3Angle(Vector3 v1, Vector3 v2) +{ + float result = 0.0f; + + Vector3 cross = { v1.y*v2.z - v1.z*v2.y, v1.z*v2.x - v1.x*v2.z, v1.x*v2.y - v1.y*v2.x }; + float len = sqrtf(cross.x*cross.x + cross.y*cross.y + cross.z*cross.z); + float dot = (v1.x*v2.x + v1.y*v2.y + v1.z*v2.z); + result = atan2f(len, dot); + + return result; +} + +// Negate provided vector (invert direction) +RMAPI Vector3 Vector3Negate(Vector3 v) +{ + Vector3 result = { -v.x, -v.y, -v.z }; + + return result; +} + +// Divide vector by vector +RMAPI Vector3 Vector3Divide(Vector3 v1, Vector3 v2) +{ + Vector3 result = { v1.x/v2.x, v1.y/v2.y, v1.z/v2.z }; + + return result; +} + +// Normalize provided vector +RMAPI Vector3 Vector3Normalize(Vector3 v) +{ + Vector3 result = v; + + float length = sqrtf(v.x*v.x + v.y*v.y + v.z*v.z); + if (length != 0.0f) + { + float ilength = 1.0f/length; + + result.x *= ilength; + result.y *= ilength; + result.z *= ilength; + } + + return result; +} + +//Calculate the projection of the vector v1 on to v2 +RMAPI Vector3 Vector3Project(Vector3 v1, Vector3 v2) +{ + Vector3 result = { 0 }; + + float v1dv2 = (v1.x*v2.x + v1.y*v2.y + v1.z*v2.z); + float v2dv2 = (v2.x*v2.x + v2.y*v2.y + v2.z*v2.z); + + float mag = v1dv2/v2dv2; + + result.x = v2.x*mag; + result.y = v2.y*mag; + result.z = v2.z*mag; + + return result; +} + +//Calculate the rejection of the vector v1 on to v2 +RMAPI Vector3 Vector3Reject(Vector3 v1, Vector3 v2) +{ + Vector3 result = { 0 }; + + float v1dv2 = (v1.x*v2.x + v1.y*v2.y + v1.z*v2.z); + float v2dv2 = (v2.x*v2.x + v2.y*v2.y + v2.z*v2.z); + + float mag = v1dv2/v2dv2; + + result.x = v1.x - (v2.x*mag); + result.y = v1.y - (v2.y*mag); + result.z = v1.z - (v2.z*mag); + + return result; +} + +// Orthonormalize provided vectors +// Makes vectors normalized and orthogonal to each other +// Gram-Schmidt function implementation +RMAPI void Vector3OrthoNormalize(Vector3 *v1, Vector3 *v2) +{ + float length = 0.0f; + float ilength = 0.0f; + + // Vector3Normalize(*v1); + Vector3 v = *v1; + length = sqrtf(v.x*v.x + v.y*v.y + v.z*v.z); + if (length == 0.0f) length = 1.0f; + ilength = 1.0f/length; + v1->x *= ilength; + v1->y *= ilength; + v1->z *= ilength; + + // Vector3CrossProduct(*v1, *v2) + Vector3 vn1 = { v1->y*v2->z - v1->z*v2->y, v1->z*v2->x - v1->x*v2->z, v1->x*v2->y - v1->y*v2->x }; + + // Vector3Normalize(vn1); + v = vn1; + length = sqrtf(v.x*v.x + v.y*v.y + v.z*v.z); + if (length == 0.0f) length = 1.0f; + ilength = 1.0f/length; + vn1.x *= ilength; + vn1.y *= ilength; + vn1.z *= ilength; + + // Vector3CrossProduct(vn1, *v1) + Vector3 vn2 = { vn1.y*v1->z - vn1.z*v1->y, vn1.z*v1->x - vn1.x*v1->z, vn1.x*v1->y - vn1.y*v1->x }; + + *v2 = vn2; +} + +// Transforms a Vector3 by a given Matrix +RMAPI Vector3 Vector3Transform(Vector3 v, Matrix mat) +{ + Vector3 result = { 0 }; + + float x = v.x; + float y = v.y; + float z = v.z; + + result.x = mat.m0*x + mat.m4*y + mat.m8*z + mat.m12; + result.y = mat.m1*x + mat.m5*y + mat.m9*z + mat.m13; + result.z = mat.m2*x + mat.m6*y + mat.m10*z + mat.m14; + + return result; +} + +// Transform a vector by quaternion rotation +RMAPI Vector3 Vector3RotateByQuaternion(Vector3 v, Quaternion q) +{ + Vector3 result = { 0 }; + + result.x = v.x*(q.x*q.x + q.w*q.w - q.y*q.y - q.z*q.z) + v.y*(2*q.x*q.y - 2*q.w*q.z) + v.z*(2*q.x*q.z + 2*q.w*q.y); + result.y = v.x*(2*q.w*q.z + 2*q.x*q.y) + v.y*(q.w*q.w - q.x*q.x + q.y*q.y - q.z*q.z) + v.z*(-2*q.w*q.x + 2*q.y*q.z); + result.z = v.x*(-2*q.w*q.y + 2*q.x*q.z) + v.y*(2*q.w*q.x + 2*q.y*q.z)+ v.z*(q.w*q.w - q.x*q.x - q.y*q.y + q.z*q.z); + + return result; +} + +// Rotates a vector around an axis +RMAPI Vector3 Vector3RotateByAxisAngle(Vector3 v, Vector3 axis, float angle) +{ + // Using Euler-Rodrigues Formula + // Ref.: https://en.wikipedia.org/w/index.php?title=Euler%E2%80%93Rodrigues_formula + + Vector3 result = v; + + // Vector3Normalize(axis); + float length = sqrtf(axis.x*axis.x + axis.y*axis.y + axis.z*axis.z); + if (length == 0.0f) length = 1.0f; + float ilength = 1.0f/length; + axis.x *= ilength; + axis.y *= ilength; + axis.z *= ilength; + + angle /= 2.0f; + float a = sinf(angle); + float b = axis.x*a; + float c = axis.y*a; + float d = axis.z*a; + a = cosf(angle); + Vector3 w = { b, c, d }; + + // Vector3CrossProduct(w, v) + Vector3 wv = { w.y*v.z - w.z*v.y, w.z*v.x - w.x*v.z, w.x*v.y - w.y*v.x }; + + // Vector3CrossProduct(w, wv) + Vector3 wwv = { w.y*wv.z - w.z*wv.y, w.z*wv.x - w.x*wv.z, w.x*wv.y - w.y*wv.x }; + + // Vector3Scale(wv, 2*a) + a *= 2; + wv.x *= a; + wv.y *= a; + wv.z *= a; + + // Vector3Scale(wwv, 2) + wwv.x *= 2; + wwv.y *= 2; + wwv.z *= 2; + + result.x += wv.x; + result.y += wv.y; + result.z += wv.z; + + result.x += wwv.x; + result.y += wwv.y; + result.z += wwv.z; + + return result; +} + +// Move Vector towards target +RMAPI Vector3 Vector3MoveTowards(Vector3 v, Vector3 target, float maxDistance) +{ + Vector3 result = { 0 }; + + float dx = target.x - v.x; + float dy = target.y - v.y; + float dz = target.z - v.z; + float value = (dx*dx) + (dy*dy) + (dz*dz); + + if ((value == 0) || ((maxDistance >= 0) && (value <= maxDistance*maxDistance))) return target; + + float dist = sqrtf(value); + + result.x = v.x + dx/dist*maxDistance; + result.y = v.y + dy/dist*maxDistance; + result.z = v.z + dz/dist*maxDistance; + + return result; +} + +// Calculate linear interpolation between two vectors +RMAPI Vector3 Vector3Lerp(Vector3 v1, Vector3 v2, float amount) +{ + Vector3 result = { 0 }; + + result.x = v1.x + amount*(v2.x - v1.x); + result.y = v1.y + amount*(v2.y - v1.y); + result.z = v1.z + amount*(v2.z - v1.z); + + return result; +} + +// Calculate cubic hermite interpolation between two vectors and their tangents +// as described in the GLTF 2.0 specification: https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#interpolation-cubic +RMAPI Vector3 Vector3CubicHermite(Vector3 v1, Vector3 tangent1, Vector3 v2, Vector3 tangent2, float amount) +{ + Vector3 result = { 0 }; + + float amountPow2 = amount*amount; + float amountPow3 = amount*amount*amount; + + result.x = (2*amountPow3 - 3*amountPow2 + 1)*v1.x + (amountPow3 - 2*amountPow2 + amount)*tangent1.x + (-2*amountPow3 + 3*amountPow2)*v2.x + (amountPow3 - amountPow2)*tangent2.x; + result.y = (2*amountPow3 - 3*amountPow2 + 1)*v1.y + (amountPow3 - 2*amountPow2 + amount)*tangent1.y + (-2*amountPow3 + 3*amountPow2)*v2.y + (amountPow3 - amountPow2)*tangent2.y; + result.z = (2*amountPow3 - 3*amountPow2 + 1)*v1.z + (amountPow3 - 2*amountPow2 + amount)*tangent1.z + (-2*amountPow3 + 3*amountPow2)*v2.z + (amountPow3 - amountPow2)*tangent2.z; + + return result; +} + +// Calculate reflected vector to normal +RMAPI Vector3 Vector3Reflect(Vector3 v, Vector3 normal) +{ + Vector3 result = { 0 }; + + // I is the original vector + // N is the normal of the incident plane + // R = I - (2*N*(DotProduct[I, N])) + + float dotProduct = (v.x*normal.x + v.y*normal.y + v.z*normal.z); + + result.x = v.x - (2.0f*normal.x)*dotProduct; + result.y = v.y - (2.0f*normal.y)*dotProduct; + result.z = v.z - (2.0f*normal.z)*dotProduct; + + return result; +} + +// Get min value for each pair of components +RMAPI Vector3 Vector3Min(Vector3 v1, Vector3 v2) +{ + Vector3 result = { 0 }; + + result.x = fminf(v1.x, v2.x); + result.y = fminf(v1.y, v2.y); + result.z = fminf(v1.z, v2.z); + + return result; +} + +// Get max value for each pair of components +RMAPI Vector3 Vector3Max(Vector3 v1, Vector3 v2) +{ + Vector3 result = { 0 }; + + result.x = fmaxf(v1.x, v2.x); + result.y = fmaxf(v1.y, v2.y); + result.z = fmaxf(v1.z, v2.z); + + return result; +} + +// Compute barycenter coordinates (u, v, w) for point p with respect to triangle (a, b, c) +// NOTE: Assumes P is on the plane of the triangle +RMAPI Vector3 Vector3Barycenter(Vector3 p, Vector3 a, Vector3 b, Vector3 c) +{ + Vector3 result = { 0 }; + + Vector3 v0 = { b.x - a.x, b.y - a.y, b.z - a.z }; // Vector3Subtract(b, a) + Vector3 v1 = { c.x - a.x, c.y - a.y, c.z - a.z }; // Vector3Subtract(c, a) + Vector3 v2 = { p.x - a.x, p.y - a.y, p.z - a.z }; // Vector3Subtract(p, a) + float d00 = (v0.x*v0.x + v0.y*v0.y + v0.z*v0.z); // Vector3DotProduct(v0, v0) + float d01 = (v0.x*v1.x + v0.y*v1.y + v0.z*v1.z); // Vector3DotProduct(v0, v1) + float d11 = (v1.x*v1.x + v1.y*v1.y + v1.z*v1.z); // Vector3DotProduct(v1, v1) + float d20 = (v2.x*v0.x + v2.y*v0.y + v2.z*v0.z); // Vector3DotProduct(v2, v0) + float d21 = (v2.x*v1.x + v2.y*v1.y + v2.z*v1.z); // Vector3DotProduct(v2, v1) + + float denom = d00*d11 - d01*d01; + + result.y = (d11*d20 - d01*d21)/denom; + result.z = (d00*d21 - d01*d20)/denom; + result.x = 1.0f - (result.z + result.y); + + return result; +} + +// Projects a Vector3 from screen space into object space +// NOTE: We are avoiding calling other raymath functions despite available +RMAPI Vector3 Vector3Unproject(Vector3 source, Matrix projection, Matrix view) +{ + Vector3 result = { 0 }; + + // Calculate unprojected matrix (multiply view matrix by projection matrix) and invert it + Matrix matViewProj = { // MatrixMultiply(view, projection); + view.m0*projection.m0 + view.m1*projection.m4 + view.m2*projection.m8 + view.m3*projection.m12, + view.m0*projection.m1 + view.m1*projection.m5 + view.m2*projection.m9 + view.m3*projection.m13, + view.m0*projection.m2 + view.m1*projection.m6 + view.m2*projection.m10 + view.m3*projection.m14, + view.m0*projection.m3 + view.m1*projection.m7 + view.m2*projection.m11 + view.m3*projection.m15, + view.m4*projection.m0 + view.m5*projection.m4 + view.m6*projection.m8 + view.m7*projection.m12, + view.m4*projection.m1 + view.m5*projection.m5 + view.m6*projection.m9 + view.m7*projection.m13, + view.m4*projection.m2 + view.m5*projection.m6 + view.m6*projection.m10 + view.m7*projection.m14, + view.m4*projection.m3 + view.m5*projection.m7 + view.m6*projection.m11 + view.m7*projection.m15, + view.m8*projection.m0 + view.m9*projection.m4 + view.m10*projection.m8 + view.m11*projection.m12, + view.m8*projection.m1 + view.m9*projection.m5 + view.m10*projection.m9 + view.m11*projection.m13, + view.m8*projection.m2 + view.m9*projection.m6 + view.m10*projection.m10 + view.m11*projection.m14, + view.m8*projection.m3 + view.m9*projection.m7 + view.m10*projection.m11 + view.m11*projection.m15, + view.m12*projection.m0 + view.m13*projection.m4 + view.m14*projection.m8 + view.m15*projection.m12, + view.m12*projection.m1 + view.m13*projection.m5 + view.m14*projection.m9 + view.m15*projection.m13, + view.m12*projection.m2 + view.m13*projection.m6 + view.m14*projection.m10 + view.m15*projection.m14, + view.m12*projection.m3 + view.m13*projection.m7 + view.m14*projection.m11 + view.m15*projection.m15 }; + + // Calculate inverted matrix -> MatrixInvert(matViewProj); + // Cache the matrix values (speed optimization) + float a00 = matViewProj.m0, a01 = matViewProj.m1, a02 = matViewProj.m2, a03 = matViewProj.m3; + float a10 = matViewProj.m4, a11 = matViewProj.m5, a12 = matViewProj.m6, a13 = matViewProj.m7; + float a20 = matViewProj.m8, a21 = matViewProj.m9, a22 = matViewProj.m10, a23 = matViewProj.m11; + float a30 = matViewProj.m12, a31 = matViewProj.m13, a32 = matViewProj.m14, a33 = matViewProj.m15; + + float b00 = a00*a11 - a01*a10; + float b01 = a00*a12 - a02*a10; + float b02 = a00*a13 - a03*a10; + float b03 = a01*a12 - a02*a11; + float b04 = a01*a13 - a03*a11; + float b05 = a02*a13 - a03*a12; + float b06 = a20*a31 - a21*a30; + float b07 = a20*a32 - a22*a30; + float b08 = a20*a33 - a23*a30; + float b09 = a21*a32 - a22*a31; + float b10 = a21*a33 - a23*a31; + float b11 = a22*a33 - a23*a32; + + // Calculate the invert determinant (inlined to avoid double-caching) + float invDet = 1.0f/(b00*b11 - b01*b10 + b02*b09 + b03*b08 - b04*b07 + b05*b06); + + Matrix matViewProjInv = { + (a11*b11 - a12*b10 + a13*b09)*invDet, + (-a01*b11 + a02*b10 - a03*b09)*invDet, + (a31*b05 - a32*b04 + a33*b03)*invDet, + (-a21*b05 + a22*b04 - a23*b03)*invDet, + (-a10*b11 + a12*b08 - a13*b07)*invDet, + (a00*b11 - a02*b08 + a03*b07)*invDet, + (-a30*b05 + a32*b02 - a33*b01)*invDet, + (a20*b05 - a22*b02 + a23*b01)*invDet, + (a10*b10 - a11*b08 + a13*b06)*invDet, + (-a00*b10 + a01*b08 - a03*b06)*invDet, + (a30*b04 - a31*b02 + a33*b00)*invDet, + (-a20*b04 + a21*b02 - a23*b00)*invDet, + (-a10*b09 + a11*b07 - a12*b06)*invDet, + (a00*b09 - a01*b07 + a02*b06)*invDet, + (-a30*b03 + a31*b01 - a32*b00)*invDet, + (a20*b03 - a21*b01 + a22*b00)*invDet }; + + // Create quaternion from source point + Quaternion quat = { source.x, source.y, source.z, 1.0f }; + + // Multiply quat point by unprojecte matrix + Quaternion qtransformed = { // QuaternionTransform(quat, matViewProjInv) + matViewProjInv.m0*quat.x + matViewProjInv.m4*quat.y + matViewProjInv.m8*quat.z + matViewProjInv.m12*quat.w, + matViewProjInv.m1*quat.x + matViewProjInv.m5*quat.y + matViewProjInv.m9*quat.z + matViewProjInv.m13*quat.w, + matViewProjInv.m2*quat.x + matViewProjInv.m6*quat.y + matViewProjInv.m10*quat.z + matViewProjInv.m14*quat.w, + matViewProjInv.m3*quat.x + matViewProjInv.m7*quat.y + matViewProjInv.m11*quat.z + matViewProjInv.m15*quat.w }; + + // Normalized world points in vectors + result.x = qtransformed.x/qtransformed.w; + result.y = qtransformed.y/qtransformed.w; + result.z = qtransformed.z/qtransformed.w; + + return result; +} + +// Get Vector3 as float array +RMAPI float3 Vector3ToFloatV(Vector3 v) +{ + float3 buffer = { 0 }; + + buffer.v[0] = v.x; + buffer.v[1] = v.y; + buffer.v[2] = v.z; + + return buffer; +} + +// Invert the given vector +RMAPI Vector3 Vector3Invert(Vector3 v) +{ + Vector3 result = { 1.0f/v.x, 1.0f/v.y, 1.0f/v.z }; + + return result; +} + +// Clamp the components of the vector between +// min and max values specified by the given vectors +RMAPI Vector3 Vector3Clamp(Vector3 v, Vector3 min, Vector3 max) +{ + Vector3 result = { 0 }; + + result.x = fminf(max.x, fmaxf(min.x, v.x)); + result.y = fminf(max.y, fmaxf(min.y, v.y)); + result.z = fminf(max.z, fmaxf(min.z, v.z)); + + return result; +} + +// Clamp the magnitude of the vector between two values +RMAPI Vector3 Vector3ClampValue(Vector3 v, float min, float max) +{ + Vector3 result = v; + + float length = (v.x*v.x) + (v.y*v.y) + (v.z*v.z); + if (length > 0.0f) + { + length = sqrtf(length); + + float scale = 1; // By default, 1 as the neutral element. + if (length < min) + { + scale = min/length; + } + else if (length > max) + { + scale = max/length; + } + + result.x = v.x*scale; + result.y = v.y*scale; + result.z = v.z*scale; + } + + return result; +} + +// Check whether two given vectors are almost equal +RMAPI int Vector3Equals(Vector3 p, Vector3 q) +{ +#if !defined(EPSILON) + #define EPSILON 0.000001f +#endif + + int result = ((fabsf(p.x - q.x)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.x), fabsf(q.x))))) && + ((fabsf(p.y - q.y)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.y), fabsf(q.y))))) && + ((fabsf(p.z - q.z)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.z), fabsf(q.z))))); + + return result; +} + +// Compute the direction of a refracted ray +// v: normalized direction of the incoming ray +// n: normalized normal vector of the interface of two optical media +// r: ratio of the refractive index of the medium from where the ray comes +// to the refractive index of the medium on the other side of the surface +RMAPI Vector3 Vector3Refract(Vector3 v, Vector3 n, float r) +{ + Vector3 result = { 0 }; + + float dot = v.x*n.x + v.y*n.y + v.z*n.z; + float d = 1.0f - r*r*(1.0f - dot*dot); + + if (d >= 0.0f) + { + d = sqrtf(d); + v.x = r*v.x - (r*dot + d)*n.x; + v.y = r*v.y - (r*dot + d)*n.y; + v.z = r*v.z - (r*dot + d)*n.z; + + result = v; + } + + return result; +} + + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Vector4 math +//---------------------------------------------------------------------------------- + +RMAPI Vector4 Vector4Zero(void) +{ + Vector4 result = { 0.0f, 0.0f, 0.0f, 0.0f }; + return result; +} + +RMAPI Vector4 Vector4One(void) +{ + Vector4 result = { 1.0f, 1.0f, 1.0f, 1.0f }; + return result; +} + +RMAPI Vector4 Vector4Add(Vector4 v1, Vector4 v2) +{ + Vector4 result = { + v1.x + v2.x, + v1.y + v2.y, + v1.z + v2.z, + v1.w + v2.w + }; + return result; +} + +RMAPI Vector4 Vector4AddValue(Vector4 v, float add) +{ + Vector4 result = { + v.x + add, + v.y + add, + v.z + add, + v.w + add + }; + return result; +} + +RMAPI Vector4 Vector4Subtract(Vector4 v1, Vector4 v2) +{ + Vector4 result = { + v1.x - v2.x, + v1.y - v2.y, + v1.z - v2.z, + v1.w - v2.w + }; + return result; +} + +RMAPI Vector4 Vector4SubtractValue(Vector4 v, float add) +{ + Vector4 result = { + v.x - add, + v.y - add, + v.z - add, + v.w - add + }; + return result; +} + +RMAPI float Vector4Length(Vector4 v) +{ + float result = sqrtf((v.x*v.x) + (v.y*v.y) + (v.z*v.z) + (v.w*v.w)); + return result; +} + +RMAPI float Vector4LengthSqr(Vector4 v) +{ + float result = (v.x*v.x) + (v.y*v.y) + (v.z*v.z) + (v.w*v.w); + return result; +} + +RMAPI float Vector4DotProduct(Vector4 v1, Vector4 v2) +{ + float result = (v1.x*v2.x + v1.y*v2.y + v1.z*v2.z + v1.w*v2.w); + return result; +} + +// Calculate distance between two vectors +RMAPI float Vector4Distance(Vector4 v1, Vector4 v2) +{ + float result = sqrtf( + (v1.x - v2.x)*(v1.x - v2.x) + (v1.y - v2.y)*(v1.y - v2.y) + + (v1.z - v2.z)*(v1.z - v2.z) + (v1.w - v2.w)*(v1.w - v2.w)); + return result; +} + +// Calculate square distance between two vectors +RMAPI float Vector4DistanceSqr(Vector4 v1, Vector4 v2) +{ + float result = + (v1.x - v2.x)*(v1.x - v2.x) + (v1.y - v2.y)*(v1.y - v2.y) + + (v1.z - v2.z)*(v1.z - v2.z) + (v1.w - v2.w)*(v1.w - v2.w); + + return result; +} + +RMAPI Vector4 Vector4Scale(Vector4 v, float scale) +{ + Vector4 result = { v.x*scale, v.y*scale, v.z*scale, v.w*scale }; + return result; +} + +// Multiply vector by vector +RMAPI Vector4 Vector4Multiply(Vector4 v1, Vector4 v2) +{ + Vector4 result = { v1.x*v2.x, v1.y*v2.y, v1.z*v2.z, v1.w*v2.w }; + return result; +} + +// Negate vector +RMAPI Vector4 Vector4Negate(Vector4 v) +{ + Vector4 result = { -v.x, -v.y, -v.z, -v.w }; + return result; +} + +// Divide vector by vector +RMAPI Vector4 Vector4Divide(Vector4 v1, Vector4 v2) +{ + Vector4 result = { v1.x/v2.x, v1.y/v2.y, v1.z/v2.z, v1.w/v2.w }; + return result; +} + +// Normalize provided vector +RMAPI Vector4 Vector4Normalize(Vector4 v) +{ + Vector4 result = { 0 }; + float length = sqrtf((v.x*v.x) + (v.y*v.y) + (v.z*v.z) + (v.w*v.w)); + + if (length > 0) + { + float ilength = 1.0f/length; + result.x = v.x*ilength; + result.y = v.y*ilength; + result.z = v.z*ilength; + result.w = v.w*ilength; + } + + return result; +} + +// Get min value for each pair of components +RMAPI Vector4 Vector4Min(Vector4 v1, Vector4 v2) +{ + Vector4 result = { 0 }; + + result.x = fminf(v1.x, v2.x); + result.y = fminf(v1.y, v2.y); + result.z = fminf(v1.z, v2.z); + result.w = fminf(v1.w, v2.w); + + return result; +} + +// Get max value for each pair of components +RMAPI Vector4 Vector4Max(Vector4 v1, Vector4 v2) +{ + Vector4 result = { 0 }; + + result.x = fmaxf(v1.x, v2.x); + result.y = fmaxf(v1.y, v2.y); + result.z = fmaxf(v1.z, v2.z); + result.w = fmaxf(v1.w, v2.w); + + return result; +} + +// Calculate linear interpolation between two vectors +RMAPI Vector4 Vector4Lerp(Vector4 v1, Vector4 v2, float amount) +{ + Vector4 result = { 0 }; + + result.x = v1.x + amount*(v2.x - v1.x); + result.y = v1.y + amount*(v2.y - v1.y); + result.z = v1.z + amount*(v2.z - v1.z); + result.w = v1.w + amount*(v2.w - v1.w); + + return result; +} + +// Move Vector towards target +RMAPI Vector4 Vector4MoveTowards(Vector4 v, Vector4 target, float maxDistance) +{ + Vector4 result = { 0 }; + + float dx = target.x - v.x; + float dy = target.y - v.y; + float dz = target.z - v.z; + float dw = target.w - v.w; + float value = (dx*dx) + (dy*dy) + (dz*dz) + (dw*dw); + + if ((value == 0) || ((maxDistance >= 0) && (value <= maxDistance*maxDistance))) return target; + + float dist = sqrtf(value); + + result.x = v.x + dx/dist*maxDistance; + result.y = v.y + dy/dist*maxDistance; + result.z = v.z + dz/dist*maxDistance; + result.w = v.w + dw/dist*maxDistance; + + return result; +} + +// Invert the given vector +RMAPI Vector4 Vector4Invert(Vector4 v) +{ + Vector4 result = { 1.0f/v.x, 1.0f/v.y, 1.0f/v.z, 1.0f/v.w }; + return result; +} + +// Check whether two given vectors are almost equal +RMAPI int Vector4Equals(Vector4 p, Vector4 q) +{ +#if !defined(EPSILON) + #define EPSILON 0.000001f +#endif + + int result = ((fabsf(p.x - q.x)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.x), fabsf(q.x))))) && + ((fabsf(p.y - q.y)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.y), fabsf(q.y))))) && + ((fabsf(p.z - q.z)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.z), fabsf(q.z))))) && + ((fabsf(p.w - q.w)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.w), fabsf(q.w))))); + return result; +} + + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Matrix math +//---------------------------------------------------------------------------------- + +// Compute matrix determinant +RMAPI float MatrixDeterminant(Matrix mat) +{ + float result = 0.0f; + + // Cache the matrix values (speed optimization) + float a00 = mat.m0, a01 = mat.m1, a02 = mat.m2, a03 = mat.m3; + float a10 = mat.m4, a11 = mat.m5, a12 = mat.m6, a13 = mat.m7; + float a20 = mat.m8, a21 = mat.m9, a22 = mat.m10, a23 = mat.m11; + float a30 = mat.m12, a31 = mat.m13, a32 = mat.m14, a33 = mat.m15; + + result = a30*a21*a12*a03 - a20*a31*a12*a03 - a30*a11*a22*a03 + a10*a31*a22*a03 + + a20*a11*a32*a03 - a10*a21*a32*a03 - a30*a21*a02*a13 + a20*a31*a02*a13 + + a30*a01*a22*a13 - a00*a31*a22*a13 - a20*a01*a32*a13 + a00*a21*a32*a13 + + a30*a11*a02*a23 - a10*a31*a02*a23 - a30*a01*a12*a23 + a00*a31*a12*a23 + + a10*a01*a32*a23 - a00*a11*a32*a23 - a20*a11*a02*a33 + a10*a21*a02*a33 + + a20*a01*a12*a33 - a00*a21*a12*a33 - a10*a01*a22*a33 + a00*a11*a22*a33; + + return result; +} + +// Get the trace of the matrix (sum of the values along the diagonal) +RMAPI float MatrixTrace(Matrix mat) +{ + float result = (mat.m0 + mat.m5 + mat.m10 + mat.m15); + + return result; +} + +// Transposes provided matrix +RMAPI Matrix MatrixTranspose(Matrix mat) +{ + Matrix result = { 0 }; + + result.m0 = mat.m0; + result.m1 = mat.m4; + result.m2 = mat.m8; + result.m3 = mat.m12; + result.m4 = mat.m1; + result.m5 = mat.m5; + result.m6 = mat.m9; + result.m7 = mat.m13; + result.m8 = mat.m2; + result.m9 = mat.m6; + result.m10 = mat.m10; + result.m11 = mat.m14; + result.m12 = mat.m3; + result.m13 = mat.m7; + result.m14 = mat.m11; + result.m15 = mat.m15; + + return result; +} + +// Invert provided matrix +RMAPI Matrix MatrixInvert(Matrix mat) +{ + Matrix result = { 0 }; + + // Cache the matrix values (speed optimization) + float a00 = mat.m0, a01 = mat.m1, a02 = mat.m2, a03 = mat.m3; + float a10 = mat.m4, a11 = mat.m5, a12 = mat.m6, a13 = mat.m7; + float a20 = mat.m8, a21 = mat.m9, a22 = mat.m10, a23 = mat.m11; + float a30 = mat.m12, a31 = mat.m13, a32 = mat.m14, a33 = mat.m15; + + float b00 = a00*a11 - a01*a10; + float b01 = a00*a12 - a02*a10; + float b02 = a00*a13 - a03*a10; + float b03 = a01*a12 - a02*a11; + float b04 = a01*a13 - a03*a11; + float b05 = a02*a13 - a03*a12; + float b06 = a20*a31 - a21*a30; + float b07 = a20*a32 - a22*a30; + float b08 = a20*a33 - a23*a30; + float b09 = a21*a32 - a22*a31; + float b10 = a21*a33 - a23*a31; + float b11 = a22*a33 - a23*a32; + + // Calculate the invert determinant (inlined to avoid double-caching) + float invDet = 1.0f/(b00*b11 - b01*b10 + b02*b09 + b03*b08 - b04*b07 + b05*b06); + + result.m0 = (a11*b11 - a12*b10 + a13*b09)*invDet; + result.m1 = (-a01*b11 + a02*b10 - a03*b09)*invDet; + result.m2 = (a31*b05 - a32*b04 + a33*b03)*invDet; + result.m3 = (-a21*b05 + a22*b04 - a23*b03)*invDet; + result.m4 = (-a10*b11 + a12*b08 - a13*b07)*invDet; + result.m5 = (a00*b11 - a02*b08 + a03*b07)*invDet; + result.m6 = (-a30*b05 + a32*b02 - a33*b01)*invDet; + result.m7 = (a20*b05 - a22*b02 + a23*b01)*invDet; + result.m8 = (a10*b10 - a11*b08 + a13*b06)*invDet; + result.m9 = (-a00*b10 + a01*b08 - a03*b06)*invDet; + result.m10 = (a30*b04 - a31*b02 + a33*b00)*invDet; + result.m11 = (-a20*b04 + a21*b02 - a23*b00)*invDet; + result.m12 = (-a10*b09 + a11*b07 - a12*b06)*invDet; + result.m13 = (a00*b09 - a01*b07 + a02*b06)*invDet; + result.m14 = (-a30*b03 + a31*b01 - a32*b00)*invDet; + result.m15 = (a20*b03 - a21*b01 + a22*b00)*invDet; + + return result; +} + +// Get identity matrix +RMAPI Matrix MatrixIdentity(void) +{ + Matrix result = { 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f }; + + return result; +} + +// Add two matrices +RMAPI Matrix MatrixAdd(Matrix left, Matrix right) +{ + Matrix result = { 0 }; + + result.m0 = left.m0 + right.m0; + result.m1 = left.m1 + right.m1; + result.m2 = left.m2 + right.m2; + result.m3 = left.m3 + right.m3; + result.m4 = left.m4 + right.m4; + result.m5 = left.m5 + right.m5; + result.m6 = left.m6 + right.m6; + result.m7 = left.m7 + right.m7; + result.m8 = left.m8 + right.m8; + result.m9 = left.m9 + right.m9; + result.m10 = left.m10 + right.m10; + result.m11 = left.m11 + right.m11; + result.m12 = left.m12 + right.m12; + result.m13 = left.m13 + right.m13; + result.m14 = left.m14 + right.m14; + result.m15 = left.m15 + right.m15; + + return result; +} + +// Subtract two matrices (left - right) +RMAPI Matrix MatrixSubtract(Matrix left, Matrix right) +{ + Matrix result = { 0 }; + + result.m0 = left.m0 - right.m0; + result.m1 = left.m1 - right.m1; + result.m2 = left.m2 - right.m2; + result.m3 = left.m3 - right.m3; + result.m4 = left.m4 - right.m4; + result.m5 = left.m5 - right.m5; + result.m6 = left.m6 - right.m6; + result.m7 = left.m7 - right.m7; + result.m8 = left.m8 - right.m8; + result.m9 = left.m9 - right.m9; + result.m10 = left.m10 - right.m10; + result.m11 = left.m11 - right.m11; + result.m12 = left.m12 - right.m12; + result.m13 = left.m13 - right.m13; + result.m14 = left.m14 - right.m14; + result.m15 = left.m15 - right.m15; + + return result; +} + +// Get two matrix multiplication +// NOTE: When multiplying matrices... the order matters! +RMAPI Matrix MatrixMultiply(Matrix left, Matrix right) +{ + Matrix result = { 0 }; + + result.m0 = left.m0*right.m0 + left.m1*right.m4 + left.m2*right.m8 + left.m3*right.m12; + result.m1 = left.m0*right.m1 + left.m1*right.m5 + left.m2*right.m9 + left.m3*right.m13; + result.m2 = left.m0*right.m2 + left.m1*right.m6 + left.m2*right.m10 + left.m3*right.m14; + result.m3 = left.m0*right.m3 + left.m1*right.m7 + left.m2*right.m11 + left.m3*right.m15; + result.m4 = left.m4*right.m0 + left.m5*right.m4 + left.m6*right.m8 + left.m7*right.m12; + result.m5 = left.m4*right.m1 + left.m5*right.m5 + left.m6*right.m9 + left.m7*right.m13; + result.m6 = left.m4*right.m2 + left.m5*right.m6 + left.m6*right.m10 + left.m7*right.m14; + result.m7 = left.m4*right.m3 + left.m5*right.m7 + left.m6*right.m11 + left.m7*right.m15; + result.m8 = left.m8*right.m0 + left.m9*right.m4 + left.m10*right.m8 + left.m11*right.m12; + result.m9 = left.m8*right.m1 + left.m9*right.m5 + left.m10*right.m9 + left.m11*right.m13; + result.m10 = left.m8*right.m2 + left.m9*right.m6 + left.m10*right.m10 + left.m11*right.m14; + result.m11 = left.m8*right.m3 + left.m9*right.m7 + left.m10*right.m11 + left.m11*right.m15; + result.m12 = left.m12*right.m0 + left.m13*right.m4 + left.m14*right.m8 + left.m15*right.m12; + result.m13 = left.m12*right.m1 + left.m13*right.m5 + left.m14*right.m9 + left.m15*right.m13; + result.m14 = left.m12*right.m2 + left.m13*right.m6 + left.m14*right.m10 + left.m15*right.m14; + result.m15 = left.m12*right.m3 + left.m13*right.m7 + left.m14*right.m11 + left.m15*right.m15; + + return result; +} + +// Get translation matrix +RMAPI Matrix MatrixTranslate(float x, float y, float z) +{ + Matrix result = { 1.0f, 0.0f, 0.0f, x, + 0.0f, 1.0f, 0.0f, y, + 0.0f, 0.0f, 1.0f, z, + 0.0f, 0.0f, 0.0f, 1.0f }; + + return result; +} + +// Create rotation matrix from axis and angle +// NOTE: Angle should be provided in radians +RMAPI Matrix MatrixRotate(Vector3 axis, float angle) +{ + Matrix result = { 0 }; + + float x = axis.x, y = axis.y, z = axis.z; + + float lengthSquared = x*x + y*y + z*z; + + if ((lengthSquared != 1.0f) && (lengthSquared != 0.0f)) + { + float ilength = 1.0f/sqrtf(lengthSquared); + x *= ilength; + y *= ilength; + z *= ilength; + } + + float sinres = sinf(angle); + float cosres = cosf(angle); + float t = 1.0f - cosres; + + result.m0 = x*x*t + cosres; + result.m1 = y*x*t + z*sinres; + result.m2 = z*x*t - y*sinres; + result.m3 = 0.0f; + + result.m4 = x*y*t - z*sinres; + result.m5 = y*y*t + cosres; + result.m6 = z*y*t + x*sinres; + result.m7 = 0.0f; + + result.m8 = x*z*t + y*sinres; + result.m9 = y*z*t - x*sinres; + result.m10 = z*z*t + cosres; + result.m11 = 0.0f; + + result.m12 = 0.0f; + result.m13 = 0.0f; + result.m14 = 0.0f; + result.m15 = 1.0f; + + return result; +} + +// Get x-rotation matrix +// NOTE: Angle must be provided in radians +RMAPI Matrix MatrixRotateX(float angle) +{ + Matrix result = { 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f }; // MatrixIdentity() + + float cosres = cosf(angle); + float sinres = sinf(angle); + + result.m5 = cosres; + result.m6 = sinres; + result.m9 = -sinres; + result.m10 = cosres; + + return result; +} + +// Get y-rotation matrix +// NOTE: Angle must be provided in radians +RMAPI Matrix MatrixRotateY(float angle) +{ + Matrix result = { 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f }; // MatrixIdentity() + + float cosres = cosf(angle); + float sinres = sinf(angle); + + result.m0 = cosres; + result.m2 = -sinres; + result.m8 = sinres; + result.m10 = cosres; + + return result; +} + +// Get z-rotation matrix +// NOTE: Angle must be provided in radians +RMAPI Matrix MatrixRotateZ(float angle) +{ + Matrix result = { 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f }; // MatrixIdentity() + + float cosres = cosf(angle); + float sinres = sinf(angle); + + result.m0 = cosres; + result.m1 = sinres; + result.m4 = -sinres; + result.m5 = cosres; + + return result; +} + + +// Get xyz-rotation matrix +// NOTE: Angle must be provided in radians +RMAPI Matrix MatrixRotateXYZ(Vector3 angle) +{ + Matrix result = { 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f }; // MatrixIdentity() + + float cosz = cosf(-angle.z); + float sinz = sinf(-angle.z); + float cosy = cosf(-angle.y); + float siny = sinf(-angle.y); + float cosx = cosf(-angle.x); + float sinx = sinf(-angle.x); + + result.m0 = cosz*cosy; + result.m1 = (cosz*siny*sinx) - (sinz*cosx); + result.m2 = (cosz*siny*cosx) + (sinz*sinx); + + result.m4 = sinz*cosy; + result.m5 = (sinz*siny*sinx) + (cosz*cosx); + result.m6 = (sinz*siny*cosx) - (cosz*sinx); + + result.m8 = -siny; + result.m9 = cosy*sinx; + result.m10= cosy*cosx; + + return result; +} + +// Get zyx-rotation matrix +// NOTE: Angle must be provided in radians +RMAPI Matrix MatrixRotateZYX(Vector3 angle) +{ + Matrix result = { 0 }; + + float cz = cosf(angle.z); + float sz = sinf(angle.z); + float cy = cosf(angle.y); + float sy = sinf(angle.y); + float cx = cosf(angle.x); + float sx = sinf(angle.x); + + result.m0 = cz*cy; + result.m4 = cz*sy*sx - cx*sz; + result.m8 = sz*sx + cz*cx*sy; + result.m12 = 0; + + result.m1 = cy*sz; + result.m5 = cz*cx + sz*sy*sx; + result.m9 = cx*sz*sy - cz*sx; + result.m13 = 0; + + result.m2 = -sy; + result.m6 = cy*sx; + result.m10 = cy*cx; + result.m14 = 0; + + result.m3 = 0; + result.m7 = 0; + result.m11 = 0; + result.m15 = 1; + + return result; +} + +// Get scaling matrix +RMAPI Matrix MatrixScale(float x, float y, float z) +{ + Matrix result = { x, 0.0f, 0.0f, 0.0f, + 0.0f, y, 0.0f, 0.0f, + 0.0f, 0.0f, z, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f }; + + return result; +} + +// Get perspective projection matrix +RMAPI Matrix MatrixFrustum(double left, double right, double bottom, double top, double nearPlane, double farPlane) +{ + Matrix result = { 0 }; + + float rl = (float)(right - left); + float tb = (float)(top - bottom); + float fn = (float)(farPlane - nearPlane); + + result.m0 = ((float)nearPlane*2.0f)/rl; + result.m1 = 0.0f; + result.m2 = 0.0f; + result.m3 = 0.0f; + + result.m4 = 0.0f; + result.m5 = ((float)nearPlane*2.0f)/tb; + result.m6 = 0.0f; + result.m7 = 0.0f; + + result.m8 = ((float)right + (float)left)/rl; + result.m9 = ((float)top + (float)bottom)/tb; + result.m10 = -((float)farPlane + (float)nearPlane)/fn; + result.m11 = -1.0f; + + result.m12 = 0.0f; + result.m13 = 0.0f; + result.m14 = -((float)farPlane*(float)nearPlane*2.0f)/fn; + result.m15 = 0.0f; + + return result; +} + +// Get perspective projection matrix +// NOTE: Fovy angle must be provided in radians +RMAPI Matrix MatrixPerspective(double fovY, double aspect, double nearPlane, double farPlane) +{ + Matrix result = { 0 }; + + double top = nearPlane*tan(fovY*0.5); + double bottom = -top; + double right = top*aspect; + double left = -right; + + // MatrixFrustum(-right, right, -top, top, near, far); + float rl = (float)(right - left); + float tb = (float)(top - bottom); + float fn = (float)(farPlane - nearPlane); + + result.m0 = ((float)nearPlane*2.0f)/rl; + result.m5 = ((float)nearPlane*2.0f)/tb; + result.m8 = ((float)right + (float)left)/rl; + result.m9 = ((float)top + (float)bottom)/tb; + result.m10 = -((float)farPlane + (float)nearPlane)/fn; + result.m11 = -1.0f; + result.m14 = -((float)farPlane*(float)nearPlane*2.0f)/fn; + + return result; +} + +// Get orthographic projection matrix +RMAPI Matrix MatrixOrtho(double left, double right, double bottom, double top, double nearPlane, double farPlane) +{ + Matrix result = { 0 }; + + float rl = (float)(right - left); + float tb = (float)(top - bottom); + float fn = (float)(farPlane - nearPlane); + + result.m0 = 2.0f/rl; + result.m1 = 0.0f; + result.m2 = 0.0f; + result.m3 = 0.0f; + result.m4 = 0.0f; + result.m5 = 2.0f/tb; + result.m6 = 0.0f; + result.m7 = 0.0f; + result.m8 = 0.0f; + result.m9 = 0.0f; + result.m10 = -2.0f/fn; + result.m11 = 0.0f; + result.m12 = -((float)left + (float)right)/rl; + result.m13 = -((float)top + (float)bottom)/tb; + result.m14 = -((float)farPlane + (float)nearPlane)/fn; + result.m15 = 1.0f; + + return result; +} + +// Get camera look-at matrix (view matrix) +RMAPI Matrix MatrixLookAt(Vector3 eye, Vector3 target, Vector3 up) +{ + Matrix result = { 0 }; + + float length = 0.0f; + float ilength = 0.0f; + + // Vector3Subtract(eye, target) + Vector3 vz = { eye.x - target.x, eye.y - target.y, eye.z - target.z }; + + // Vector3Normalize(vz) + Vector3 v = vz; + length = sqrtf(v.x*v.x + v.y*v.y + v.z*v.z); + if (length == 0.0f) length = 1.0f; + ilength = 1.0f/length; + vz.x *= ilength; + vz.y *= ilength; + vz.z *= ilength; + + // Vector3CrossProduct(up, vz) + Vector3 vx = { up.y*vz.z - up.z*vz.y, up.z*vz.x - up.x*vz.z, up.x*vz.y - up.y*vz.x }; + + // Vector3Normalize(x) + v = vx; + length = sqrtf(v.x*v.x + v.y*v.y + v.z*v.z); + if (length == 0.0f) length = 1.0f; + ilength = 1.0f/length; + vx.x *= ilength; + vx.y *= ilength; + vx.z *= ilength; + + // Vector3CrossProduct(vz, vx) + Vector3 vy = { vz.y*vx.z - vz.z*vx.y, vz.z*vx.x - vz.x*vx.z, vz.x*vx.y - vz.y*vx.x }; + + result.m0 = vx.x; + result.m1 = vy.x; + result.m2 = vz.x; + result.m3 = 0.0f; + result.m4 = vx.y; + result.m5 = vy.y; + result.m6 = vz.y; + result.m7 = 0.0f; + result.m8 = vx.z; + result.m9 = vy.z; + result.m10 = vz.z; + result.m11 = 0.0f; + result.m12 = -(vx.x*eye.x + vx.y*eye.y + vx.z*eye.z); // Vector3DotProduct(vx, eye) + result.m13 = -(vy.x*eye.x + vy.y*eye.y + vy.z*eye.z); // Vector3DotProduct(vy, eye) + result.m14 = -(vz.x*eye.x + vz.y*eye.y + vz.z*eye.z); // Vector3DotProduct(vz, eye) + result.m15 = 1.0f; + + return result; +} + +// Get float array of matrix data +RMAPI float16 MatrixToFloatV(Matrix mat) +{ + float16 result = { 0 }; + + result.v[0] = mat.m0; + result.v[1] = mat.m1; + result.v[2] = mat.m2; + result.v[3] = mat.m3; + result.v[4] = mat.m4; + result.v[5] = mat.m5; + result.v[6] = mat.m6; + result.v[7] = mat.m7; + result.v[8] = mat.m8; + result.v[9] = mat.m9; + result.v[10] = mat.m10; + result.v[11] = mat.m11; + result.v[12] = mat.m12; + result.v[13] = mat.m13; + result.v[14] = mat.m14; + result.v[15] = mat.m15; + + return result; +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Quaternion math +//---------------------------------------------------------------------------------- + +// Add two quaternions +RMAPI Quaternion QuaternionAdd(Quaternion q1, Quaternion q2) +{ + Quaternion result = {q1.x + q2.x, q1.y + q2.y, q1.z + q2.z, q1.w + q2.w}; + + return result; +} + +// Add quaternion and float value +RMAPI Quaternion QuaternionAddValue(Quaternion q, float add) +{ + Quaternion result = {q.x + add, q.y + add, q.z + add, q.w + add}; + + return result; +} + +// Subtract two quaternions +RMAPI Quaternion QuaternionSubtract(Quaternion q1, Quaternion q2) +{ + Quaternion result = {q1.x - q2.x, q1.y - q2.y, q1.z - q2.z, q1.w - q2.w}; + + return result; +} + +// Subtract quaternion and float value +RMAPI Quaternion QuaternionSubtractValue(Quaternion q, float sub) +{ + Quaternion result = {q.x - sub, q.y - sub, q.z - sub, q.w - sub}; + + return result; +} + +// Get identity quaternion +RMAPI Quaternion QuaternionIdentity(void) +{ + Quaternion result = { 0.0f, 0.0f, 0.0f, 1.0f }; + + return result; +} + +// Computes the length of a quaternion +RMAPI float QuaternionLength(Quaternion q) +{ + float result = sqrtf(q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w); + + return result; +} + +// Normalize provided quaternion +RMAPI Quaternion QuaternionNormalize(Quaternion q) +{ + Quaternion result = { 0 }; + + float length = sqrtf(q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w); + if (length == 0.0f) length = 1.0f; + float ilength = 1.0f/length; + + result.x = q.x*ilength; + result.y = q.y*ilength; + result.z = q.z*ilength; + result.w = q.w*ilength; + + return result; +} + +// Invert provided quaternion +RMAPI Quaternion QuaternionInvert(Quaternion q) +{ + Quaternion result = q; + + float lengthSq = q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w; + + if (lengthSq != 0.0f) + { + float invLength = 1.0f/lengthSq; + + result.x *= -invLength; + result.y *= -invLength; + result.z *= -invLength; + result.w *= invLength; + } + + return result; +} + +// Calculate two quaternion multiplication +RMAPI Quaternion QuaternionMultiply(Quaternion q1, Quaternion q2) +{ + Quaternion result = { 0 }; + + float qax = q1.x, qay = q1.y, qaz = q1.z, qaw = q1.w; + float qbx = q2.x, qby = q2.y, qbz = q2.z, qbw = q2.w; + + result.x = qax*qbw + qaw*qbx + qay*qbz - qaz*qby; + result.y = qay*qbw + qaw*qby + qaz*qbx - qax*qbz; + result.z = qaz*qbw + qaw*qbz + qax*qby - qay*qbx; + result.w = qaw*qbw - qax*qbx - qay*qby - qaz*qbz; + + return result; +} + +// Scale quaternion by float value +RMAPI Quaternion QuaternionScale(Quaternion q, float mul) +{ + Quaternion result = { 0 }; + + result.x = q.x*mul; + result.y = q.y*mul; + result.z = q.z*mul; + result.w = q.w*mul; + + return result; +} + +// Divide two quaternions +RMAPI Quaternion QuaternionDivide(Quaternion q1, Quaternion q2) +{ + Quaternion result = { q1.x/q2.x, q1.y/q2.y, q1.z/q2.z, q1.w/q2.w }; + + return result; +} + +// Calculate linear interpolation between two quaternions +RMAPI Quaternion QuaternionLerp(Quaternion q1, Quaternion q2, float amount) +{ + Quaternion result = { 0 }; + + result.x = q1.x + amount*(q2.x - q1.x); + result.y = q1.y + amount*(q2.y - q1.y); + result.z = q1.z + amount*(q2.z - q1.z); + result.w = q1.w + amount*(q2.w - q1.w); + + return result; +} + +// Calculate slerp-optimized interpolation between two quaternions +RMAPI Quaternion QuaternionNlerp(Quaternion q1, Quaternion q2, float amount) +{ + Quaternion result = { 0 }; + + // QuaternionLerp(q1, q2, amount) + result.x = q1.x + amount*(q2.x - q1.x); + result.y = q1.y + amount*(q2.y - q1.y); + result.z = q1.z + amount*(q2.z - q1.z); + result.w = q1.w + amount*(q2.w - q1.w); + + // QuaternionNormalize(q); + Quaternion q = result; + float length = sqrtf(q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w); + if (length == 0.0f) length = 1.0f; + float ilength = 1.0f/length; + + result.x = q.x*ilength; + result.y = q.y*ilength; + result.z = q.z*ilength; + result.w = q.w*ilength; + + return result; +} + +// Calculates spherical linear interpolation between two quaternions +RMAPI Quaternion QuaternionSlerp(Quaternion q1, Quaternion q2, float amount) +{ + Quaternion result = { 0 }; + +#if !defined(EPSILON) + #define EPSILON 0.000001f +#endif + + float cosHalfTheta = q1.x*q2.x + q1.y*q2.y + q1.z*q2.z + q1.w*q2.w; + + if (cosHalfTheta < 0) + { + q2.x = -q2.x; q2.y = -q2.y; q2.z = -q2.z; q2.w = -q2.w; + cosHalfTheta = -cosHalfTheta; + } + + if (fabsf(cosHalfTheta) >= 1.0f) result = q1; + else if (cosHalfTheta > 0.95f) result = QuaternionNlerp(q1, q2, amount); + else + { + float halfTheta = acosf(cosHalfTheta); + float sinHalfTheta = sqrtf(1.0f - cosHalfTheta*cosHalfTheta); + + if (fabsf(sinHalfTheta) < EPSILON) + { + result.x = (q1.x*0.5f + q2.x*0.5f); + result.y = (q1.y*0.5f + q2.y*0.5f); + result.z = (q1.z*0.5f + q2.z*0.5f); + result.w = (q1.w*0.5f + q2.w*0.5f); + } + else + { + float ratioA = sinf((1 - amount)*halfTheta)/sinHalfTheta; + float ratioB = sinf(amount*halfTheta)/sinHalfTheta; + + result.x = (q1.x*ratioA + q2.x*ratioB); + result.y = (q1.y*ratioA + q2.y*ratioB); + result.z = (q1.z*ratioA + q2.z*ratioB); + result.w = (q1.w*ratioA + q2.w*ratioB); + } + } + + return result; +} + +// Calculate quaternion cubic spline interpolation using Cubic Hermite Spline algorithm +// as described in the GLTF 2.0 specification: https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#interpolation-cubic +RMAPI Quaternion QuaternionCubicHermiteSpline(Quaternion q1, Quaternion outTangent1, Quaternion q2, Quaternion inTangent2, float t) +{ + float t2 = t*t; + float t3 = t2*t; + float h00 = 2*t3 - 3*t2 + 1; + float h10 = t3 - 2*t2 + t; + float h01 = -2*t3 + 3*t2; + float h11 = t3 - t2; + + Quaternion p0 = QuaternionScale(q1, h00); + Quaternion m0 = QuaternionScale(outTangent1, h10); + Quaternion p1 = QuaternionScale(q2, h01); + Quaternion m1 = QuaternionScale(inTangent2, h11); + + Quaternion result = { 0 }; + + result = QuaternionAdd(p0, m0); + result = QuaternionAdd(result, p1); + result = QuaternionAdd(result, m1); + result = QuaternionNormalize(result); + + return result; +} + +// Calculate quaternion based on the rotation from one vector to another +RMAPI Quaternion QuaternionFromVector3ToVector3(Vector3 from, Vector3 to) +{ + Quaternion result = { 0 }; + + float cos2Theta = (from.x*to.x + from.y*to.y + from.z*to.z); // Vector3DotProduct(from, to) + Vector3 cross = { from.y*to.z - from.z*to.y, from.z*to.x - from.x*to.z, from.x*to.y - from.y*to.x }; // Vector3CrossProduct(from, to) + + result.x = cross.x; + result.y = cross.y; + result.z = cross.z; + result.w = 1.0f + cos2Theta; + + // QuaternionNormalize(q); + // NOTE: Normalize to essentially nlerp the original and identity to 0.5 + Quaternion q = result; + float length = sqrtf(q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w); + if (length == 0.0f) length = 1.0f; + float ilength = 1.0f/length; + + result.x = q.x*ilength; + result.y = q.y*ilength; + result.z = q.z*ilength; + result.w = q.w*ilength; + + return result; +} + +// Get a quaternion for a given rotation matrix +RMAPI Quaternion QuaternionFromMatrix(Matrix mat) +{ + Quaternion result = { 0 }; + + float fourWSquaredMinus1 = mat.m0 + mat.m5 + mat.m10; + float fourXSquaredMinus1 = mat.m0 - mat.m5 - mat.m10; + float fourYSquaredMinus1 = mat.m5 - mat.m0 - mat.m10; + float fourZSquaredMinus1 = mat.m10 - mat.m0 - mat.m5; + + int biggestIndex = 0; + float fourBiggestSquaredMinus1 = fourWSquaredMinus1; + if (fourXSquaredMinus1 > fourBiggestSquaredMinus1) + { + fourBiggestSquaredMinus1 = fourXSquaredMinus1; + biggestIndex = 1; + } + + if (fourYSquaredMinus1 > fourBiggestSquaredMinus1) + { + fourBiggestSquaredMinus1 = fourYSquaredMinus1; + biggestIndex = 2; + } + + if (fourZSquaredMinus1 > fourBiggestSquaredMinus1) + { + fourBiggestSquaredMinus1 = fourZSquaredMinus1; + biggestIndex = 3; + } + + float biggestVal = sqrtf(fourBiggestSquaredMinus1 + 1.0f)*0.5f; + float mult = 0.25f/biggestVal; + + switch (biggestIndex) + { + case 0: + result.w = biggestVal; + result.x = (mat.m6 - mat.m9)*mult; + result.y = (mat.m8 - mat.m2)*mult; + result.z = (mat.m1 - mat.m4)*mult; + break; + case 1: + result.x = biggestVal; + result.w = (mat.m6 - mat.m9)*mult; + result.y = (mat.m1 + mat.m4)*mult; + result.z = (mat.m8 + mat.m2)*mult; + break; + case 2: + result.y = biggestVal; + result.w = (mat.m8 - mat.m2)*mult; + result.x = (mat.m1 + mat.m4)*mult; + result.z = (mat.m6 + mat.m9)*mult; + break; + case 3: + result.z = biggestVal; + result.w = (mat.m1 - mat.m4)*mult; + result.x = (mat.m8 + mat.m2)*mult; + result.y = (mat.m6 + mat.m9)*mult; + break; + } + + return result; +} + +// Get a matrix for a given quaternion +RMAPI Matrix QuaternionToMatrix(Quaternion q) +{ + Matrix result = { 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f }; // MatrixIdentity() + + float a2 = q.x*q.x; + float b2 = q.y*q.y; + float c2 = q.z*q.z; + float ac = q.x*q.z; + float ab = q.x*q.y; + float bc = q.y*q.z; + float ad = q.w*q.x; + float bd = q.w*q.y; + float cd = q.w*q.z; + + result.m0 = 1 - 2*(b2 + c2); + result.m1 = 2*(ab + cd); + result.m2 = 2*(ac - bd); + + result.m4 = 2*(ab - cd); + result.m5 = 1 - 2*(a2 + c2); + result.m6 = 2*(bc + ad); + + result.m8 = 2*(ac + bd); + result.m9 = 2*(bc - ad); + result.m10 = 1 - 2*(a2 + b2); + + return result; +} + +// Get rotation quaternion for an angle and axis +// NOTE: Angle must be provided in radians +RMAPI Quaternion QuaternionFromAxisAngle(Vector3 axis, float angle) +{ + Quaternion result = { 0.0f, 0.0f, 0.0f, 1.0f }; + + float axisLength = sqrtf(axis.x*axis.x + axis.y*axis.y + axis.z*axis.z); + + if (axisLength != 0.0f) + { + angle *= 0.5f; + + float length = 0.0f; + float ilength = 0.0f; + + // Vector3Normalize(axis) + length = axisLength; + if (length == 0.0f) length = 1.0f; + ilength = 1.0f/length; + axis.x *= ilength; + axis.y *= ilength; + axis.z *= ilength; + + float sinres = sinf(angle); + float cosres = cosf(angle); + + result.x = axis.x*sinres; + result.y = axis.y*sinres; + result.z = axis.z*sinres; + result.w = cosres; + + // QuaternionNormalize(q); + Quaternion q = result; + length = sqrtf(q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w); + if (length == 0.0f) length = 1.0f; + ilength = 1.0f/length; + result.x = q.x*ilength; + result.y = q.y*ilength; + result.z = q.z*ilength; + result.w = q.w*ilength; + } + + return result; +} + +// Get the rotation angle and axis for a given quaternion +RMAPI void QuaternionToAxisAngle(Quaternion q, Vector3 *outAxis, float *outAngle) +{ + if (fabsf(q.w) > 1.0f) + { + // QuaternionNormalize(q); + float length = sqrtf(q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w); + if (length == 0.0f) length = 1.0f; + float ilength = 1.0f/length; + + q.x = q.x*ilength; + q.y = q.y*ilength; + q.z = q.z*ilength; + q.w = q.w*ilength; + } + + Vector3 resAxis = { 0.0f, 0.0f, 0.0f }; + float resAngle = 2.0f*acosf(q.w); + float den = sqrtf(1.0f - q.w*q.w); + + if (den > EPSILON) + { + resAxis.x = q.x/den; + resAxis.y = q.y/den; + resAxis.z = q.z/den; + } + else + { + // This occurs when the angle is zero. + // Not a problem: just set an arbitrary normalized axis. + resAxis.x = 1.0f; + } + + *outAxis = resAxis; + *outAngle = resAngle; +} + +// Get the quaternion equivalent to Euler angles +// NOTE: Rotation order is ZYX +RMAPI Quaternion QuaternionFromEuler(float pitch, float yaw, float roll) +{ + Quaternion result = { 0 }; + + float x0 = cosf(pitch*0.5f); + float x1 = sinf(pitch*0.5f); + float y0 = cosf(yaw*0.5f); + float y1 = sinf(yaw*0.5f); + float z0 = cosf(roll*0.5f); + float z1 = sinf(roll*0.5f); + + result.x = x1*y0*z0 - x0*y1*z1; + result.y = x0*y1*z0 + x1*y0*z1; + result.z = x0*y0*z1 - x1*y1*z0; + result.w = x0*y0*z0 + x1*y1*z1; + + return result; +} + +// Get the Euler angles equivalent to quaternion (roll, pitch, yaw) +// NOTE: Angles are returned in a Vector3 struct in radians +RMAPI Vector3 QuaternionToEuler(Quaternion q) +{ + Vector3 result = { 0 }; + + // Roll (x-axis rotation) + float x0 = 2.0f*(q.w*q.x + q.y*q.z); + float x1 = 1.0f - 2.0f*(q.x*q.x + q.y*q.y); + result.x = atan2f(x0, x1); + + // Pitch (y-axis rotation) + float y0 = 2.0f*(q.w*q.y - q.z*q.x); + y0 = y0 > 1.0f ? 1.0f : y0; + y0 = y0 < -1.0f ? -1.0f : y0; + result.y = asinf(y0); + + // Yaw (z-axis rotation) + float z0 = 2.0f*(q.w*q.z + q.x*q.y); + float z1 = 1.0f - 2.0f*(q.y*q.y + q.z*q.z); + result.z = atan2f(z0, z1); + + return result; +} + +// Transform a quaternion given a transformation matrix +RMAPI Quaternion QuaternionTransform(Quaternion q, Matrix mat) +{ + Quaternion result = { 0 }; + + result.x = mat.m0*q.x + mat.m4*q.y + mat.m8*q.z + mat.m12*q.w; + result.y = mat.m1*q.x + mat.m5*q.y + mat.m9*q.z + mat.m13*q.w; + result.z = mat.m2*q.x + mat.m6*q.y + mat.m10*q.z + mat.m14*q.w; + result.w = mat.m3*q.x + mat.m7*q.y + mat.m11*q.z + mat.m15*q.w; + + return result; +} + +// Check whether two given quaternions are almost equal +RMAPI int QuaternionEquals(Quaternion p, Quaternion q) +{ +#if !defined(EPSILON) + #define EPSILON 0.000001f +#endif + + int result = (((fabsf(p.x - q.x)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.x), fabsf(q.x))))) && + ((fabsf(p.y - q.y)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.y), fabsf(q.y))))) && + ((fabsf(p.z - q.z)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.z), fabsf(q.z))))) && + ((fabsf(p.w - q.w)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.w), fabsf(q.w)))))) || + (((fabsf(p.x + q.x)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.x), fabsf(q.x))))) && + ((fabsf(p.y + q.y)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.y), fabsf(q.y))))) && + ((fabsf(p.z + q.z)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.z), fabsf(q.z))))) && + ((fabsf(p.w + q.w)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.w), fabsf(q.w)))))); + + return result; +} + +// Decompose a transformation matrix into its rotational, translational and scaling components +RMAPI void MatrixDecompose(Matrix mat, Vector3 *translation, Quaternion *rotation, Vector3 *scale) +{ + // Extract translation. + translation->x = mat.m12; + translation->y = mat.m13; + translation->z = mat.m14; + + // Extract upper-left for determinant computation + const float a = mat.m0; + const float b = mat.m4; + const float c = mat.m8; + const float d = mat.m1; + const float e = mat.m5; + const float f = mat.m9; + const float g = mat.m2; + const float h = mat.m6; + const float i = mat.m10; + const float A = e*i - f*h; + const float B = f*g - d*i; + const float C = d*h - e*g; + + // Extract scale + const float det = a*A + b*B + c*C; + Vector3 abc = { a, b, c }; + Vector3 def = { d, e, f }; + Vector3 ghi = { g, h, i }; + + float scalex = Vector3Length(abc); + float scaley = Vector3Length(def); + float scalez = Vector3Length(ghi); + Vector3 s = { scalex, scaley, scalez }; + + if (det < 0) s = Vector3Negate(s); + + *scale = s; + + // Remove scale from the matrix if it is not close to zero + Matrix clone = mat; + if (!FloatEquals(det, 0)) + { + clone.m0 /= s.x; + clone.m4 /= s.x; + clone.m8 /= s.x; + clone.m1 /= s.y; + clone.m5 /= s.y; + clone.m9 /= s.y; + clone.m2 /= s.z; + clone.m6 /= s.z; + clone.m10 /= s.z; + + // Extract rotation + *rotation = QuaternionFromMatrix(clone); + } + else + { + // Set to identity if close to zero + *rotation = QuaternionIdentity(); + } +} + +#if defined(__cplusplus) && !defined(RAYMATH_DISABLE_CPP_OPERATORS) + +// Optional C++ math operators +//------------------------------------------------------------------------------- + +// Vector2 operators +static constexpr Vector2 Vector2Zeros = { 0, 0 }; +static constexpr Vector2 Vector2Ones = { 1, 1 }; +static constexpr Vector2 Vector2UnitX = { 1, 0 }; +static constexpr Vector2 Vector2UnitY = { 0, 1 }; + +inline Vector2 operator + (const Vector2& lhs, const Vector2& rhs) +{ + return Vector2Add(lhs, rhs); +} + +inline const Vector2& operator += (Vector2& lhs, const Vector2& rhs) +{ + lhs = Vector2Add(lhs, rhs); + return lhs; +} + +inline Vector2 operator - (const Vector2& lhs, const Vector2& rhs) +{ + return Vector2Subtract(lhs, rhs); +} + +inline const Vector2& operator -= (Vector2& lhs, const Vector2& rhs) +{ + lhs = Vector2Subtract(lhs, rhs); + return lhs; +} + +inline Vector2 operator * (const Vector2& lhs, const float& rhs) +{ + return Vector2Scale(lhs, rhs); +} + +inline const Vector2& operator *= (Vector2& lhs, const float& rhs) +{ + lhs = Vector2Scale(lhs, rhs); + return lhs; +} + +inline Vector2 operator * (const Vector2& lhs, const Vector2& rhs) +{ + return Vector2Multiply(lhs, rhs); +} + +inline const Vector2& operator *= (Vector2& lhs, const Vector2& rhs) +{ + lhs = Vector2Multiply(lhs, rhs); + return lhs; +} + +inline Vector2 operator * (const Vector2& lhs, const Matrix& rhs) +{ + return Vector2Transform(lhs, rhs); +} + +inline const Vector2& operator -= (Vector2& lhs, const Matrix& rhs) +{ + lhs = Vector2Transform(lhs, rhs); + return lhs; +} + +inline Vector2 operator / (const Vector2& lhs, const float& rhs) +{ + return Vector2Scale(lhs, 1.0f / rhs); +} + +inline const Vector2& operator /= (Vector2& lhs, const float& rhs) +{ + lhs = Vector2Scale(lhs, rhs); + return lhs; +} + +inline Vector2 operator / (const Vector2& lhs, const Vector2& rhs) +{ + return Vector2Divide(lhs, rhs); +} + +inline const Vector2& operator /= (Vector2& lhs, const Vector2& rhs) +{ + lhs = Vector2Divide(lhs, rhs); + return lhs; +} + +inline bool operator == (const Vector2& lhs, const Vector2& rhs) +{ + return FloatEquals(lhs.x, rhs.x) && FloatEquals(lhs.y, rhs.y); +} + +inline bool operator != (const Vector2& lhs, const Vector2& rhs) +{ + return !FloatEquals(lhs.x, rhs.x) || !FloatEquals(lhs.y, rhs.y); +} + +// Vector3 operators +static constexpr Vector3 Vector3Zeros = { 0, 0, 0 }; +static constexpr Vector3 Vector3Ones = { 1, 1, 1 }; +static constexpr Vector3 Vector3UnitX = { 1, 0, 0 }; +static constexpr Vector3 Vector3UnitY = { 0, 1, 0 }; +static constexpr Vector3 Vector3UnitZ = { 0, 0, 1 }; + +inline Vector3 operator + (const Vector3& lhs, const Vector3& rhs) +{ + return Vector3Add(lhs, rhs); +} + +inline const Vector3& operator += (Vector3& lhs, const Vector3& rhs) +{ + lhs = Vector3Add(lhs, rhs); + return lhs; +} + +inline Vector3 operator - (const Vector3& lhs, const Vector3& rhs) +{ + return Vector3Subtract(lhs, rhs); +} + +inline const Vector3& operator -= (Vector3& lhs, const Vector3& rhs) +{ + lhs = Vector3Subtract(lhs, rhs); + return lhs; +} + +inline Vector3 operator * (const Vector3& lhs, const float& rhs) +{ + return Vector3Scale(lhs, rhs); +} + +inline const Vector3& operator *= (Vector3& lhs, const float& rhs) +{ + lhs = Vector3Scale(lhs, rhs); + return lhs; +} + +inline Vector3 operator * (const Vector3& lhs, const Vector3& rhs) +{ + return Vector3Multiply(lhs, rhs); +} + +inline const Vector3& operator *= (Vector3& lhs, const Vector3& rhs) +{ + lhs = Vector3Multiply(lhs, rhs); + return lhs; +} + +inline Vector3 operator * (const Vector3& lhs, const Matrix& rhs) +{ + return Vector3Transform(lhs, rhs); +} + +inline const Vector3& operator -= (Vector3& lhs, const Matrix& rhs) +{ + lhs = Vector3Transform(lhs, rhs); + return lhs; +} + +inline Vector3 operator / (const Vector3& lhs, const float& rhs) +{ + return Vector3Scale(lhs, 1.0f / rhs); +} + +inline const Vector3& operator /= (Vector3& lhs, const float& rhs) +{ + lhs = Vector3Scale(lhs, rhs); + return lhs; +} + +inline Vector3 operator / (const Vector3& lhs, const Vector3& rhs) +{ + return Vector3Divide(lhs, rhs); +} + +inline const Vector3& operator /= (Vector3& lhs, const Vector3& rhs) +{ + lhs = Vector3Divide(lhs, rhs); + return lhs; +} + +inline bool operator == (const Vector3& lhs, const Vector3& rhs) +{ + return FloatEquals(lhs.x, rhs.x) && FloatEquals(lhs.y, rhs.y) && FloatEquals(lhs.z, rhs.z); +} + +inline bool operator != (const Vector3& lhs, const Vector3& rhs) +{ + return !FloatEquals(lhs.x, rhs.x) || !FloatEquals(lhs.y, rhs.y) || !FloatEquals(lhs.z, rhs.z); +} + +// Vector4 operators +static constexpr Vector4 Vector4Zeros = { 0, 0, 0, 0 }; +static constexpr Vector4 Vector4Ones = { 1, 1, 1, 1 }; +static constexpr Vector4 Vector4UnitX = { 1, 0, 0, 0 }; +static constexpr Vector4 Vector4UnitY = { 0, 1, 0, 0 }; +static constexpr Vector4 Vector4UnitZ = { 0, 0, 1, 0 }; +static constexpr Vector4 Vector4UnitW = { 0, 0, 0, 1 }; + +inline Vector4 operator + (const Vector4& lhs, const Vector4& rhs) +{ + return Vector4Add(lhs, rhs); +} + +inline const Vector4& operator += (Vector4& lhs, const Vector4& rhs) +{ + lhs = Vector4Add(lhs, rhs); + return lhs; +} + +inline Vector4 operator - (const Vector4& lhs, const Vector4& rhs) +{ + return Vector4Subtract(lhs, rhs); +} + +inline const Vector4& operator -= (Vector4& lhs, const Vector4& rhs) +{ + lhs = Vector4Subtract(lhs, rhs); + return lhs; +} + +inline Vector4 operator * (const Vector4& lhs, const float& rhs) +{ + return Vector4Scale(lhs, rhs); +} + +inline const Vector4& operator *= (Vector4& lhs, const float& rhs) +{ + lhs = Vector4Scale(lhs, rhs); + return lhs; +} + +inline Vector4 operator * (const Vector4& lhs, const Vector4& rhs) +{ + return Vector4Multiply(lhs, rhs); +} + +inline const Vector4& operator *= (Vector4& lhs, const Vector4& rhs) +{ + lhs = Vector4Multiply(lhs, rhs); + return lhs; +} + +inline Vector4 operator / (const Vector4& lhs, const float& rhs) +{ + return Vector4Scale(lhs, 1.0f / rhs); +} + +inline const Vector4& operator /= (Vector4& lhs, const float& rhs) +{ + lhs = Vector4Scale(lhs, rhs); + return lhs; +} + +inline Vector4 operator / (const Vector4& lhs, const Vector4& rhs) +{ + return Vector4Divide(lhs, rhs); +} + +inline const Vector4& operator /= (Vector4& lhs, const Vector4& rhs) +{ + lhs = Vector4Divide(lhs, rhs); + return lhs; +} + +inline bool operator == (const Vector4& lhs, const Vector4& rhs) +{ + return FloatEquals(lhs.x, rhs.x) && FloatEquals(lhs.y, rhs.y) && FloatEquals(lhs.z, rhs.z) && FloatEquals(lhs.w, rhs.w); +} + +inline bool operator != (const Vector4& lhs, const Vector4& rhs) +{ + return !FloatEquals(lhs.x, rhs.x) || !FloatEquals(lhs.y, rhs.y) || !FloatEquals(lhs.z, rhs.z) || !FloatEquals(lhs.w, rhs.w); +} + +// Quaternion operators +static constexpr Quaternion QuaternionZeros = { 0, 0, 0, 0 }; +static constexpr Quaternion QuaternionOnes = { 1, 1, 1, 1 }; +static constexpr Quaternion QuaternionUnitX = { 0, 0, 0, 1 }; + +inline Quaternion operator + (const Quaternion& lhs, const float& rhs) +{ + return QuaternionAddValue(lhs, rhs); +} + +inline const Quaternion& operator += (Quaternion& lhs, const float& rhs) +{ + lhs = QuaternionAddValue(lhs, rhs); + return lhs; +} + +inline Quaternion operator - (const Quaternion& lhs, const float& rhs) +{ + return QuaternionSubtractValue(lhs, rhs); +} + +inline const Quaternion& operator -= (Quaternion& lhs, const float& rhs) +{ + lhs = QuaternionSubtractValue(lhs, rhs); + return lhs; +} + +inline Quaternion operator * (const Quaternion& lhs, const Matrix& rhs) +{ + return QuaternionTransform(lhs, rhs); +} + +inline const Quaternion& operator *= (Quaternion& lhs, const Matrix& rhs) +{ + lhs = QuaternionTransform(lhs, rhs); + return lhs; +} + +// Matrix operators +inline Matrix operator + (const Matrix& lhs, const Matrix& rhs) +{ + return MatrixAdd(lhs, rhs); +} + +inline const Matrix& operator += (Matrix& lhs, const Matrix& rhs) +{ + lhs = MatrixAdd(lhs, rhs); + return lhs; +} + +inline Matrix operator - (const Matrix& lhs, const Matrix& rhs) +{ + return MatrixSubtract(lhs, rhs); +} + +inline const Matrix& operator -= (Matrix& lhs, const Matrix& rhs) +{ + lhs = MatrixSubtract(lhs, rhs); + return lhs; +} + +inline Matrix operator * (const Matrix& lhs, const Matrix& rhs) +{ + return MatrixMultiply(lhs, rhs); +} + +inline const Matrix& operator *= (Matrix& lhs, const Matrix& rhs) +{ + lhs = MatrixMultiply(lhs, rhs); + return lhs; +} +//------------------------------------------------------------------------------- +#endif // C++ operators + +#endif // RAYMATH_H diff --git a/include/raytmx.h b/include/raytmx.h new file mode 100644 index 0000000..f446272 --- /dev/null +++ b/include/raytmx.h @@ -0,0 +1,4815 @@ +/* +Copyright (c) 2024-2026 Luke Philipsen + +Permission to use, copy, modify, and/or distribute this software for +any purpose with or without fee is hereby granted. + +THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL +WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE +FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY +DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN +AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +/* Usage + + Do this: + #define RAYTMX_IMPLEMENTATION + before you include this file in *one* C or C++ file to create the implementation. + + You can define RAYTMX_DEC with + #define RAYTMX_DEC static + or + #define RAYTMX_DEC extern + to specify raytmx function declarations as static or extern, respectively. + The default specifier is extern. +*/ + +#ifndef RAYTMX_H + #define RAYTMX_H + +#include <ctype.h> /* isspace() */ +#include <math.h> /* floor(), INFINITY */ +#include <stddef.h> /* NULL */ +#include <stdint.h> /* int32_t, uint32_t */ +#include <stdlib.h> /* atoi(), strtoul() */ +#include <string.h> /* memcpy(), memset(), strcpy(), strcpy_s() strlen(), strncpy(), strncpy_s() */ + +#include "raylib.h" +#include "rlgl.h" + +#ifndef RAYTMX_DEC + #define RAYTMX_DEC +#endif /* RAYTMX_DEC */ + +#ifdef __cplusplus + extern "C" { +#endif /* __cpluspus */ + +/***************/ +/* Definitions */ + +/** + * Function signature of raylib's LoadTexture() as a type. For use with SetLoadTextureTMX(). + */ +typedef Texture2D (*LoadTextureCallback)(const char* fileName); + +/** + * Bit flags passed to SetTraceLogFlagsTMX() that optionally disable the logging of specific TMX elements. + */ +enum tmx_log_flags { + LOG_SKIP_PROPERTIES = 1, /**< Skip <properties> and child <property> elements. */ + LOG_SKIP_LAYERS = 2, /**< Skip <layer>, <objectgroup>, <imagelayer>, and <group> layers and children thereof. */ + LOG_SKIP_TILE_LAYERS = 4, /**< Skip <layer> layers and children thereof. */ + LOG_SKIP_TILES = 8, /**< Skip tiles (GIDs) of tile layers (<layer>s). */ + LOG_SKIP_OBJECT_GROUPS = 16, /**< Skip <objectgroup> layers and children thereof. */ + LOG_SKIP_OBJECTS = 32, /**< Skip objects of object layers (<objectgroup>s). */ + LOG_SKIP_IMAGE_LAYERS = 64, /**< Skip <imagelayer> layers and children thereof. */ + LOG_SKIP_IMAGES = 128, /**< Skip images of image layers (<imagelayer>s). */ + LOG_SKIP_WANG_SETS = 256, /**< Skip <wangsets> and child <wangset> elements. */ + LOG_SKIP_WANG_TILES = 512 /**< Skip Wang tiles of Wang sets (<wangset>s). */ +}; + +/** + * Identifiers for the possible layer types. + */ +typedef enum tmx_layer_type { + LAYER_TYPE_TILE_LAYER = 0, /**< Layer containing a set number of tiles referenced by Global IDs (GIDs). */ + LAYER_TYPE_OBJECT_GROUP, /**< Layer containing an arbitrary number of various object types. */ + LAYER_TYPE_IMAGE_LAYER, /**< Layer consisting of a single image. */ + LAYER_TYPE_GROUP /**< Container layer, with no visuals of its own, that holds other, child layers. */ +} TmxLayerType; + +/** + * Identifiers for the possible property (data) types. + */ +typedef enum tmx_property_type { + PROPERTY_TYPE_STRING = 0, /**< String, or a sequence of characters. */ + PROPERTY_TYPE_INT, /**< Integer number. */ + PROPERTY_TYPE_FLOAT, /**< Floating-point number. */ + PROPERTY_TYPE_BOOL, /**< Boolean, true or false. */ + PROPERTY_TYPE_COLOR, /**< Color with red, green, and blue values and a possible alpha value. */ + PROPERTY_TYPE_FILE, /**< File name or path to a file as a string. */ + PROPERTY_TYPE_OBJECT /**< Object (<object>) within the map as an integer ID. */ +} TmxPropertyType; + +/** + * Identifiers for the possible draw orders applicable to object layers. + */ +typedef enum tmx_object_group_draw_order { + OBJECT_GROUP_DRAW_ORDER_TOP_DOWN = 0, /**< Drawn in ascending order by y-coordinate. */ + OBJECT_GROUP_DRAW_ORDER_INDEX /**< Drawn in the order in which the appear in the document. */ +} TmxObjectGroupDrawOrder; + +/** + * Identifiers for the possible alignments of tiles with an object's bounds. + */ +typedef enum tmx_object_alignment { + OBJECT_ALIGNMENT_UNSPECIFIED = 0, /**< For orthogonal, behaves like bottom-left. For isometric, like bottm. */ + OBJECT_ALIGNMENT_TOP_LEFT, /**< Tiles are snapped to the upper-left bound of objects. */ + OBJECT_ALIGNMENT_TOP, /**< Tiles are snapped to the upper-center bound of objects. */ + OBJECT_ALIGNMENT_TOP_RIGHT, /**< Tiles are snapped to the upper-right bound of objects. */ + OBJECT_ALIGNMENT_LEFT, /**< Tiles are snapped to the left-center bound of objects. */ + OBJECT_ALIGNMENT_CENTER, /**< Tiles are snapped to the horizontal and vertical center of objects. */ + OBJECT_ALIGNMENT_RIGHT, /**< Tiles are snapped to the right-center bound of objects. */ + OBJECT_ALIGNMENT_BOTTOM_LEFT, /**< Tiles are snapped to the lower-left bound of objects. */ + OBJECT_ALIGNMENT_BOTTOM, /**< Tiles are snapped to the lower-center bound of objects. */ + OBJECT_ALIGNMENT_BOTTOM_RIGHT /**< Tiles are snapped to the lower-right bound of objects. */ +} TmxObjectAlignment; + +/** + * Identifiers for the possible object types. + */ +typedef enum tmx_object_type { + OBJECT_TYPE_RECTANGLE = 0, /**< Four-sided polygon four right angles and axis-aligned edges. */ + OBJECT_TYPE_ELLIPSE, /**< Ellipse, or a circle when the axes are equal. */ + OBJECT_TYPE_POINT, /**< Individual (X, Y) coordinate with no dimensions. */ + OBJECT_TYPE_POLYGON, /**< Filled polygon formed by an ordered series of points. */ + OBJECT_TYPE_POLYLINE, /**< Unfilled polygon formed by an ordered series of points. */ + OBJECT_TYPE_TEXT, /**< Text, or the visual representation of a string. */ + OBJECT_TYPE_TILE /**< Tile, referenced by a Global ID (GID), from a tileset known to the map. */ +} TmxObjectType; + +/** + * Identifiers for the possible horizontal alignments of text. + */ +typedef enum tmx_horizontal_alignment { + HORIZONTAL_ALIGNMENT_LEFT = 0, /**< Text is to be snapped to the left bound of its object. */ + HORIZONTAL_ALIGNMENT_CENTER, /**< Text is to be centered along the X axis of its object. */ + HORIZONTAL_ALIGNMENT_RIGHT, /**< Text is to be snapped to the right bound of its object. */ + HORIZONTAL_ALIGNMENT_JUSTIFY /**< Text is to be evenly spaced, per line, filling the object's width. */ +} TmxHorizontalAlignment; + +/** + * Identifiers for the possible vertical alignments of text. + */ +typedef enum tmx_vertical_alignment { + VERTICAL_ALIGNMENT_TOP = 0, /**< Text is to be snapped to the upper bound of its object. */ + VERTICAL_ALIGNMENT_CENTER, /**< Text is to be cnetered along the Y axis of its object. */ + VERTICAL_ALIGNMENT_BOTTOM /**< Text is to be snapped to the lower bound of its object. */ +} TmxVerticalAlignment; + +/** + * Identifiers for the possible map orientations. + */ +typedef enum tmx_orientation { + ORIENTATION_NONE = 0, /**< Orientation was not specified. Assumed to be orthogonal. */ + ORIENTATION_ORTHOGONAL, /**< Standard top-down view with rectanglular tiles. */ + ORIENTATION_ISOMETRIC, /**< Subset top-down view from a 45-degree angle. NOT supported. */ + ORIENTATION_STAGGERED, /**< Variation of isometric with staggered axes. */ + ORIENTATION_HEXAGONAL /**< Top-down view in which tiles are six-sided and alternative rows/columns are offset. */ +} TmxOrientation; + +/** + * Identifiers for the possible orders in which tiles in a tile layer are rendered/drawn. + */ +typedef enum tmx_render_order { + RENDER_ORDER_RIGHT_DOWN = 0, /**< Tiles are rendered by row, from left to right, then column, from top to bottom. */ + RENDER_ORDER_RIGHT_UP, /**< Tiles are rendered by row, from left to right, then column, from bottom to top. */ + RENDER_ORDER_LEFT_DOWN, /**< Tiles are rendered by row, from right to left, then column, from top to bottom. */ + RENDER_ORDER_LEFT_UP /**< Tiles are rendered by row, from right to left, then column, from bottom to top. */ +} TmxRenderOrder; + +/** + * Model of an <image> element. Defines an image and relevant attributes along with a loaded texture. + */ +typedef struct tmx_image { + char* source; /**< File name and/or path referencing the image on disk. */ + Color trans; /**< [optional] Color that defines is treated as transparent. Not currently implemented. */ + bool hasTrans; /**< When true, indicates 'trans' has been set with a color to be treated as transparent. */ + uint32_t width; /**< Width of the image in pixels. */ + uint32_t height; /**< Height of the image in pixels. */ + Texture2D texture; /**< The image as a raylib texture loaded into VRAM, if loading was successful. */ +} TmxImage; + +/** + * Model of a <layer> element when combined with the 'TmxLayer' model. Defines a tile layer with a fixed-size list of + * tile Global IDs (GIDs). + */ +typedef struct tmx_tile_layer { + uint32_t width; /**< Width of the layer in tiles. */ + uint32_t height; /**< Height of the layer in tiles. */ + char* encoding; /**< [optional] Encoding used to encode tiles. May be NULL, "base64," or "csv." */ + char* compression; /**< [optional] Compression used to compress tiles. May be NULL, "gzip," "zlib," or "zstd." */ + uint32_t* tiles; /**< Array of tile Global IDs (GIDs) contained by this tile layer. */ + uint32_t tilesLength; /**< Length of the 'tiles' array. */ +} TmxTileLayer; + +/** + * Contains the information needed to quickly draw a single line of a <text> element. + */ +typedef struct tmx_text_line { + char* content; /**< The string to be drawn. This may be the whole content of the parent string or partial. */ + Font font; /**< The raylib Font to be used when drawing. */ + Vector2 position; /**< Absolute position of this line. This is separate from its object layer's potential offset. */ + float spacing; /**< Spacing in pixels to be applied between each character when drawing. */ +} TmxTextLine; + +/** + * Model of a <text> element along with some pre-calculated objects for efficient drawing. + */ +typedef struct tmx_text { + char* fontFamily; /**< Font family (e.g. "sans-serif") to be used to render the text. */ + uint32_t pixelSize; /**< Size of the font in pixels. */ + bool wrap; /**< When true, indicates word wrapping should be used when appropriate. */ + Color color; /**< Color of the text. */ + bool bold; /**< When true, indicates the text should be bolded. */ + bool italic; /**< When true, indicates the text should be italicized. */ + bool underline; /**< When true, indicates the text should be underlined. */ + bool strikeOut; /**< When true, indicates the text should be struck/crossed out. */ + bool kerning; /**< When true, indicates kerning should be used when drawing. */ + TmxHorizontalAlignment halign; /**< Horizontal alignment of the text within its object. */ + TmxVerticalAlignment valign; /**< Vertical alignment of the text within its object. */ + char* content; /**< The string to be drawn. */ + TmxTextLine* lines; /**< Array of pre-calculated lines with all values needed to quickly draw this text. */ + uint32_t linesLength; /**< Length of the 'lines' array. */ +} TmxText; + +/** + * Model of a <property> element. Describes a property of the model it's attached to with a name, type, and value. + */ +typedef struct tmx_property { + TmxPropertyType type; /**< The specific (data) type of the property indicating which associated value to read. */ + char* name; /**< Name of the property. */ + char* stringValue; /**< The property's value for string-typed properties. */ + int32_t intValue; /**< The property's value for integer-typed properties. */ + float floatValue; /**< The property's value for floating point-typed properties. */ + bool boolValue; /**< The property's value for boolean-typed properties. */ + Color colorValue; /**< The property's value for color-typed properties. */ +} TmxProperty; + +/** + * Model of an <object> element within an <objectgroup> element. Objects are amorphous entities of varying type but all + * are potentially visible with positions and dimensions, although points' dimensions are effectively zero. + */ +typedef struct tmx_object { + TmxObjectType type; /**< The specific object type indicating which optional fields have relevant information. */ + uint32_t id; /**< Unique ID of the object. */ + char* name; /**< Name of the object. */ + char* typeString; /**< The type/class of the object. */ /* 'type' is a reserved keyword hence 'typeString' */ + double x; /**< X coordinate, in pixels, of the object. This is separate from its object layer's potential offset. */ + double y; /**< Y coordinate, in pixels, of the object. This is separate from its object layer's potential offset. */ + double width; /**< Width of the object in pixels. */ + double height; /**< Height of the object in pixels. */ + double rotation; /**< Rotation of the object in (clockwise) degrees around the object's (x, y) position. */ + uint32_t gid; /**< (Semi-optional) Global ID of a tile drawn as the object. If zero, the object is not a tile. */ + bool visible; /**< When true, indicates the object will be drawn. */ + char* templateString; /**< [optional] File name and/or path referencing an object template on disk applied to this + object. If NULL, no template is used. */ + Vector2* points; /**< [optional] Array of ordered points that define a poly(gon|line). Relative, not absolute. */ + uint32_t pointsLength; /**< Length of the 'points' array. */ + Vector2* drawPoints; /**< [optional] Array used as a buffer when drawing. Equal in length to the 'points' array. */ + TmxText* text; /**< [optional] Text to be drawn. */ + TmxProperty* properties; /**< Array of named, typed properties that apply to this object. */ + uint32_t propertiesLength; /**< Length of the 'properties' array. */ + Rectangle aabb; /**< Axis-Aligned Bounding Box (AABB). */ +} TmxObject; + +/** + * Model of an <objectgroup> element when combined with the 'TmxLayer' model. Defines an object layer of an arbitrary + * number of objects of varying types. + */ +typedef struct tmx_object_group { + /* uint32_t width; */ /**< Width of the object layer in tiles. TMX documentation describes it as "meaningless." */ + /* uint32_t height; */ /**< Height of the object layer in tiles. TMX documentation describes it as "meaningless." */ + Color color; /**< [optional] Color used to display objects within the layer. */ + bool hasColor; /**< When true, indicates 'color' has been set. */ + TmxObjectGroupDrawOrder drawOrder; /**< Indicates the order in which objects in this layer are drawn. */ + TmxObject* objects; /**< Array of objects contained by this object layer. */ + uint32_t objectsLength; /**< Length of the 'objects' array. */ + uint32_t* ySortedObjects; /**< Array of indexes of 'objects' sorted by the objects' y-coordinates. */ +} TmxObjectGroup; + +/** + * Model of an <imagelayer> element when combined with the 'TmxLayer' model. Defines a layer consisting of one image. + */ +typedef struct tmx_image_layer { + bool repeatX; /**< When true, indicates the image is repeated along the X axis. */ + bool repeatY; /**< When true, indicates the image is repeated along the Y axis. */ + TmxImage image; /**< Sole image of this layer. */ + bool hasImage; /**< When true, indicates 'image' has been set. Should always be true. */ +} TmxImageLayer; + +struct tmx_layer; /* Forward declaration of the following type. Contains children of the same type. */ + +/** + * Model of multiple layer elements: <layer>, <objectgroup>, <imagelayer>, or <group>. Defines a layer with attributes + * common to all, more-specific layer types. The more-specific attributes + */ +typedef struct tmx_layer { + TmxLayerType type; /**< The specific layer type indicating which associated layer ('exact') has mspecific values. */ + uint32_t id; /**< Unique integer ID of the layer. */ + char* name; /**< Name of the layer. */ + char* classString; /**< [optional] Class of the layer. If unused, defaults to an empty string. */ + bool visible; /**< When true, indicates the layer and its children will be drawn. */ + double opacity; /**< Opacity of the layer and its children where 0.0 means the layer is fully transparent. */ + Color tintColor; /**< [optional] Tint color applied to the layer and its children. */ + bool hasTintColor; /**< When true, indicates 'tintColor' has been set. */ + int32_t offsetX; /**< Horizontal offset of the layer and its children in pixels. */ + int32_t offsetY; /**< Vertical offset of the layer and its children in pixels. */ + double parallaxX; /**< Horizontal parallax factor. 1.0 means the layers position on the screen changes at the same + rate as the camera. 0.0 means the layer will not move with the camera. */ + double parallaxY; /**< Veritcal parallax factor. 1.0 means the layers position on the screen changes at the same + rate as the camera. 0.0 means the layer will not move with the camera. */ + TmxProperty* properties; /**< Array of named, typed properties that apply to this layer. */ + uint32_t propertiesLength; /**< Length of the 'properties' array. */ + struct tmx_layer* layers; /**< [optional] Array of child layers, may be NULL. Only used by group layers. */ + uint32_t layersLength; /**< Length of the 'layers' array. */ + union layer_type_union { + TmxTileLayer tileLayer; + TmxObjectGroup objectGroup; + TmxImageLayer imageLayer; + } exact; /**< Additional layer information specific to a tile, object, or image layer but not groups. */ +} TmxLayer; + +/** + * Model of a <frame> element. Defines a temporal frame of an animation with the Global ID (GID) of the tile to be + * displayed and the duration thereof. + */ +typedef struct tmx_animation_frame { + uint32_t gid; /**< The Global ID (GID) of a tile from the animation's tileset to be drawn for some duration. */ + float duration; /**< Duration in milliseconds that the frame should be displayed. */ +} TmxAnimationFrame; + +/** + * Model of an <animation> element. Defines a series of (tile) frames. + */ +typedef struct tmx_animation { + TmxAnimationFrame* frames; /**< Array of frames. These frames identify tiles and durations to be displayed. */ + uint32_t framesLength; /**< Length of the 'frames' array. */ +} TmxAnimation; + +/** + * Model of a <tile> element within a <tileset> element. Contains information about tiles that are not or cannot be + * implicitly determined from the tileset. + */ +typedef struct tmx_tileset_tile { + uint32_t id; /**< Local ID of the tile within its tileset. This is a factor in but different from its Global ID. */ + char* classString; /**< [optional] Class of the tile. If unused, defaults to an empty string. */ + int32_t x; /**< X coordinate, in pixels, of the sub-rectangle within the tileset's image to extract. */ + int32_t y; /**< Y coordinate, in pixels, of the sub-rectangle within the tileset's image to extract. */ + uint32_t width; /**< Width, in pixels, of the sub-rectangle within the tileset's image to extract. */ + uint32_t height; /**< Height, in pixels, of the sub-rectangle within the tileset's image to extract. */ + TmxImage image; /**< [optional] Image to be used as the tile for "collection of images" tilesets. */ + bool hasImage; /**< When true, indicates 'image' is set. */ + TmxAnimation animation; /**< [optional] Animation. Lists GIDs to be drawn temporarily and periodically. */ + bool hasAnimation; /**< When true, indicates 'animation' is assigned. */ + TmxProperty* properties; /**< Array of named, typed properties that apply to this tileset tile. */ + uint32_t propertiesLength; /**< Length of the 'properties' array. */ + TmxObjectGroup objectGroup; /**< [optional] 0+ objects representing collision information unique to the tile. */ +} TmxTilesetTile; + +/** + * Model of a <tileset> element. Defines an image, or serious of images, from which tiles are drawn along with + * information on how to extract areas from within the image and/or how to align them within an object. + */ +typedef struct tmx_tileset { + uint32_t firstGid; /**< First Global ID (GID) of a tile in this tileset. */ + uint32_t lastGid; /**< Last Global ID (GID) of a tile in this tileset. */ + char* source; /**< [optional] Source of this tileset, may be NULL. Only used for external tilesets. */ + char* name; /**< Name of the tileset. */ + char* classString; /**< [optional] Class of the tileset. If unused, defaults to an empty string. */ + uint32_t tileWidth; /**< Maximum, although typically exact, width of the tiles in this tileset in pixels. */ + uint32_t tileHeight; /**< Maximum, although typically exact, height of the tiles in this tileset in pixels. */ + uint32_t spacing; /**< Spacing in pixels between tiles in this tileset. */ + uint32_t margin; /**< Margin around the tiles in this tileset. */ + uint32_t tileCount; /**< Number of tiles in this tileset. Note: 'lastGid' - 'firstGid' is not always 'tileCount.' */ + uint32_t columns; /**< Number of tile columsn in this tileset. */ + TmxObjectAlignment objectAlignment; /**< Controls the alignment of tiles of this tileset when used as objects. */ + int32_t tileOffsetX; /**< Horizontal offset in pixels applied when drawing tiles from this tileset. */ + int32_t tileOffsetY; /**< Vertical offset in pixels applied when drawing tiles form this tileset. */ + TmxImage image; /**< [optional] Image from which this tilesets tiles are extracted. */ + bool hasImage; /**< When true, indicates 'image' is set. */ + TmxProperty* properties; /**< Array of named, typed properties that apply to this tileset. */ + uint32_t propertiesLength; /**< Length of the 'properties' array. */ + TmxTilesetTile* tiles; /**< Array of explicitly-defined tiles within the tileset. */ + uint32_t tilesLength; /**< Length of the 'tiles' array. */ +} TmxTileset; + +/** + * Contains the information and objects needed to quickly draw a <tile> in a raylib application. + */ +typedef struct tmx_tile { + uint32_t gid; /**< Global ID (GID) unique to the tile, used for lookups. 0 means the tile is effectively unused. */ + Rectangle sourceRect; /**< Sub-rectangle within a tileset to extract that is to be drawn. */ + Texture2D texture; /**< Texture in VRAM to be used to draw. May be used whole or as a source of a sub-rectangle. */ + Vector2 offset; /**< Offset in pixels to be applied to the tile, derived from the tileset. */ + TmxAnimation animation; /**< [optional] Animation. Lists GIDs to be drawn temporarily and periodically. */ + bool hasAnimation; /**< When true, indicates 'animation' is assigned. */ + uint32_t frameIndex; /**< For animations, the current animation frame to draw. */ + float frameTime; /**< For animations, an accumulator. The time, in seconds, the current frame has been drawn. */ + TmxObjectGroup objectGroup; /**< [optional] 0+ objects representing collision information unique to the tile. */ +} TmxTile; + +/** + * Model of a <map> element along with some pre-calculated objects for efficient drawing. + */ +typedef struct tmx_map { + char* fileName; /**< File name of the TMX file with extension. */ + TmxOrientation orientation; /**< Map orientation. May be orthogonal, isometric, staggered, or hexagonal. */ + TmxRenderOrder renderOrder; /**< Order in which tiles on tile layers are rendered. */ + uint32_t width; /**< Width of this map in tiles. */ + uint32_t height; /**< Height of htis map in tiles. */ + uint32_t tileWidth; /**< Width of a tile in pixels. */ + uint32_t tileHeight; /**< Height of a tile in pixels. */ + int32_t parallaxOriginX; /**< X coordinate, in pixels, of the parallax origin. */ + int32_t parallaxOriginY; /**< Y coordinate, in pixels, of hte parallax origin. */ + Color backgroundColor; /**< [optional] Background color to be drawn behind the map with its dimensions. */ + bool hasBackgroundColor; /**< When true, indicates 'backgroundColor' is set. */ + TmxProperty* properties; /**< Array of named, typed properties that apply to this map. */ + uint32_t propertiesLength; /**< Length of the 'properties' array. */ + TmxTileset* tilesets; /**< Array of tilesets used by the map. */ + uint32_t tilesetsLength; /**< Length of the 'tilesets' array. */ + TmxLayer* layers; /**< Array of layers and potential child layers that make up this map. */ + uint32_t layersLength; /**< Length of the 'layers' array. */ + TmxTile* gidsToTiles; /**< Array of pre-calculated tile metadata with all the values needed to quickly draw a tile + given its GID. Allocated such that gidsToTiles[1] returns the data of tile GID 1. */ + uint32_t gidsToTilesLength; /**< Length of the 'gidsToTiles' array. */ +} TmxMap; + +/** + * Given a path to TMX document, parse it and create an equivalent model that can be, among other uses, quickly drawn. + * This function allocates memory and loads textures into VRAM. To clean up, use UnloadTMX(). + * + * @param fileName File name and/or path referencing a TMX document on disk to be loaded. + * @return A model of the map as defined by the given TMX document, or NULL if loading failed for any reason. + */ +RAYTMX_DEC TmxMap* LoadTMX(const char* fileName); + +/** + * Unload a given map model by freeing memory allocations and unloading textures. In other words, free the resources + * reserved by LoadTMX(). + * + * @param map A previously-loaded map model to be freed/unloaded. + */ +RAYTMX_DEC void UnloadTMX(TmxMap* map); + +/** + * Draw the entirety of the given map at the given position. + * When a camera is also passed to this function, parallaxed scrolling can be applied to layers with parallax factors + * that are not 1.0 and the screen's surface can be known allowing for occlusion culling and the performance gains that + * come with it. + * The given tint is applied to map and its layers. When layers have their own tints, the two colors are combined. If no + * tint is needed, passing WHITE effectively means no tint is applied. + * + * @param map A loaded map model to be drawn in whole at the given coordinates. + * @param camera [optional] Camera2D to be used for parallax and/or occlusion. Required for parallax. 'viewport' takes + * priority for occlusion. + * @param viewport [optional] Region drawn to. Used for occlusion. Only tiles, objects, etc. in this region are drawn. + * @param posX X coordinate at which to draw the map. This corresponds to the top-left corner of the map. + * @param posY Y coordinate at which to draw the map. This corresponds to the top-left corner of the map. + * @param tint A tint to be applied to the map and its layers. This tint is combined with any individual layer tints. + */ +RAYTMX_DEC void DrawTMX(const TmxMap* map, const Camera2D* camera, const Rectangle* viewport, int posX, int posY, + Color tint); + +/** + * Draw the given layers at the given position. + * When a camera is also passed to this function, parallaxed scrolling can be applied to layers with parallax factors + * that are not 1.0 and the screen's surface can be known allowing for occlusion culling and the performance gains that + * come with it. + * The given tint is applied to the layers. When layers have their own tints, the two colors are combined. If no tint is + * needed, passing WHITE effectively means no tint is applied. + * + * @param map A loaded map model whose layers are to be drawn. + * @param camera [optional] Camera2D to be used for parallax and/or occlusion. Required for parallax. 'viewport' takes + * priority for occlusion. + * @param viewport [optional] Region drawn to. Used for occlusion. Only tiles, objects, etc. in this region are drawn. + * @param layers An array of select layers to be drawn. + * @param layersLength Length of the given array of layers. + * @param posX X coordinate at which to draw the layers. This corresponds to the top-left corner of the layers. + * @param posY Y coordinate at which to draw the layers. This corresponds to the top-left corner of the layers. + * @param tint A tint to be applied to the layers. This tint is combined with any individual layer tints. + */ +RAYTMX_DEC void DrawTMXLayers(const TmxMap* map, const Camera2D* camera, const Rectangle* viewport, + const TmxLayer* layers, uint32_t layersLength, int posX, int posY, Color tint); + +/** + * Progress the animations of the given map in real-time. This is intended to be called once per frame, or once per + * BeginDrawing() an EndDrawing() call. If called more or less frequently, animation speeds will be affected. + * + * @param map A loaded map model to be animated. + */ +RAYTMX_DEC void AnimateTMX(TmxMap* map); + +/** + * Check for collisions between two objects of arbitrary type. Objects that are not primitive shapes, namely text and + * tiles, are treated as rectangles. + * + * @param object1 A TMX <object> to be checked for a collision. + * @param object2 Another TMX <object> to be checked for a collision. + * @return True if the given objects collide with one another, or false if there is no collision. + */ +RAYTMX_DEC bool CheckCollisionTMXObjects(TmxObject object1, TmxObject object2); + +/** + * Check for collisions between the given tile or group layers and the given rectangle. The tiles must have collision + * information created with the Tiled Collision Editor. + * Note: This function assumes the map is positioned at (0, 0). If the map is drawn with an offset, normalize. + * + * @param map A loaded map model containing the given layers. + * @param layers An array of select tile or group layers to be checked for collisions. + * @param layersLength Length of the given array of layers. + * @param rec The rectangle to perform collision checks on. + * @param outputObject Output parameter assigned with the object the rectangle collided with. NULL if not wanted. + * @return True if the given rectangle collides with any tile in the given layers, or false if there is no collision. + */ +RAYTMX_DEC bool CheckCollisionTMXTileLayersRec(const TmxMap* map, const TmxLayer* layers, uint32_t layersLength, + Rectangle rec, TmxObject* outputObject); + +/** + * Check for collisions between the given tile or group layers and the given circle. The tiles must have collision + * information created with the Tiled Collision Editor. + * Note: This function assumes the map is positioned at (0, 0). If the map is drawn with an offset, normalize. + * + * @param map A loaded map model containing the given layers. + * @param layers An array of select tile or group layers to be checked for collisions. + * @param layersLength Length of the given array of layers. + * @param center The center point of the circle. + * @param radius The radius of the circle. + * @param outputObject Output parameter assigned with the object the circle collided with. NULL if not wanted. + * @return True if the given circle collides with any tile in the given layers, or false if there is no collision. + */ +RAYTMX_DEC bool CheckCollisionTMXTileLayersCircle(const TmxMap* map, const TmxLayer* layers, uint32_t layersLength, + Vector2 center, float radius, TmxObject* outputObject); + +/** + * Check for collisions between the given tile or group layers and the given point. The tiles must have collision + * information created with the Tiled Collision Editor. + * Note: This function assumes the map is positioned at (0, 0). If the map is drawn with an offset, normalize. + * + * @param map A loaded map model containing the given layers. + * @param layers An array of select tile or group layers to be checked for collisions. + * @param layersLength Length of the given array of layers. + * @param point The point to perform collision checks on. + * @param outputObject Output parameter assigned with the object the point collided with. NULL if not wanted. + * @return True if the given point collides with any tile in the given layers, or false if there is no collision. + */ +RAYTMX_DEC bool CheckCollisionTMXTileLayersPoint(const TmxMap* map, const TmxLayer* layers, uint32_t layersLength, + Vector2 point, TmxObject* outputObject); + +/** + * Check for collisions between the given tile or group layers and the given polygon. The tiles must have collision + * information created with the Tiled Collision Editor. + * Note: This function assumes the map is positioned at (0, 0). If the map is drawn with an offset, normalize. + * + * This function calculates the Axis-Aligned Bounding Box (AABB) of the polygon each time it's called. If the polygon's + * AABB is known, CheckCollisionTMXLayersPolyEx() can be used for better performance. + * + * @param map A loaded map model containing the given layers. + * @param layers An array of select tile or group layers to be checked for collisions. + * @param layersLength Length of the given array of layers. + * @param points An array of vertices defining the polygon. No repeats. + * @param pointCount The length of the array of vertices. + * @param outputObject Output parameter assigned with the object the polygon collided with. NULL if not wanted. + * @return True if the given polygon collides with any tile in the given layers, or false if there is no collision. + */ +RAYTMX_DEC bool CheckCollisionTMXLayersPoly(const TmxMap* map, const TmxLayer* layers, uint32_t layersLength, + Vector2* points, int pointCount, TmxObject* outputObject); + +/** + * Check for collisions between the given tile or group layers and the given polygon with the given Axis-Aligned + * Bounding Box (AABB). The tiles must have collision information created with the Tiled Collision Editor. + * Note: This function assumes the map is positioned at (0, 0). If the map is drawn with an offset, normalize. + * + * @param map A loaded map model containing the given layers. + * @param layers An array of select tile or group layers to be checked for collisions. + * @param layersLength Length of the given array of layers. + * @param points An array of vertices defining the polygon. No repeats. + * @param pointCount The length of the array of vertices. + * @param aabb Bounding box of the polygon. Used for quicker, broad collision checks. + * @param outputObject Output parameter assigned with the object the polygon collided with. NULL if not wanted. + * @return True if the given polygon collides with any tile in the given layers, or false if there is no collision. + */ +RAYTMX_DEC bool CheckCollisionTMXLayersPolyEx(const TmxMap* map, const TmxLayer* layers, uint32_t layersLength, + Vector2* points, int pointCount, Rectangle aabb, TmxObject* outputObject); + +/** + * Check for collisions between the given object group, with 0+ objects of arbitrary shape, and the given rectangle. + * Note: This function assumes the map is positioned at (0, 0). If the map is drawn with an offset, normalize. + * + * @param group The object group whose 0+ objects will be checked for collisions. + * @param rec The rectangle to perform collision checks on. + * @param outputObject Output parameter assigned with the object the rectangle collided with. NULL if not wanted. + * @return True if the given rectangle collides with any object in the object group, or false if there is no collision. + */ +RAYTMX_DEC bool CheckCollisionTMXObjectGroupRec(TmxObjectGroup group, Rectangle rec, TmxObject* outputObject); + +/** + * Check for collisions between the given object group, with 0+ objects of arbitrary shape, and the given circle. + * Note: This function assumes the map is positioned at (0, 0). If the map is drawn with an offset, normalize. + * + * @param group The object group whose 0+ objects will be checked for collisions. + * @param center The center point of the circle. + * @param radius The radius of the circle. + * @param outputObject Output parameter assigned with the object the circle collided with. NULL if not wanted. + * @return True if the given circle collides with any object in the object group, or false if there is no collision. + */ +RAYTMX_DEC bool CheckCollisionTMXObjectGroupCircle(TmxObjectGroup group, Vector2 center, float radius, + TmxObject* outputObject); + +/** + * Check for collisions between the given object group, with 0+ objects of arbitrary shape, and the given point. + * Note: This function assumes the map is positioned at (0, 0). If the map is drawn with an offset, normalize. + * + * @param group The object group whose 0+ objects will be checked for collisions. + * @param point The point to perform collision checks on. + * @param outputObject Output parameter assigned with the object the point collided with. NULL if not wanted. + * @return True if the given point collides with any object in the object group, or false if there is no collision. + */ +RAYTMX_DEC bool CheckCollisionTMXObjectGroupPoint(TmxObjectGroup group, Vector2 point, TmxObject* outputObject); + +/** + * Check for collisions between the given object group, with 0+ objects of arbitrary shape, and the given polygon. + * This function calculates the Axis-Aligned Bounding Box (AABB) of the polygon each time it's called. If the polygon's + * AABB is known, CheckCollisionTMXObjectGroupPolyEx() can be used for better performance. + * Note: This function assumes the map is positioned at (0, 0). If the map is drawn with an offset, normalize. + * + * @param group The object group whose 0+ objects will be checked for collisions. + * @param points An array of vertices defining the polygon. No repeats. + * @param pointCount The length of the array of vertices. + * @param outputObject Output parameter assigned with the object the polygon collided with. NULL if not wanted. + * @return True if the given polygon collides with any object in the object group, or false if there is no collision. + */ +RAYTMX_DEC bool CheckCollisionTMXObjectGroupPoly(TmxObjectGroup group, Vector2* points, int pointCount, + TmxObject* outputObject); + +/** + * Check for collisions between the given object group, with 0+ objects of arbitrary shape, and the given polygon with + * the given Axis-Aligned Bound Box (AABB). + * Note: This function assumes the map is positioned at (0, 0). If the map is drawn with an offset, normalize. + * + * @param group The object group whose 0+ objects will be checked for collisions. + * @param points An array of vertices defining the polygon. No repeats. + * @param pointCount The length of the array of vertices. + * @param aabb Bounding box of the polygon. Used for quicker, broad collision checks. + * @param outputObject Output parameter assigned with the object the polygon collided with. NULL if not wanted. + * @return True if the given polygon collides with any object in the object group, or false if there is no collision. + */ +RAYTMX_DEC bool CheckCollisionTMXObjectGroupPolyEx(TmxObjectGroup group, Vector2* points, int pointCount, + Rectangle aabb, TmxObject* outputObject); + +/** + * Set a custom callback in place of raylib's LoadTexture(). The callback must return a Texture2D and take a const char* + * as the sole parameter. To unset, pass NULL to this function. + * + * @param callback A function pointer with a "Texture2D CustLoadTexture(const char* fileName)" signature, or NULL if + * unsetting the custom callback. + */ +RAYTMX_DEC void SetLoadTextureTMX(LoadTextureCallback callback); + +/** + * Log properties of the given map as a formatted string. + * SetTraceLogFlagsTMX() may be used to exclude select information. + * + * @param logLevel The level/severity with which to log the string (e.g. LOG_DEBUG, LOG_INFO, etc.). + * @param map A loaded map to be logged. + */ +RAYTMX_DEC void TraceLogTMX(int logLevel, const TmxMap* map); + +/** + * Globally set logging options for TraceLogTMX() allowing for select types of information to be excluded. + * The flags used by this function are defined in the tmx_log_flags enumeration. + * + * @param logFlags Logically OR'd bit flags to be applied to all logging following this call. + */ +RAYTMX_DEC void SetTraceLogFlagsTMX(int logFlags); + +#ifdef __cplusplus + } +#endif /* __cplusplus */ + +#ifdef RAYTMX_IMPLEMENTATION + +#ifndef HOXML_IMPLEMENTATION + #define HOXML_IMPLEMENTATION +#endif +#include "hoxml.h" + +/******************/ +/* Implementation */ + +#define TMX_LINE_THICKNESS 3.0f /* Thickness, in pixels, that outlines of specific objects are drawn with */ + +/* Bit flags that GIDs may be masked with in order to indicate transformations for individual tiles */ +enum tmx_flip_flags { + FLIP_FLAG_HORIZONTAL = 0x80000000, + FLIP_FLAG_VERTICAL = 0x40000000, + FLIP_FLAG_DIAGONAL = 0x20000000, + FLIP_FLAG_ROTATE_120 = 0x10000000 +}; + +typedef enum raytmx_document_format { + FORMAT_TMX = 0, /* Tilemap with tilesets, layers, etc. */ + FORMAT_TSX, /* External tilesets */ + FORMAT_TX /* Object templates */ +} RaytmxDocumentFormat; + +typedef struct raytmx_external_tileset { + TmxTileset tileset; + bool isSuccess; /* 'isSuccess' is true when the external tileset was successfully loaded */ +} RaytmxExternalTileset; + +typedef struct raytmx_object_template { + TmxTileset tileset; + TmxObject object; + bool isSuccess, hasTileset; /* 'isSuccess' is true when the object template was successfully loaded */ +} RaytmxObjectTemplate; + +struct raytmx_cached_texture; /* Forward declaration */ +typedef struct raytmx_cached_texture { + char* fileName; + Texture2D texture; + struct raytmx_cached_texture* next; +} RaytmxCachedTextureNode; /* Associates a file name with a Texture2D allowing for the reuse of textures in VRAM */ + +struct raytmx_cached_template; /* Forward declaration */ +typedef struct raytmx_cached_template { + char* fileName; + RaytmxObjectTemplate objectTemplate; + struct raytmx_cached_template* next; +} RaytmxCachedTemplateNode; /* Associates a file name with an object template */ + +struct raytmx_property_node; /* Forward declaration */ +typedef struct raytmx_property_node { + TmxProperty property; + struct raytmx_property_node* next; +} RaytmxPropertyNode; + +struct raytmx_tileset_node; /* Forward declaration */ +typedef struct raytmx_tileset_node { + TmxTileset tileset; + struct raytmx_tileset_node* next; +} RaytmxTilesetNode; + +struct raytmx_tileset_tile_node; /* Forward declaration */ +typedef struct raytmx_tileset_tile_node { + TmxTilesetTile tile; + struct raytmx_tileset_tile_node* next; +} RaytmxTilesetTileNode; + +struct raytmx_animation_frame_node; /* Forward declaration */ +typedef struct raytmx_animation_frame_node { + TmxAnimationFrame frame; + struct raytmx_animation_frame_node* next; +} RaytmxAnimationFrameNode; + +struct raytmx_layer_node; /* Forward declaration */ +typedef struct raytmx_layer_node { + TmxLayer layer; + uint32_t childrenLength; + struct raytmx_layer_node *next, *parent, *childrenRoot, *childrenTail; +} RaytmxLayerNode; + +struct raytmx_tile_layer_tile_node; /* Forward declaration */ +typedef struct raytmx_tile_layer_tile_node { + uint32_t gid; + struct raytmx_tile_layer_tile_node* next; +} RaytmxTileLayerTileNode; + +struct raytmx_object_node; /* Forward declaration */ +typedef struct raytmx_object_node { + TmxObject object; + struct raytmx_object_node* next; +} RaytmxObjectNode; + +struct raytmx_object_sorting_node; /* Forward declaration */ +typedef struct raytmx_object_sorting_node { + double y; + uint32_t index; + struct raytmx_object_sorting_node* next; +} RaytmxObjectSortingNode; + +struct raytmx_poly_point_node; /* Forward declaration */ +typedef struct raytmx_poly_point_node { + Vector2 point; + struct raytmx_poly_point_node* next; +} RaytmxPolyPointNode; + +struct raytmx_text_line_node; /* Forward declaration */ +typedef struct raytmx_text_line_node { + TmxTextLine line; + struct raytmx_text_line_node* next; +} RaytmxTextLineNode; + +typedef struct raytmx_state { + RaytmxDocumentFormat format; + char documentDirectory[512]; + bool isSuccess; + + /* Variables intended for TMX (map) parsing */ + RaytmxCachedTextureNode* texturesRoot; + RaytmxCachedTemplateNode* templatesRoot; + TmxOrientation mapOrientation; + TmxRenderOrder mapRenderOrder; + uint32_t mapWidth, mapHeight, mapTileWidth, mapTileHeight, mapPropertiesLength; + int32_t mapParallaxOriginX, mapParallaxOriginY; + Color mapBackgroundColor; + bool mapHasBackgroundColor; + TmxProperty* mapProperties; + + /* These variables, when not NULL, are assigned to the current element(s) being parsed */ + TmxProperty* property; + TmxTileset* tileset; + TmxImage* image; + TmxTilesetTile* tilesetTile; + TmxAnimationFrame* animationFrame; + /* TmxWangSet* wangSet; */ /* TODO: Wang sets. Low priority. */ + /* TmxWangColor* wangColor; */ /* TODO: Wang sets. Low priority. */ + TmxLayer* layer; + TmxTileLayer* tileLayer; + TmxObjectGroup* objectGroup; + TmxImageLayer* imageLayer; + TmxObject* object; + + /* These variables are linked lists containing various elements where an arbitrary amount are allowed, such as */ + /* 1+ <object> elements in an <objectgroup>, that will be copied to arrays of known sizes later on */ + RaytmxPropertyNode *propertiesRoot, *propertiesTail; + RaytmxTilesetNode *tilesetsRoot, *tilesetsTail; + RaytmxTilesetTileNode *tilesetTilesRoot, *tilesetTilesTail; + RaytmxAnimationFrameNode *animationFramesRoot, *animationFramesTail; + RaytmxLayerNode *layersRoot, *layersTail, *groupNode; + RaytmxTileLayerTileNode *layerTilesRoot, *layerTilesTail; + RaytmxObjectNode *objectsRoot, *objectsTail; + uint32_t tilesetsLength, tilesetTilesLength, animationFramesLength, propertiesLength, layersLength, + layerTilesLength, objectsLength, propertiesDepth; +} RaytmxState; /* Intermediate data used internally to parse TMX (map), TSX (tileset), and TX (template) files */ + +typedef struct raytmx_transform { + Vector2 position; + Vector2 parallax; + Vector2 cameraOffset; +} RaytmxTransform; + +RaytmxExternalTileset LoadTSX(const char* fileName); +RaytmxObjectTemplate LoadTX(const char* fileName); +void ParseDocument(RaytmxState* raytmxState, const char* fileName); +void HandleElementBegin(RaytmxState* raytmxState, hoxml_context_t* hoxmlContext); +void HandleAttribute(RaytmxState* raytmxState, hoxml_context_t* hoxmlContext); +void HandleElementEnd(RaytmxState* raytmxState, hoxml_context_t* hoxmlContext); +void FreeState(RaytmxState* raytmxState); +void FreeString(char* str); +void FreeTileset(TmxTileset tileset); +void FreeProperty(TmxProperty property); +void FreeLayer(TmxLayer layer); +void FreeObject(TmxObject object); +bool IterateTileLayer(const TmxMap* map, const TmxTileLayer* layer, Rectangle viewport, RaytmxTransform transform, + uint32_t* rawGid, TmxTile* tile, Rectangle* tileRect); +void DrawTMXLayersInternal(const TmxMap* map, const Camera2D* camera, const Rectangle* viewport, const TmxLayer* layers, + uint32_t layersLength, RaytmxTransform transform, Color tint); +void DrawTMXTileLayer(const TmxMap* map, Rectangle viewport, TmxLayer layer, RaytmxTransform transform, Color tint); +void DrawTMXLayerTile(const TmxMap* map, Rectangle viewport, uint32_t rawGid, Rectangle destRect, Color tint); +void DrawTMXObjectTile(const TmxMap* map, Rectangle viewport, uint32_t rawGid, RaytmxTransform transform, float width, + float height, Color tint); +void DrawTMXObjectGroup(const TmxMap* map, Rectangle viewport, TmxLayer layer, RaytmxTransform transform, Color tint); +void DrawTMXImageLayer(const TmxMap* map, Rectangle viewport, TmxLayer layer, RaytmxTransform transform, Color tint); +bool CheckCollisionTMXTileLayerObject(const TmxMap* map, const TmxLayer* layers, uint32_t layersLength, + TmxObject object, TmxObject* outputObject); +bool CheckCollisionTMXObjectGroupObject(TmxObjectGroup group, TmxObject object, TmxObject* outputObject); +void TraceLogTMXTilesets(int logLevel, TmxOrientation orientation, TmxTileset* tilesets, uint32_t tilesetsLength, + int numSpaces); +void TraceLogTMXProperties(int logLevel, TmxProperty* properties, uint32_t propertiesLength, int numSpaces); +void TraceLogTMXLayers(int logLevel, TmxLayer* layers, uint32_t layersLength, int numSpaces); +void TraceLogTMXObject(int logLevel, TmxObject object, int numSpaces); +void StringCopy(char* destination, const char* source); +TmxProperty* AddProperty(RaytmxState* raytmxState); +void AddTileLayerTile(RaytmxState* raytmxState, uint32_t gid); +TmxTileset* AddTileset(RaytmxState* raytmxState); +TmxTilesetTile* AddTilesetTile(RaytmxState* raytmxState); +TmxAnimationFrame* AddAnimationFrame(RaytmxState* raytmxState); +TmxLayer* AddGenericLayer(RaytmxState* raytmxState, bool isGroup); +TmxObject* AddObject(RaytmxState* raytmxState); +void AppendLayerTo(TmxMap* map, RaytmxLayerNode* groupNode, RaytmxLayerNode* layersRoot, uint32_t layersLength); +RaytmxCachedTextureNode* LoadCachedTexture(RaytmxState* raytmxState, const char* fileName); +RaytmxCachedTemplateNode* LoadCachedTemplate(RaytmxState* raytmxState, const char* fileName); +Color GetColorFromHexString(const char* hex); +uint32_t GetGid(uint32_t rawGid, bool* isFlippedHorizontally, bool* isFlippedVertically, bool* isFlippedDiagonally, + bool* isRotatedHexagonal120); +void* MemAllocZero(unsigned int size); +char* GetDirectoryPath2(const char* filePath); +char* JoinPath(const char* prefix, const char* suffix); +void StringCopyN(char* destination, const char* source, size_t number); +void StringConcatenate(char* destination, const char* source); + +/**********************************************************************************************************************/ +/* Public implementation. */ + +RAYTMX_DEC TmxMap* LoadTMX(const char* fileName) { + RaytmxState raytmxState[1]; + memset(raytmxState, 0, sizeof(RaytmxState)); /* Initialize all values to zero, NULL, or an equivalent enum value */ + raytmxState->format = FORMAT_TMX; + + /* Initialize the map object */ + TmxMap* map = (TmxMap*)MemAllocZero(sizeof(TmxMap)); + + /* Do format-agnostic parsing of the document. The state object will be populated with raytmx's models of the */ + /* equivalent TMX, TSX, and/or TX elements. */ + ParseDocument(raytmxState, fileName); + if (!raytmxState->isSuccess) { + UnloadTMX(map); + return NULL; + } + + /* Copy some top-level map properties */ + map->fileName = (char*)MemAllocZero((unsigned int)strlen(fileName) + 1); + StringCopy(map->fileName, GetFileName(fileName)); + map->orientation = raytmxState->mapOrientation; + map->renderOrder = raytmxState->mapRenderOrder; + map->width = raytmxState->mapWidth; + map->height = raytmxState->mapHeight; + map->tileWidth = raytmxState->mapTileWidth; + map->tileHeight = raytmxState->mapTileHeight; + map->backgroundColor = raytmxState->mapBackgroundColor; + map->parallaxOriginX = raytmxState->mapParallaxOriginX; + map->parallaxOriginY = raytmxState->mapParallaxOriginY; + map->hasBackgroundColor = raytmxState->mapHasBackgroundColor; + + uint32_t gidsToTilesLength = 0; /* Can also be seen as the last GID */ + if (raytmxState->tilesetsRoot != NULL) { /* If there is at least one tileset */ + /* Allocate the array of tilesets and zeroize every index */ + TmxTileset* tilesets = (TmxTileset*)MemAllocZero(sizeof(TmxTileset) * raytmxState->tilesetsLength); + /* Copy the TmxTileset pointers into the array */ + RaytmxTilesetNode* tilesetIterator = raytmxState->tilesetsRoot; + for (uint32_t i = 0; tilesetIterator != NULL; i++) { + TmxTileset tileset = tilesetIterator->tileset; + + if (tileset.hasImage) { /* If the tileset has a shared image and implicitly defines tiles */ + /* These tilesets implicitly have X tiles where X = <width in tiles> * <height in tiles>. But even */ + /* these tilesets may have explicitly defined tiles for things like animations, or anything else. We */ + /* need to know the greatest ID of those explicit tiles because they are allowed to have IDs that */ + /* exceed the tileset's tile count and this affects global IDs, even in other tilesets. */ + uint32_t greatestID = 0; + for (uint32_t i = 0; i < tileset.tilesLength; i++) + { + if (greatestID < tileset.tiles[i].id) + greatestID = tileset.tiles[i].id; + } + /* Determine the last GID, meaning the greatest GID value, from the greatest local ID and tile count */ + if (greatestID >= tileset.tileCount) + tileset.lastGid = tileset.firstGid + greatestID; + else + tileset.lastGid = tileset.firstGid + tileset.tileCount - 1; + } else if (tileset.tilesLength > 0) /* If the tileset is a "collection of images" with explicit tiles */ + tileset.lastGid = tileset.firstGid + tileset.tiles[tileset.tilesLength - 1].id - 1; + + if (gidsToTilesLength < tileset.lastGid + 1) + gidsToTilesLength = tileset.lastGid + 1; /* GIDs start at 1 so the length is the last GID + 1 */ + tilesets[i] = tileset; + tilesetIterator = tilesetIterator->next; + } + /* Add the tilesets array to the map */ + map->tilesets = tilesets; + map->tilesetsLength = raytmxState->tilesetsLength; + } else + TraceLog(LOG_WARNING, "RAYTMX: The map does not contain any tilesets"); + + if (raytmxState->layersRoot != NULL) { /* If there is at least one layer within the map */ + /* Due to the existence of <group> layers, layers can have children of multiple generations. To form the */ + /* resulting tree-like structure, recursion is used. */ + AppendLayerTo(map, NULL, raytmxState->layersRoot, raytmxState->layersLength); + } else + TraceLog(LOG_WARNING, "RAYTMX: The map does not contain any layers"); + + if (gidsToTilesLength > 0) { + TmxTile* gidsToTiles = (TmxTile*)MemAllocZero(sizeof(TmxTile) * gidsToTilesLength); + + for (uint32_t i = 0; i < map->tilesetsLength; i++) { + TmxTileset* tileset = &map->tilesets[i]; + if (tileset->hasImage) { /* If the tileset has a shared image (i.e. not a "collection of images") */ + /* First, iterate through the explicit tiles. These are explicitly defined with <tile> elements and */ + /* are used to, among other things, provide animations and specific source rectangles. Note: Their */ + /* IDs may exceed the tileset's tile count. */ + for (uint32_t j = 0; j < tileset->tilesLength; j++) { + TmxTilesetTile tilesetTile = tileset->tiles[j]; + uint32_t gid = tileset->firstGid + tilesetTile.id; + if (tilesetTile.hasAnimation) { /* If the tile is meta, pointing to a series of other tiles */ + gidsToTiles[gid].hasAnimation = true; + gidsToTiles[gid].animation = tilesetTile.animation; + /* Frames' tile (G)IDs are initially set with local values. Now that all tilesets are known */ + /* and this tileset's first global ID is available, update the ID to be global. */ + for (uint32_t k = 0; k < gidsToTiles[gid].animation.framesLength; k++) + gidsToTiles[gid].animation.frames[k].gid += tileset->firstGid; + } + if (tilesetTile.x != 0 || tilesetTile.y != 0 || tilesetTile.width != 0 || tilesetTile.height != 0) { + /* This tile directly tells us the area within the tileset's image to use when drawing, */ + /* overriding the implicit dimensions derived from the map's tile width and height. */ + gidsToTiles[gid].sourceRect.x = (float)tilesetTile.x; + gidsToTiles[gid].sourceRect.y = (float)tilesetTile.y; + gidsToTiles[gid].sourceRect.width = (float)tilesetTile.width; + gidsToTiles[gid].sourceRect.height = (float)tilesetTile.height; + } + /* Tiles may have child object groups. These objects are a form of collision information. The */ + /* object group may be empty or may have objects. A simple assignment covers both. */ + gidsToTiles[gid].objectGroup = tilesetTile.objectGroup; + gidsToTiles[gid].gid = gid; /* Tell the tile its GID */ + } /* for (uint32_t j = 0; j < tileset->tilesLength; j++) */ + /* Second, loop through the implicit tiles. These are inferred from knowing the dimensions of the */ + /* tileset's image, dimensiosn of tiles, and the (right-down) order of tiles within the image. */ + for (uint32_t id = 0; id < tileset->tileCount; id++) { + uint32_t gid = id + tileset->firstGid, x = id % tileset->columns, y = id / tileset->columns; + gidsToTiles[gid].gid = gid; /* Tell the tile its GID */ + /* If that source renctangle within the image was NOT explicitly defined. */ + /* Note: Float comparisons like this are typically unreliable but the GIDs-to-tiles array is */ + /* initialized with zeroes making this accurate. */ + if (gidsToTiles[gid].sourceRect.x == 0.0f && gidsToTiles[gid].sourceRect.y == 0.0f && + gidsToTiles[gid].sourceRect.width == 0.0f && gidsToTiles[gid].sourceRect.height == 0.0f) { + /* Calculate the area within the texture to be drawn from contextual information */ + gidsToTiles[gid].sourceRect.x = (float)(tileset->margin + (x * tileset->tileWidth) + + (x * tileset->spacing)); + gidsToTiles[gid].sourceRect.y = (float)(tileset->margin + (y * tileset->tileHeight) + + (y * tileset->spacing)); + gidsToTiles[gid].sourceRect.width = (float)tileset->tileWidth; + gidsToTiles[gid].sourceRect.height = (float)tileset->tileHeight; + } + gidsToTiles[gid].texture = tileset->image.texture; + gidsToTiles[gid].offset.x = (float)tileset->tileOffsetX; + gidsToTiles[gid].offset.y = (float)tileset->tileOffsetY; + } /* for (uint32_t id = 0; id < tileset->tileCount; id++) */ + } else { /* If the tileset is a collection of images where each tile has its own image */ + for (uint32_t j = 0; j < tileset->tilesLength; j++) { + TmxTilesetTile tilesetTile = tileset->tiles[j]; + if (!tilesetTile.hasImage) { + TraceLog(LOG_WARNING, "RAYTMX: Skipping tile %d of image collection tileset \"%s\" because " + "it has no image", tilesetTile.id, tileset->name); + continue; + } + + int32_t gid = tileset->firstGid + tilesetTile.id; + gidsToTiles[gid].gid = gid; + gidsToTiles[gid].sourceRect.x = (float)tilesetTile.x; /* Defaults to and probably is zero */ + gidsToTiles[gid].sourceRect.y = (float)tilesetTile.y; /* Defaults to and probably is zero */ + if (tilesetTile.width != tilesetTile.image.width) + gidsToTiles[gid].sourceRect.width = (float)tilesetTile.width; + else + gidsToTiles[gid].sourceRect.width = (float)tilesetTile.image.width; + if (tilesetTile.height != tilesetTile.image.height) + gidsToTiles[gid].sourceRect.height = (float)tilesetTile.height; + else + gidsToTiles[gid].sourceRect.height = (float)tilesetTile.image.height; + gidsToTiles[gid].texture = tilesetTile.image.texture; + } + } + } + + map->gidsToTiles = gidsToTiles; + map->gidsToTilesLength = gidsToTilesLength; + } /* gidsToTilesLength > 0 */ + + /* Free the linked lists and zeroize related values */ + FreeState(raytmxState); + + return map; +} + +RAYTMX_DEC void UnloadTMX(TmxMap* map) { + if (map == NULL) + return; + + if (map->fileName != NULL) + MemFree(map->fileName); + + if (map->properties != NULL) { + for (uint32_t i = 0; i < map->propertiesLength; i++) + FreeProperty(map->properties[i]); + MemFree(map->properties); + } + + if (map->tilesets != NULL) { + for (uint32_t i = 0; i < map->tilesetsLength; i++) + FreeTileset(map->tilesets[i]); + MemFree(map->tilesets); + } + + if (map->layers != NULL) { + for (uint32_t i = 0; i < map->layersLength; i++) + FreeLayer(map->layers[i]); + MemFree(map->layers); + } + + if (map->gidsToTiles != NULL) + MemFree(map->gidsToTiles); + + MemFree(map); +} + +RAYTMX_DEC void DrawTMX(const TmxMap* map, const Camera2D* camera, const Rectangle* viewport, int posX, int posY, + Color tint) { + if (map == NULL) + return; + + if (map->hasBackgroundColor) { + Rectangle backgroundRect; + if (viewport != NULL) + backgroundRect = *viewport; + else { + backgroundRect.x = (float)posX; + backgroundRect.y = (float)posY; + backgroundRect.width = (float)(map->width * map->tileWidth); + backgroundRect.height = (float)(map->height * map->tileHeight); + } + + DrawRectangleRec(/* rec: */ backgroundRect, /* color: */ map->backgroundColor); + } + + DrawTMXLayers(map, camera, viewport, map->layers, map->layersLength, posX, posY, tint); +} + +RAYTMX_DEC void DrawTMXLayers(const TmxMap* map, const Camera2D* camera, const Rectangle* viewport, + const TmxLayer* layers, uint32_t layersLength, int posX, int posY, Color tint) +{ + /* Pack some position and related information into an object to pass to various functions. At a minimum, this */ + /* will determine the position each layer is drawn at. With a camera, this will also be used for parallax. */ + RaytmxTransform transform = + { + .position = (Vector2){ .x = (float)posX, .y = (float)posY }, + .parallax = (Vector2){ .x = 1.0f, .y = 1.0f }, + .cameraOffset = (Vector2){ .x = 0.0, .y = 0.0f } + }; + + if (map != NULL && camera != NULL) /* If the map, with its parallax origin, and a camera are available */ + { + /* Calculate the camera's offset, or distance, from the parallax origin. This origin is the reference point */ + /* for parallax scrolling and this offset determines how much a layer will scroll as the camera moves. */ + transform.cameraOffset = (Vector2) + { + .x = camera->target.x - map->parallaxOriginX, + .y = camera->target.y - map->parallaxOriginY + }; + } + + DrawTMXLayersInternal(map, camera, viewport, layers, layersLength, transform, tint); +} + +RAYTMX_DEC void AnimateTMX(TmxMap* map) { + if (map == NULL) + return; + + float dt = GetFrameTime(); /* Returns the duration, in seconds, of the last frame drawn */ + /* Iterate through the tiles, searching for those that are animations */ + for (uint32_t gid = 0; gid < map->gidsToTilesLength; gid++) { + TmxTile* tile = &map->gidsToTiles[gid]; /* A pointer is used in case the frame time needs to be reassigned */ + /* If the GID maps to a valid tile, that tile is an animation, and the bounds check passes */ + if (tile->gid > 0 && tile->hasAnimation && tile->frameIndex < tile->animation.framesLength) { + tile->frameTime += dt; + /* If the current frame has been displayed for its whole duration, or longer */ + if (tile->frameTime > tile->animation.frames[tile->frameIndex].duration) { + tile->frameTime -= tile->animation.frames[tile->frameIndex].duration; + /* Increment the frame index to display the next one... */ + tile->frameIndex += 1; + /* ...unless the last frame was "last" in both senses */ + if (tile->frameIndex == tile->animation.framesLength) + tile->frameIndex = 0; /* Wrap around to the first frame */ + } + } + } +} + +/** + * Helper function that creates a TmxObject equivalent to the given rectangle. + * + * @param rec A rectangle as raylib's Rectangle type. + * @return An equivalent TmxObject. + */ +TmxObject CreateRectangularTMXObject(Rectangle rec) { + TmxObject recAsObject; + memset(&recAsObject, 0, sizeof(TmxObject)); /* Zero initialize */ + + recAsObject.type = OBJECT_TYPE_RECTANGLE; + recAsObject.x = (double)rec.x; + recAsObject.y = (double)rec.y; + recAsObject.width = (double)rec.width; + recAsObject.height = (double)rec.height; + recAsObject.aabb = rec; + + return recAsObject; +} + +RAYTMX_DEC bool CheckCollisionTMXTileLayersRec(const TmxMap* map, const TmxLayer* layers, uint32_t layersLength, + Rectangle rec, TmxObject* outputObject) { + if (map == NULL || layers == NULL || layersLength == 0) + return false; + + /* Check the rectangle against objects associated with tiles in the layers for collisions */ + return CheckCollisionTMXTileLayerObject(map, layers, layersLength, CreateRectangularTMXObject(rec), outputObject); +} + +/** + * Helper function that creates a TmxObject equivalent to the given circle. + * + * @param center The center point of the circle. + * @param radius The radius of the circle. + * @return An equivalent TmxObject. + */ +TmxObject CreateCircularTMXObject(Vector2 center, float radius) { + TmxObject circleAsObject; + memset(&circleAsObject, 0, sizeof(TmxObject)); /* Zero initialize */ + + circleAsObject.type = OBJECT_TYPE_ELLIPSE; + circleAsObject.x = (double)(center.x - radius); + circleAsObject.y = (double)(center.y - radius); + circleAsObject.width = 2.0 * (double)radius; + circleAsObject.height = 2.0 * (double)radius; + circleAsObject.aabb.x = center.x - radius; + circleAsObject.aabb.y = center.y - radius; + circleAsObject.aabb.width = 2.0f * radius; + circleAsObject.aabb.height = 2.0f * radius; + + return circleAsObject; +} + +RAYTMX_DEC bool CheckCollisionTMXTileLayersCircle(const TmxMap* map, const TmxLayer* layers, uint32_t layersLength, + Vector2 center, float radius, TmxObject* outputObject) { + if (map == NULL || layers == NULL || layersLength == 0) + return false; + + /* Check the circle against objects associated with tiles in the layers for collisions */ + return CheckCollisionTMXTileLayerObject(map, layers, layersLength, CreateCircularTMXObject(center, radius), + outputObject); +} + +/** + * Helper function that creates a TmxObject equivalent to the given point. + * + * @param point The point. + * @return An equivalent TmxObject. + */ +TmxObject CreatePointTMXObject(Vector2 point) { + TmxObject pointAsObject; + memset(&pointAsObject, 0, sizeof(TmxObject)); /* Zero initialize */ + + pointAsObject.type = OBJECT_TYPE_POINT; + pointAsObject.x = (float)point.x; + pointAsObject.y = (float)point.y; + pointAsObject.aabb.x = (float)point.x; + pointAsObject.aabb.y = (float)point.y; + + return pointAsObject; +} + +RAYTMX_DEC bool CheckCollisionTMXTileLayersPoint(const TmxMap* map, const TmxLayer* layers, uint32_t layersLength, + Vector2 point, TmxObject* outputObject) { + if (map == NULL || layers == NULL || layersLength == 0) + return false; + + /* Check the point against objects associated with tiles in the layers for collisions */ + return CheckCollisionTMXTileLayerObject(map, layers, layersLength, CreatePointTMXObject(point), outputObject); +} + +/** + * Helper function that creates a TmxObject equivalent to the given polygon with the given Axis-Aligned Bounding Box + * (AABB). If the AABB should be calculated from the given vertices, pass a rectangle with a zero or negative width or + * height. + * + * @param points An array of vertices defining the polygon. No repeats. + * @param pointCount The length of the array of vertices. + * @param aabb Bounding box of the polygon. Used for quicker, broad collision checks. + * @return An equivalent TmxObject. + */ +TmxObject CreatePolygonTMXObject(Vector2* points, int pointCount, Rectangle aabb) { + /* Create a polygon TMX object to represent the given polygon */ + TmxObject polygonAsObject; + memset(&polygonAsObject, 0, sizeof(TmxObject)); /* Zero initialize */ + + polygonAsObject.type = OBJECT_TYPE_POLYGON; + + if (aabb.width <= 0.0f || aabb.height <= 0.0f) { /* If the AABB wasn't pre-calculated or is just wrong */ + /* Calculate the bounding box by searching for minimum and maximum coordinates of the vertices */ + float minX = INFINITY, maxX = -INFINITY, minY = INFINITY, maxY = -INFINITY; + for (int i = 0; i < pointCount; i++) { + Vector2 point = points[i]; + if (point.x < minX) + minX = point.x; + if (point.x > maxX) + maxX = point.x; + if (point.y < minY) + minY = point.y; + if (point.y > maxY) + maxY = point.y; + } + aabb.x = minX; + aabb.y = minY; + aabb.width = maxX - minX; + aabb.height = maxY - minY; + } + + /* Polygons' points are relative to the object's position. Because this function takes points with absolute */ + /* positions, we'll account for this by making the object's position zero. */ + polygonAsObject.x = 0.0; + polygonAsObject.y = 0.0; + polygonAsObject.width = (double)aabb.width; + polygonAsObject.height = (double)aabb.height; + polygonAsObject.aabb = aabb; + polygonAsObject.points = points; + polygonAsObject.pointsLength = pointCount; + + return polygonAsObject; +} + +RAYTMX_DEC bool CheckCollisionTMXLayersPoly(const TmxMap* map, const TmxLayer* layers, uint32_t layersLength, + Vector2* points, int pointCount, TmxObject* outputObject) { + if (map == NULL || layers == NULL || layersLength == 0 || points == NULL || pointCount < 3) + return false; + + /* Check the polygon against objects associated with tiles in the layers for collisions */ + return CheckCollisionTMXTileLayerObject(map, layers, layersLength, + CreatePolygonTMXObject(points, pointCount, (Rectangle){0.0f}), outputObject); +} + +RAYTMX_DEC bool CheckCollisionTMXLayersPolyEx(const TmxMap* map, const TmxLayer* layers, uint32_t layersLength, + Vector2* points, int pointCount, Rectangle aabb, TmxObject* outputObject) { + if (map == NULL || layers == NULL || layersLength == 0 || points == NULL || pointCount < 3) + return false; + + /* Check the polygon against objects associated with tiles in the layers for collisions */ + return CheckCollisionTMXTileLayerObject(map, layers, layersLength, + CreatePolygonTMXObject(points, pointCount, aabb), outputObject); +} + +RAYTMX_DEC bool CheckCollisionTMXObjectGroupRec(TmxObjectGroup group, Rectangle rec, TmxObject* outputObject) { + if (group.objectsLength == 0 || rec.width < 0.0f || rec.height < 0.0f) + return false; /* Early-out opportunity. These cases would always return false. */ + + /* Check the rectangle against TMX objects in the group for collisions */ + return CheckCollisionTMXObjectGroupObject(group, CreateRectangularTMXObject(rec), outputObject); +} + +RAYTMX_DEC bool CheckCollisionTMXObjectGroupCircle(TmxObjectGroup group, Vector2 center, float radius, + TmxObject* outputObject) { + if (group.objectsLength == 0 || radius < 0.0f) + return false; /* Early-out opportunity. These cases would always return false. */ + + /* Check the circle against TMX objects in the group for collisions */ + return CheckCollisionTMXObjectGroupObject(group, CreateCircularTMXObject(center, radius), outputObject); +} + +RAYTMX_DEC bool CheckCollisionTMXObjectGroupPoint(TmxObjectGroup group, Vector2 point, TmxObject* outputObject) { + if (group.objectsLength == 0) + return false; /* Early-out opportunity. This case would always return false. */ + + /* Check the point against TMX objects in the group for collisions */ + return CheckCollisionTMXObjectGroupObject(group, CreatePointTMXObject(point), outputObject); +} + +RAYTMX_DEC bool CheckCollisionTMXObjectGroupPoly(TmxObjectGroup group, Vector2* points, int pointCount, + TmxObject* outputObject) { + if (group.objectsLength == 0 || points == NULL || pointCount < 3) + return false; /* Early-out opportunity. These cases would always return false. */ + + /* Check the polygon TMX object against other TMX objects in the group for collisions */ + return CheckCollisionTMXObjectGroupObject(group, CreatePolygonTMXObject(points, pointCount, (Rectangle){0.0f}), + outputObject); +} + +RAYTMX_DEC bool CheckCollisionTMXObjectGroupPolyEx(TmxObjectGroup group, Vector2* points, int pointCount, + Rectangle aabb, TmxObject* outputObject) { + if (group.objectsLength == 0 || points == NULL || pointCount < 3) + return false; /* Early-out opportunity. These cases would always return false. */ + + /* Check the polygon TMX object against other TMX objects in the group for collisions */ + return CheckCollisionTMXObjectGroupObject(group, CreatePolygonTMXObject(points, pointCount, aabb), outputObject); +} + +static LoadTextureCallback loadTextureOverride = NULL; + +RAYTMX_DEC void SetLoadTextureTMX(LoadTextureCallback callback) { + loadTextureOverride = callback; +} + +static int tmxLogFlags = 0; + +RAYTMX_DEC void TraceLogTMX(int logLevel, const TmxMap* map) { + if (map == NULL) + return; + + switch (map->orientation) { + case ORIENTATION_NONE: TraceLog(logLevel, "orientation: none"); break; + case ORIENTATION_ORTHOGONAL: TraceLog(logLevel, "orientation: orthogonal"); break; + case ORIENTATION_ISOMETRIC: TraceLog(logLevel, "orientation: isometric"); break; + case ORIENTATION_STAGGERED: TraceLog(logLevel, "orientation: staggered"); break; + case ORIENTATION_HEXAGONAL: TraceLog(logLevel, "orientation: hexagonal"); break; + } + + switch (map->renderOrder) { + case RENDER_ORDER_RIGHT_DOWN: TraceLog(logLevel, "render order: right-down"); break; + case RENDER_ORDER_RIGHT_UP: TraceLog(logLevel, "render order: right-up"); break; + case RENDER_ORDER_LEFT_DOWN: TraceLog(logLevel, "render order: left-down"); break; + case RENDER_ORDER_LEFT_UP: TraceLog(logLevel, "render order: left-up"); break; + } + + TraceLog(logLevel, "width: %u tiles", map->width); + TraceLog(logLevel, "height: %u tiles", map->height); + TraceLog(logLevel, "tile width: %u pixels", map->tileWidth); + TraceLog(logLevel, "tile height: %u pixels", map->tileHeight); + TraceLog(logLevel, "parallax origin X: %d pixels", map->parallaxOriginX); + TraceLog(logLevel, "parallax origin Y: %d pixels", map->parallaxOriginY); + if (map->hasBackgroundColor) + TraceLog(logLevel, "background color: 0x%08X", map->backgroundColor); + + TraceLogTMXTilesets(logLevel, map->orientation, map->tilesets, map->tilesetsLength, 0); + TraceLogTMXProperties(logLevel, map->properties, map->propertiesLength, 0); + TraceLogTMXLayers(logLevel, map->layers, map->layersLength, 0); +} + +RAYTMX_DEC void SetTraceLogFlagsTMX(int logFlags) { + tmxLogFlags = logFlags; +} + +/**********************************************************************************************************************/ +/* Private implementation. */ + +RaytmxExternalTileset LoadTSX(const char* fileName) { + RaytmxState raytmxState[1]; + memset(raytmxState, 0, sizeof(RaytmxState)); /* Initialize all values to zero, NULL, or an equivalent enum value */ + raytmxState->format = FORMAT_TSX; + + /* Initialize an external tileset object */ + RaytmxExternalTileset externalTileset; + memset(&externalTileset, 0, sizeof(RaytmxExternalTileset)); + + /* Do format-agnostic parsing of the document. The state object will be populated with raytmx's models of the */ + /* equivalent TMX, TSX, and/or TX elements. */ + ParseDocument(raytmxState, fileName); + if (!raytmxState->isSuccess) + return externalTileset; /* Will have 'isSuccess' set to false to indicate a failure */ + + if (raytmxState->tilesetsRoot != NULL) { /* If there is at least one tileset */ + /* Copy the root tileset so it can be returned */ + externalTileset.tileset = raytmxState->tilesetsRoot->tileset; + externalTileset.isSuccess = true; + /* TSX files should have only one tileset so any others will be freed/unloaded immediately */ + RaytmxTilesetNode* tilesetIterator = raytmxState->tilesetsRoot->next; + while (tilesetIterator != NULL) { + FreeTileset(tilesetIterator->tileset); + tilesetIterator = tilesetIterator->next; + } + } else + TraceLog(LOG_WARNING, "RAYTMX: TSX file (external tileset) \"%s\" does not contain any tilesets", fileName); + + /* Free the linked lists and zeroize related values */ + FreeState(raytmxState); + + return externalTileset; +} + +RaytmxObjectTemplate LoadTX(const char* fileName) { + RaytmxState raytmxState[1]; + memset(raytmxState, 0, sizeof(RaytmxState)); /* Initialize all values to zero, NULL, or an equivalent enum value */ + raytmxState->format = FORMAT_TX; + + /* Initialize an object template object */ + RaytmxObjectTemplate objectTemplate; + memset(&objectTemplate, 0, sizeof(RaytmxObjectTemplate)); + + /* Do format-agnostic parsing of the document. The state object will be populated with raytmx's models of the */ + /* equivalent TMX, TSX, and/or TX elements. */ + ParseDocument(raytmxState, fileName); + if (!raytmxState->isSuccess) + return objectTemplate; /* Will have 'isSuccess' set to false to indicate a failure */ + + if (raytmxState->objectsRoot != NULL) { /* If there is at least one object */ + /* Copy the root object so it can be returned */ + objectTemplate.object = raytmxState->objectsRoot->object; + objectTemplate.isSuccess = true; + /* TX files should have only one object so any others will be freed/unloaded immediately */ + RaytmxObjectNode* objectIterator = raytmxState->objectsRoot->next; + while (objectIterator != NULL) { + FreeObject(objectIterator->object); + objectIterator = objectIterator->next; + } + } else + TraceLog(LOG_WARNING, "RAYTMX: TX file (object template) \"%s\" does not contain any objects", fileName); + + if (raytmxState->tilesetsRoot != NULL) { /* If there is at least one tileset */ + /* Object templates may have a tileset. This is for cases where the object references a tile (i.e. its 'gid' */ + /* attribute is set). Copy the root tileset so it can be returned */ + objectTemplate.tileset = raytmxState->tilesetsRoot->tileset; + objectTemplate.hasTileset = true; + /* TX files should have at most one tileset so any others will be freed/unloaded immediately */ + RaytmxTilesetNode* tilesetsIterator = raytmxState->tilesetsRoot->next; + while (tilesetsIterator != NULL) { + FreeTileset(tilesetsIterator->tileset); + tilesetsIterator = tilesetsIterator->next; + } + } + + /* Free the linked lists and zeroize related values */ + FreeState(raytmxState); + + return objectTemplate; +} + +void ParseDocument(RaytmxState* raytmxState, const char* fileName) { + char* content = LoadFileText(fileName); + if (content == NULL) { + TraceLog(LOG_ERROR, "RAYTMX: Failed to open \"%s\"", fileName); + return; + } + size_t contentLength = strlen(content); + + StringCopy(raytmxState->documentDirectory, GetDirectoryPath2(fileName)); + + hoxml_context_t hoxmlContext[1]; + size_t bufferLength = contentLength; + char* buffer = (char*)MemAlloc((unsigned int)bufferLength); + hoxml_init(hoxmlContext, buffer, bufferLength); + + hoxml_code_t code; + while ((code = hoxml_parse(hoxmlContext, content, contentLength)) != HOXML_END_OF_DOCUMENT) { + if (code > HOXML_END_OF_DOCUMENT) { /* If there's information about an element, attribute, whatever */ + switch (code) { + case HOXML_ELEMENT_BEGIN: HandleElementBegin(raytmxState, hoxmlContext); break; + case HOXML_ELEMENT_END: HandleElementEnd(raytmxState, hoxmlContext); break; + case HOXML_ATTRIBUTE: HandleAttribute(raytmxState, hoxmlContext); + case HOXML_PROCESSING_INSTRUCTION_BEGIN: break; + case HOXML_PROCESSING_INSTRUCTION_END: break; + default: break; /* No other cases to handle but compilers like to complain */ + } + } else if (code < HOXML_END_OF_DOCUMENT) { /* If there was an error, recoverable or not */ + switch (code) { + case HOXML_ERROR_INSUFFICIENT_MEMORY: { + /* This is one we can recover from by expanding the buffer. In this case, it will be doubled. */ + TraceLog(LOG_DEBUG, "RAYTMX: Allocating a new XML parsing buffer due to insufficient memory"); + bufferLength *= 2; + char* newBuffer = (char*)MemAlloc((unsigned int)bufferLength); + hoxml_realloc(hoxmlContext, newBuffer, bufferLength); + MemFree(buffer); + buffer = newBuffer; + continue; + } case HOXML_ERROR_UNEXPECTED_EOF: + TraceLog(LOG_ERROR, "RAYTMX: Unexpected end of file"); + break; + case HOXML_ERROR_SYNTAX: + TraceLog(LOG_ERROR, "RAYTMX: Invalid syntax: line %d, column %d", hoxmlContext->line, + hoxmlContext->column); + break; + case HOXML_ERROR_ENCODING: + TraceLog(LOG_ERROR, "RAYTMX: Character encoding error: line %d, column %d", hoxmlContext->line, + hoxmlContext->column); + break; + case HOXML_ERROR_TAG_MISMATCH: + TraceLog(LOG_ERROR, "RAYTMX: Close tag does not match open tag: line %d, column %d", + hoxmlContext->line, hoxmlContext->column); + break; + case HOXML_ERROR_INVALID_DOCUMENT_TYPE_DECLARATION: + case HOXML_ERROR_INVALID_DOCUMENT_DECLARATION: + TraceLog(LOG_ERROR, "RAYTMX: Document (type) declaration error: line %d, column %d", + hoxmlContext->line, hoxmlContext->column); + break; + default: break; /* Keep the compiler happy */ + } + UnloadFileText(content); + return; + } + } + + UnloadFileText(content); + MemFree(buffer); + raytmxState->isSuccess = true; +} + +void HandleElementBegin(RaytmxState* raytmxState, hoxml_context_t* hoxmlContext) { + if (raytmxState == NULL || hoxmlContext == NULL) + return; + + if (strcmp(hoxmlContext->tag, "map") == 0) + ; + else if (strcmp(hoxmlContext->tag, "properties") == 0) { + /* TMX allows nested properties but they are not (currently?) supported. To avoid memory leaks <properties> */ + /* depth is tracked. */ + raytmxState->propertiesDepth += 1; + } else if (strcmp(hoxmlContext->tag, "property") == 0) + raytmxState->property = AddProperty(raytmxState); + else if (strcmp(hoxmlContext->tag, "tileset") == 0) + raytmxState->tileset = AddTileset(raytmxState); + else if (strcmp(hoxmlContext->tag, "image") == 0) { + /* If any of the elements that may have an image is/are open */ + if (raytmxState->tilesetTile != NULL || raytmxState->tileset != NULL || raytmxState->imageLayer != NULL) { + /* If the open element already has an image */ + /* Note: In all cases, the element can contain at most one <image> */ + if ((raytmxState->tilesetTile != NULL && raytmxState->tilesetTile->hasImage) || + (raytmxState->tileset != NULL && raytmxState->tileset->hasImage) || + (raytmxState->imageLayer != NULL && raytmxState->imageLayer->hasImage)) { + TraceLog(LOG_WARNING, "RAYTMX: an element contained multiple images; the image on line %d will be " + "dropped", hoxmlContext->line); + } else { + /* <image> elements can be children of <tileset>, <tile>, or <imagelayer> elements */ + if (raytmxState->tilesetTile != NULL) { + raytmxState->tilesetTile->hasImage = true; + raytmxState->image = &raytmxState->tilesetTile->image; + } else if (raytmxState->tileset != NULL) { + raytmxState->tileset->hasImage = true; + raytmxState->image = &raytmxState->tileset->image; + } else if (raytmxState->imageLayer != NULL) { + raytmxState->imageLayer->hasImage = true; + raytmxState->image = &raytmxState->imageLayer->image; + } + } + } + } /* strcmp(hoxmlContext->tag, "image") == 0 */ + else if (strcmp(hoxmlContext->tag, "tile") == 0) { + /* <tile> elements can be children of <tileset> or <layer>. They are also not the same element in that they */ + /* have entirely different attributes and a tileset's <tile> may have children. */ + if (raytmxState->tileset != NULL) + raytmxState->tilesetTile = AddTilesetTile(raytmxState); + /* Layer <tile>s are added during attribute handling because they provide a GID attribute and nothing else */ + } /* strcmp(hoxmlContext->tag, "tile") == 0 */ + else if (strcmp(hoxmlContext->tag, "animation") == 0) { + if (raytmxState->tilesetTile != NULL) + raytmxState->tilesetTile->hasAnimation = true; + } else if (strcmp(hoxmlContext->tag, "frame") == 0) + raytmxState->animationFrame = AddAnimationFrame(raytmxState); + else if (strcmp(hoxmlContext->tag, "layer") == 0) { + /* Allocate a new layer with 'tileLayer' allocated and append it to the current group, if it exists */ + raytmxState->layer = AddGenericLayer(raytmxState, /* isGroup: */ false); + raytmxState->layer->type = LAYER_TYPE_TILE_LAYER; + raytmxState->tileLayer = &raytmxState->layer->exact.tileLayer; + } else if (strcmp(hoxmlContext->tag, "objectgroup") == 0) { + if (raytmxState->tilesetTile != NULL) { /* If the object group is a child of a <tile>, it's collision info */ + raytmxState->objectGroup = &raytmxState->tilesetTile->objectGroup; + /* Child objects (rectangles, points, ellipses, or polygons) are expected to follow */ + } else { + /* Allocate a new layer with 'objectGroup' allocated and append it to the current group, if it exists */ + raytmxState->layer = AddGenericLayer(raytmxState, /* isGroup: */ false); + raytmxState->layer->type = LAYER_TYPE_OBJECT_GROUP; + raytmxState->objectGroup = &raytmxState->layer->exact.objectGroup; + } + } else if (strcmp(hoxmlContext->tag, "object") == 0) { + /* <object> elements are typically only allowable as children of <objectgroup>s but object templates, TX */ + /* files, contain them as children of root <template> */ + if (raytmxState->objectGroup != NULL || raytmxState->format == FORMAT_TX) + raytmxState->object = AddObject(raytmxState); + } else if (strcmp(hoxmlContext->tag, "ellipse") == 0) { + if (raytmxState->object != NULL) { + /* An <ellipse> within an <object> indicates its type but the <object>'s 'x,' 'y,' 'width,' and 'height' */ + /* attributes are used to define the ellipse so assigning the type is all that's necessary */ + raytmxState->object->type = OBJECT_TYPE_ELLIPSE; + } + } else if (strcmp(hoxmlContext->tag, "point") == 0) { + if (raytmxState->object != NULL) { + /* A <point> within an <object> indicates its type but the <object>'s 'x' and 'y' attributes are used to */ + /* define the point so assigning the type is all that's necessary */ + raytmxState->object->type = OBJECT_TYPE_POINT; + } + } else if (strcmp(hoxmlContext->tag, "polygon") == 0) { + if (raytmxState->object != NULL) { + /* Note: <polygon>s and <polyline>s have a list of points/vertices defined in a 'points' attribute */ + raytmxState->object->type = OBJECT_TYPE_POLYGON; + } + } else if (strcmp(hoxmlContext->tag, "polyline") == 0) { + if (raytmxState->object != NULL) { + /* Note: <polyline>s and <polygone>s have a list of points/vertices defined in a 'points' attribute */ + raytmxState->object->type = OBJECT_TYPE_POLYLINE; + } + } else if (strcmp(hoxmlContext->tag, "text") == 0) { + if (raytmxState->object != NULL) { + raytmxState->object->type = OBJECT_TYPE_TEXT; + raytmxState->object->text = (TmxText*)MemAllocZero(sizeof(TmxText)); + /* There are a couple non-zero default values for <text> attributes: */ + raytmxState->object->text->pixelSize = 16; + raytmxState->object->text->color.a = 255; /* Full opacity black */ + raytmxState->object->text->kerning = 1; + /* The font family will also default to "sans-serif" when the element ends if there is no attribute */ + } + } else if (strcmp(hoxmlContext->tag, "imagelayer") == 0) { + /* Allocate a new layer with 'imageLayer' allocated and append it to the current group, if it exists */ + raytmxState->layer = AddGenericLayer(raytmxState, /* isGroup: */ false); + raytmxState->layer->type = LAYER_TYPE_IMAGE_LAYER; + raytmxState->imageLayer = &raytmxState->layer->exact.imageLayer; + } else if (strcmp(hoxmlContext->tag, "group") == 0) { + /* Allocate a new layer and append it to the current group, if it exists */ + raytmxState->layer = AddGenericLayer(raytmxState, /* isGroup: */ true); + raytmxState->layer->type = LAYER_TYPE_GROUP; + } +} + +void HandleAttribute(RaytmxState* raytmxState, hoxml_context_t* hoxmlContext) { + if (raytmxState == NULL || hoxmlContext == NULL) + return; + + if (strcmp(hoxmlContext->tag, "map") == 0) { + if (strcmp(hoxmlContext->attribute, "orientation") == 0) { + if (strcmp(hoxmlContext->value, "orthogonal") == 0) + raytmxState->mapOrientation = ORIENTATION_ORTHOGONAL; + else if (strcmp(hoxmlContext->value, "isometric") == 0) + raytmxState->mapOrientation = ORIENTATION_ISOMETRIC; + else if (strcmp(hoxmlContext->value, "staggered") == 0) + raytmxState->mapOrientation = ORIENTATION_STAGGERED; + else if (strcmp(hoxmlContext->value, "hexagonal") == 0) + raytmxState->mapOrientation = ORIENTATION_HEXAGONAL; + } /* strcmp(hoxmlContext->attribute, "orientation") == 0 */ + else if (strcmp(hoxmlContext->attribute, "renderorder") == 0) { + if (strcmp(hoxmlContext->value, "right-down") == 0) + raytmxState->mapRenderOrder = RENDER_ORDER_RIGHT_DOWN; + else if (strcmp(hoxmlContext->value, "right-up") == 0) + raytmxState->mapRenderOrder = RENDER_ORDER_RIGHT_UP; + else if (strcmp(hoxmlContext->value, "left-down") == 0) + raytmxState->mapRenderOrder = RENDER_ORDER_LEFT_DOWN; + else if (strcmp(hoxmlContext->value, "left-up") == 0) + raytmxState->mapRenderOrder = RENDER_ORDER_LEFT_UP; + } /* strcmp(hoxmlContext->attribute, "renderorder") == 0 */ + else if (strcmp(hoxmlContext->attribute, "width") == 0) + raytmxState->mapWidth = atoi(hoxmlContext->value); + else if (strcmp(hoxmlContext->attribute, "height") == 0) + raytmxState->mapHeight = atoi(hoxmlContext->value); + else if (strcmp(hoxmlContext->attribute, "tilewidth") == 0) + raytmxState->mapTileWidth = atoi(hoxmlContext->value); + else if (strcmp(hoxmlContext->attribute, "tileheight") == 0) + raytmxState->mapTileHeight = atoi(hoxmlContext->value); + else if (strcmp(hoxmlContext->attribute, "parallaxoriginx") == 0) + raytmxState->mapParallaxOriginX = atoi(hoxmlContext->value); + else if (strcmp(hoxmlContext->attribute, "parallaxoriginy") == 0) + raytmxState->mapParallaxOriginY = atoi(hoxmlContext->value); + else if (strcmp(hoxmlContext->attribute, "backgroundcolor") == 0) { + raytmxState->mapBackgroundColor = GetColorFromHexString(hoxmlContext->value); + raytmxState->mapHasBackgroundColor = true; + } + } /* strcmp(hoxmlContext->tag, "map") == 0 */ + else if (strcmp(hoxmlContext->tag, "property") == 0) { + if (raytmxState->property != NULL) { + if (strcmp(hoxmlContext->attribute, "name") == 0) { + raytmxState->property->name = (char*)MemAllocZero((unsigned int)strlen(hoxmlContext->value) + 1); + StringCopy(raytmxState->property->name, hoxmlContext->value); + } else if (strcmp(hoxmlContext->attribute, "type") == 0) { + if (strcmp(hoxmlContext->value, "string") == 0) + raytmxState->property->type = PROPERTY_TYPE_STRING; + else if (strcmp(hoxmlContext->value, "int") == 0) + raytmxState->property->type = PROPERTY_TYPE_INT; + else if (strcmp(hoxmlContext->value, "float") == 0) + raytmxState->property->type = PROPERTY_TYPE_FLOAT; + else if (strcmp(hoxmlContext->value, "bool") == 0) + raytmxState->property->type = PROPERTY_TYPE_BOOL; + else if (strcmp(hoxmlContext->value, "color") == 0) + raytmxState->property->type = PROPERTY_TYPE_COLOR; + else if (strcmp(hoxmlContext->value, "file") == 0) + raytmxState->property->type = PROPERTY_TYPE_FILE; + else if (strcmp(hoxmlContext->value, "object") == 0) + raytmxState->property->type = PROPERTY_TYPE_OBJECT; + /* TMX documentation also mentions a "class" type but doesn't describe what it is nor does Tiled list */ + /* it as an option when adding a property. So what is it? Unsupported, that's what. */ + } /* strcmp(hoxmlContext->attribute, "type") == 0 */ + else if (strcmp(hoxmlContext->attribute, "value") == 0) { + /* Although unlikley, it's possible that 'value' attribute will be parsed before the 'type' */ + /* attribute. In that case, doing a cast/conversion now may not be possible. To avoid this, the raw */ + /* string value is copied to 'stringValue' temporarily, or permanently for string and file types, and */ + /* the cast/conversion will happen at the end of the element if needed. */ + raytmxState->property->stringValue = (char*)MemAlloc((unsigned int)strlen(hoxmlContext->value) + 1); + StringCopy(raytmxState->property->stringValue, hoxmlContext->value); + } /* strcmp(hoxmlContext->attribute, "value") == 0 */ + } /* raytmxState->property != NULL */ + } /* strcmp(hoxmlContext->tag, "property") == 0 */ + else if (strcmp(hoxmlContext->tag, "tileset") == 0) { + if (raytmxState->tileset != NULL) { + if (strcmp(hoxmlContext->attribute, "firstgid") == 0) + raytmxState->tileset->firstGid = atoi(hoxmlContext->value); + else if (strcmp(hoxmlContext->attribute, "source") == 0) { + raytmxState->tileset->source = (char*)MemAlloc((unsigned int)strlen(hoxmlContext->value) + 1); + StringCopy(raytmxState->tileset->source, hoxmlContext->value); + /* 'source' points to an external TSX file that defines the majority of the tileset. Try to load it. */ + RaytmxExternalTileset externalTileset = LoadTSX(JoinPath(raytmxState->documentDirectory, + hoxmlContext->value)); + if (externalTileset.isSuccess) { + /* A <tileset> within a <map> will have two attributes: 'firstgid' and 'source.' The rest of */ + /* the tileset's details are in the external TSX that 'source' points to. They need to be merged. */ + /* Remember the two internal attributes. */ + uint32_t tempFirstGid = raytmxState->tileset->firstGid; + char* tempSource = raytmxState->tileset->source; + /* Assign all values from the TSX's tileset to the one within the state object. This will */ + /* overrite the values of 'firstGid' and 'source.' */ + *raytmxState->tileset = externalTileset.tileset; + /* Reassign the original 'firstGid' and 'source' values */ + raytmxState->tileset->firstGid = tempFirstGid; + raytmxState->tileset->source = tempSource; + } + } else if (strcmp(hoxmlContext->attribute, "name") == 0) { + raytmxState->tileset->name = (char*)MemAllocZero((unsigned int)strlen(hoxmlContext->value) + 1); + StringCopy(raytmxState->tileset->name, hoxmlContext->value); + } else if (strcmp(hoxmlContext->attribute, "class") == 0) { + raytmxState->tileset->classString = (char*)MemAllocZero((unsigned int)strlen(hoxmlContext->value) + 1); + StringCopy(raytmxState->tileset->classString, hoxmlContext->value); + } else if (strcmp(hoxmlContext->attribute, "tilewidth") == 0) + raytmxState->tileset->tileWidth = atoi(hoxmlContext->value); + else if (strcmp(hoxmlContext->attribute, "tileheight") == 0) + raytmxState->tileset->tileHeight = atoi(hoxmlContext->value); + else if (strcmp(hoxmlContext->attribute, "spacing") == 0) + raytmxState->tileset->spacing = atoi(hoxmlContext->value); + else if (strcmp(hoxmlContext->attribute, "margin") == 0) + raytmxState->tileset->margin = atoi(hoxmlContext->value); + else if (strcmp(hoxmlContext->attribute, "tilecount") == 0) + raytmxState->tileset->tileCount = atoi(hoxmlContext->value); + else if (strcmp(hoxmlContext->attribute, "columns") == 0) + raytmxState->tileset->columns = atoi(hoxmlContext->value); + else if (strcmp(hoxmlContext->attribute, "objectalignment") == 0) { + if (strcmp(hoxmlContext->value, "unspecified") == 0) + raytmxState->tileset->objectAlignment = OBJECT_ALIGNMENT_UNSPECIFIED; + else if (strcmp(hoxmlContext->value, "topleft") == 0) + raytmxState->tileset->objectAlignment = OBJECT_ALIGNMENT_TOP_LEFT; + else if (strcmp(hoxmlContext->value, "top") == 0) + raytmxState->tileset->objectAlignment = OBJECT_ALIGNMENT_TOP; + else if (strcmp(hoxmlContext->value, "topright") == 0) + raytmxState->tileset->objectAlignment = OBJECT_ALIGNMENT_TOP_RIGHT; + else if (strcmp(hoxmlContext->value, "left") == 0) + raytmxState->tileset->objectAlignment = OBJECT_ALIGNMENT_LEFT; + else if (strcmp(hoxmlContext->value, "center") == 0) + raytmxState->tileset->objectAlignment = OBJECT_ALIGNMENT_CENTER; + else if (strcmp(hoxmlContext->value, "right") == 0) + raytmxState->tileset->objectAlignment = OBJECT_ALIGNMENT_RIGHT; + else if (strcmp(hoxmlContext->value, "bottomleft") == 0) + raytmxState->tileset->objectAlignment = OBJECT_ALIGNMENT_BOTTOM_LEFT; + else if (strcmp(hoxmlContext->value, "bottom") == 0) + raytmxState->tileset->objectAlignment = OBJECT_ALIGNMENT_BOTTOM; + else if (strcmp(hoxmlContext->value, "bottomright") == 0) + raytmxState->tileset->objectAlignment = OBJECT_ALIGNMENT_BOTTOM_RIGHT; + } + } /* raytmState->tileset != NULL */ + } /* strcmp(hoxmlContext->tag, "tileset") == 0 */ + else if (strcmp(hoxmlContext->tag, "tileoffset") == 0) { + if (raytmxState->tileset != NULL) { + if (strcmp(hoxmlContext->attribute, "x") == 0) + raytmxState->tileset->tileOffsetX = atoi(hoxmlContext->value); + else if (strcmp(hoxmlContext->attribute, "y") == 0) + raytmxState->tileset->tileOffsetY = atoi(hoxmlContext->value); + } + } /* strcmp(hoxmlContext->tag, "tileoffset") == 0 */ + else if (strcmp(hoxmlContext->tag, "image") == 0) { + if (raytmxState->image != NULL) { + if (strcmp(hoxmlContext->attribute, "source") == 0) { + raytmxState->image->source = (char*)MemAllocZero((unsigned int)strlen(hoxmlContext->value) + 1); + StringCopy(raytmxState->image->source, hoxmlContext->value); + RaytmxCachedTextureNode* cachedTexture = LoadCachedTexture(raytmxState, hoxmlContext->value); + if (cachedTexture != NULL) + raytmxState->image->texture = cachedTexture->texture; + } else if (strcmp(hoxmlContext->attribute, "trans") == 0) { + raytmxState->image->trans = GetColorFromHexString(hoxmlContext->value); + raytmxState->image->hasTrans = true; + } else if (strcmp(hoxmlContext->attribute, "width") == 0) + raytmxState->image->width = atoi(hoxmlContext->value); + else if (strcmp(hoxmlContext->attribute, "height") == 0) + raytmxState->image->height = atoi(hoxmlContext->value); + } + } /* strcmp(hoxmlContext->tag, "image") == 0 */ + else if (strcmp(hoxmlContext->tag, "tile") == 0) { + if (raytmxState->tilesetTile != NULL) { /* If the <tile> corresponds to a tileset tile */ + if (strcmp(hoxmlContext->attribute, "id") == 0) + raytmxState->tilesetTile->id = atoi(hoxmlContext->value); + else if (strcmp(hoxmlContext->attribute, "type") == 0 || strcmp(hoxmlContext->attribute, "class") == 0) { + raytmxState->tilesetTile->classString = + (char*)MemAllocZero((unsigned int)strlen(hoxmlContext->value) + 1); + StringCopy(raytmxState->tilesetTile->classString, hoxmlContext->value); + } else if (strcmp(hoxmlContext->attribute, "x") == 0) + raytmxState->tilesetTile->x = atoi(hoxmlContext->value); + else if (strcmp(hoxmlContext->attribute, "y") == 0) + raytmxState->tilesetTile->y = atoi(hoxmlContext->value); + else if (strcmp(hoxmlContext->attribute, "width") == 0) + raytmxState->tilesetTile->width = atoi(hoxmlContext->value); + else if (strcmp(hoxmlContext->attribute, "height") == 0) + raytmxState->tilesetTile->height = atoi(hoxmlContext->value); + } else { /* If the <tile> corresponds to a layer tile */ + if (strcmp(hoxmlContext->attribute, "gid") == 0) + AddTileLayerTile(raytmxState, atoi(hoxmlContext->value)); + } + } /* strcmp(hoxmlContext->tag, "tile") == 0 */ + else if (strcmp(hoxmlContext->tag, "frame") == 0) { + if (raytmxState->animationFrame != NULL) { + if (strcmp(hoxmlContext->attribute, "tileid") == 0) + raytmxState->animationFrame->gid = atoi(hoxmlContext->value); + else if (strcmp(hoxmlContext->attribute, "duration") == 0) + raytmxState->animationFrame->duration = (float)atoi(hoxmlContext->value) / 1000.0f; + } + } /* strcmp(hoxmlContext->tag, "frame") == 0 */ + else if (strcmp(hoxmlContext->tag, "layer") == 0) { + if (raytmxState->tileLayer != NULL) { + /* Check for attributes specific to <layer> layers */ + if (strcmp(hoxmlContext->attribute, "width") == 0) + raytmxState->tileLayer->width = atoi(hoxmlContext->value); + else if (strcmp(hoxmlContext->attribute, "height") == 0) + raytmxState->tileLayer->height = atoi(hoxmlContext->value); + } + } /* strcmp(hoxmlContext->tag, "layer") == 0) */ + else if (strcmp(hoxmlContext->tag, "data") == 0) { + if (raytmxState->tileLayer != NULL) { /* If this <data> applies to a <layer> */ + if (strcmp(hoxmlContext->attribute, "encoding") == 0) { + raytmxState->tileLayer->encoding = (char*)MemAllocZero((unsigned int)strlen(hoxmlContext->value) + 1); + StringCopy(raytmxState->tileLayer->encoding, hoxmlContext->value); + } else if (strcmp(hoxmlContext->attribute, "compression") == 0) { + raytmxState->tileLayer->compression = + (char*)MemAllocZero((unsigned int)strlen(hoxmlContext->value) + 1); + StringCopy(raytmxState->tileLayer->compression, hoxmlContext->value); + } + } else if (raytmxState->image != NULL) { /* If this <data> applies to an <image> */ + /* TODO (?): The TMX map format documentation says an <image> can contain a <data> element but doesn't */ + /* provide any more information than that. Tiled doesn't seem to have a feature for this either. */ + } + } /* strcmp(hoxmlContext->tag, "data") == 0 */ + else if (strcmp(hoxmlContext->tag, "objectgroup") == 0) { + if (raytmxState->objectGroup != NULL) { + /* Check for attributes specific to <objectgroup> layers */ + if (strcmp(hoxmlContext->attribute, "color") == 0) { + raytmxState->objectGroup->color = GetColorFromHexString(hoxmlContext->value); + raytmxState->objectGroup->hasColor = true; + } /* else if (strcmp(hoxmlContext->attribute, "width") == 0) + raytmxState->objectGroup->width = atoi(hoxmlContext->value); */ /* "Meaningless" according to docs. */ + /* else if (strcmp(hoxmlContext->attribute, "height") == 0) + raytmxState->objectGroup->height = atoi(hoxmlContext->value); */ /* "Meaningless" according to docs. */ + else if (strcmp(hoxmlContext->attribute, "draworder") == 0) { + if (strcmp(hoxmlContext->value, "index") == 0) + raytmxState->objectGroup->drawOrder = OBJECT_GROUP_DRAW_ORDER_INDEX; + else if (strcmp(hoxmlContext->value, "topdown") == 0) + raytmxState->objectGroup->drawOrder = OBJECT_GROUP_DRAW_ORDER_TOP_DOWN; + } + } + } /* strcmp(hoxmlContext->tag, "objectgroup") == 0 */ + else if (strcmp(hoxmlContext->tag, "object") == 0) { + if (raytmxState->object != NULL) { + if (strcmp(hoxmlContext->attribute, "id") == 0) + raytmxState->object->id = atoi(hoxmlContext->value); + else if (strcmp(hoxmlContext->attribute, "name") == 0) { + raytmxState->object->name = (char*)MemAllocZero((unsigned int)strlen(hoxmlContext->value) + 1); + StringCopy(raytmxState->object->name, hoxmlContext->value); + } else if (strcmp(hoxmlContext->attribute, "type") == 0) { + raytmxState->object->typeString = (char*)MemAllocZero((unsigned int)strlen(hoxmlContext->value) + 1); + StringCopy(raytmxState->object->typeString, hoxmlContext->value); + } else if (strcmp(hoxmlContext->attribute, "x") == 0) + raytmxState->object->x = atof(hoxmlContext->value); + else if (strcmp(hoxmlContext->attribute, "y") == 0) + raytmxState->object->y = atof(hoxmlContext->value); + else if (strcmp(hoxmlContext->attribute, "width") == 0) + raytmxState->object->width = atof(hoxmlContext->value); + else if (strcmp(hoxmlContext->attribute, "height") == 0) + raytmxState->object->height = atof(hoxmlContext->value); + else if (strcmp(hoxmlContext->attribute, "rotation") == 0) + raytmxState->object->rotation = atof(hoxmlContext->value); + else if (strcmp(hoxmlContext->attribute, "gid") == 0) { + raytmxState->object->gid = atoi(hoxmlContext->value); + /* The presence of a 'gid' attribute also indicates the object's type is that of a tile */ + raytmxState->object->type = OBJECT_TYPE_TILE; + } else if (strcmp(hoxmlContext->attribute, "visible") == 0) + raytmxState->object->visible = atoi(hoxmlContext->value) != 0 ? true : false; + else if (strcmp(hoxmlContext->attribute, "template") == 0) { + raytmxState->object->templateString = + (char*)MemAllocZero((unsigned int)strlen(hoxmlContext->value) + 1); + StringCopy(raytmxState->object->templateString, hoxmlContext->value); + } + } + } /* strcmp(hoxmlContext->tag, "object") == 0 */ + else if (strcmp(hoxmlContext->tag, "polygon") == 0 || strcmp(hoxmlContext->tag, "polyline") == 0) { + /* <polygon> and <polyline>, children of <object>, both have just one attribute: 'points' */ + if (raytmxState->object != NULL && strcmp(hoxmlContext->attribute, "points") == 0) { + if (raytmxState->object->points != NULL) { /* If there's already an array of points */ + TraceLog(LOG_WARNING, "RAYTMX: object \"%s\", has multiple 'points' attributes; points listed in any " + "latter 'points' attributes will be dropped", raytmxState->object->name); + return; + } + /* The 'points' attribute's value is a string containing all points of the poly(gon|line) in the form */ + /* "0,0 31.25,-0.75 49.5,-16.5 93.25,-17.25" where the points are [0, 0], [31.25, -0.75], etc. and these */ + /* points are relative to the object's position (its 'x' and 'y' attributes) */ + char x[32], y[32], *iterator, *terminator, *comma; + iterator = hoxmlContext->value; + RaytmxPolyPointNode *pointsRoot = NULL, *pointsTail = NULL; + uint32_t pointsLength = 0; + Vector2 vertexSum; /* Specific to polygons, the centroid is calculated requiring a sum of all vertices */ + vertexSum.x = 0; + vertexSum.y = 0; + while (iterator != NULL) { + terminator = strstr(iterator, " "); /* Use this pointer to mark the end of the current point */ + if (terminator == NULL) { /* There are no space delimiters this far into the value string */ + /* This is the end of the value string so we'll use a regular null terminator instead */ + terminator = iterator + strlen(iterator); + } + comma = strstr(iterator, ","); /* Use this pointer to mark the location of the comma between X and Y */ + if (comma == NULL) { + /* The string is malformed. Cannot continue. */ + TraceLog(LOG_WARNING, "RAYTMX: The 'points' attribute on line %d has a malformed value; at least " + "one point is lost as a result", hoxmlContext->line); + break; + } + /* 'iterator' is pointing to the first digit, 'comma' is pointing to the comma, and 'terminator' is */ + /* pointing to either the terminating space or a typical null terminator */ + StringCopyN(x, iterator, comma - iterator); /* Copy from 'iterator' up to but excluding 'comma' */ + x[comma - iterator] = '\0'; + iterator = comma + 1; /* Point the iterator right after the ',' where Y begins */ + StringCopyN(y, iterator, terminator - iterator); /* Copy 'iterator' up to but excluding 'terminator' */ + y[terminator - iterator] = '\0'; + /* Create a linked list node to hold the point and append it to the linked list */ + RaytmxPolyPointNode* node = (RaytmxPolyPointNode*)MemAllocZero(sizeof(RaytmxPolyPointNode)); + /* Note: These values may be negative. A poly(gon|line) object's position is determined by the first */ + /* vertex added leading to the first entry to be "0,0" and all other vertices relative to it. */ + node->point.x = (float)atof(x); + node->point.y = (float)atof(y); + vertexSum.x += node->point.x; + vertexSum.y += node->point.y; + if (pointsRoot == NULL) /* If this is the first point to add to the list */ + pointsRoot = node; + else /* If adding to the tail of the list */ + pointsTail->next = node; + pointsTail = node; + pointsLength += 1; + iterator = terminator[0] != '\0' ? terminator + 1 : NULL; + } + + if (pointsRoot != NULL) { /* If a list with at least one node was created from the 'points' attribute */ + /* The first vertex will be duplicated and appended to the end of the list, for drawing purposes, so */ + /* the length of the points list is incremented by one */ + pointsLength += 1; + bool isPolygon = strcmp(hoxmlContext->tag, "polygon") == 0; + if (isPolygon) { /* If the object is a polygon, not polyline */ + /* Polygons will be drawn using raylib's DrawTriangleFan() function in which the first point is */ + /* the centroid. It must also end with the first, non-centroid point. So, for polygons, the list */ + /* will have two more points. */ + pointsLength += 1; + } + /* Allocate the array and assign NULL to every index to be safe */ + Vector2* points = (Vector2*)MemAllocZero(sizeof(Vector2) * pointsLength); + if (isPolygon) { /* If the centroid should be included as a vertex */ + /* Finish calculating the centroid by averaging the sum of the vertices keeping in mind that */ + /* 'pointsLength' is equal to N + 2 */ + points[0].x = vertexSum.x / (pointsLength - 2); + points[0].y = vertexSum.y / (pointsLength - 2); + } + /* Copy the points as Vector2s into the array and free the nodes while we're at it */ + RaytmxPolyPointNode* iteratorNode = pointsRoot; + uint32_t i = isPolygon ? 1 : 0; /* Skip over the first element, the centroid, for polygons only */ + while (iteratorNode != NULL) { + points[i] = iteratorNode->point; + RaytmxPolyPointNode* parent = iteratorNode; + iteratorNode = iteratorNode->next; + i += 1; + MemFree(parent); + } + /* End the list with the first point. Both polygons and polylines use this when drawing. */ + points[pointsLength - 1].x = points[isPolygon ? 1 : 0].x; + points[pointsLength - 1].y = points[isPolygon ? 1 : 0].y; + /* TODO: sort the vertices into counter-clockwise order as DrawTriangleFan() requires it */ + /* Add the points array to the element it applies to */ + raytmxState->object->points = points; + raytmxState->object->pointsLength = pointsLength; + raytmxState->object->drawPoints = (Vector2*)MemAllocZero(sizeof(Vector2) * pointsLength); + } + } /* raytmxState->object != NULL && strcmp(hoxmlContext->attribute, "points") == 0 */ + } /* strcmp(hoxmlContext->tag, "polygon") == 0 || strcmp(hoxmlContext->tag, "polyline") == 0 */ + else if (strcmp(hoxmlContext->tag, "text") == 0) { + if (raytmxState->object != NULL && raytmxState->object->text != NULL) { + if (strcmp(hoxmlContext->attribute, "fontfamily") == 0) { + raytmxState->object->text->fontFamily = + (char*)MemAllocZero((unsigned int)strlen(hoxmlContext->value) + 1); + StringCopy(raytmxState->object->text->fontFamily, hoxmlContext->value); + } else if (strcmp(hoxmlContext->attribute, "pixelsize") == 0) + raytmxState->object->text->pixelSize = atoi(hoxmlContext->value); + else if (strcmp(hoxmlContext->attribute, "wrap") == 0) + raytmxState->object->text->wrap = atoi(hoxmlContext->value) != 0 ? true : false; + else if (strcmp(hoxmlContext->attribute, "color") == 0) + raytmxState->object->text->color = GetColorFromHexString(hoxmlContext->value); + else if (strcmp(hoxmlContext->attribute, "bold") == 0) + raytmxState->object->text->bold = atoi(hoxmlContext->value) != 0 ? true : false; + else if (strcmp(hoxmlContext->attribute, "italic") == 0) + raytmxState->object->text->italic = atoi(hoxmlContext->value) != 0 ? true : false; + else if (strcmp(hoxmlContext->attribute, "underline") == 0) + raytmxState->object->text->underline = atoi(hoxmlContext->value) != 0 ? true : false; + else if (strcmp(hoxmlContext->attribute, "strikeout") == 0) + raytmxState->object->text->strikeOut = atoi(hoxmlContext->value) != 0 ? true : false; + else if (strcmp(hoxmlContext->attribute, "kerning") == 0) + raytmxState->object->text->kerning = atoi(hoxmlContext->value) != 0 ? true : false; + else if (strcmp(hoxmlContext->attribute, "halign") == 0) { + if (strcmp(hoxmlContext->value, "left") == 0) + raytmxState->object->text->halign = HORIZONTAL_ALIGNMENT_LEFT; + else if (strcmp(hoxmlContext->value, "center") == 0) + raytmxState->object->text->halign = HORIZONTAL_ALIGNMENT_CENTER; + else if (strcmp(hoxmlContext->value, "right") == 0) + raytmxState->object->text->halign = HORIZONTAL_ALIGNMENT_RIGHT; + else if (strcmp(hoxmlContext->value, "justify") == 0) + raytmxState->object->text->halign = HORIZONTAL_ALIGNMENT_JUSTIFY; + } else if (strcmp(hoxmlContext->attribute, "valign") == 0) { + if (strcmp(hoxmlContext->value, "top") == 0) + raytmxState->object->text->valign = VERTICAL_ALIGNMENT_TOP; + else if (strcmp(hoxmlContext->value, "center") == 0) + raytmxState->object->text->valign = VERTICAL_ALIGNMENT_CENTER; + else if (strcmp(hoxmlContext->value, "bottom") == 0) + raytmxState->object->text->valign = VERTICAL_ALIGNMENT_BOTTOM; + } + } /* raytmxState->object != NULL && raytmxState->object->text != NULL */ + } /* strcmp(hoxmlContext->tag, "text") == 0 */ + else if (strcmp(hoxmlContext->tag, "imagelayer") == 0) { + if (raytmxState->imageLayer != NULL) { + /* Check for attributes specific to <imagelayer> layers */ + if (strcmp(hoxmlContext->attribute, "repeatx") == 0) + raytmxState->imageLayer->repeatX = atoi(hoxmlContext->value) != 0 ? true : false; + else if (strcmp(hoxmlContext->attribute, "repeaty") == 0) + raytmxState->imageLayer->repeatY = atoi(hoxmlContext->value) != 0 ? true : false; + } + } /* strcmp(hoxmlContext->tag, "imagelayer") == 0 */ + + if (strcmp(hoxmlContext->tag, "layer") == 0 || strcmp(hoxmlContext->tag, "objectgroup") == 0 || + strcmp(hoxmlContext->tag, "imagelayer") == 0 || strcmp(hoxmlContext->tag, "group") == 0) { + if (raytmxState->layer != NULL) { + /* Check for attributes common to all layer types */ + if (strcmp(hoxmlContext->attribute, "id") == 0) + raytmxState->layer->id = atoi(hoxmlContext->value); + else if (strcmp(hoxmlContext->attribute, "name") == 0) { + raytmxState->layer->name = (char*)MemAllocZero((unsigned int)strlen(hoxmlContext->value) + 1); + StringCopy(raytmxState->layer->name, hoxmlContext->value); + } else if (strcmp(hoxmlContext->attribute, "class") == 0) { + raytmxState->layer->classString = (char*)MemAllocZero((unsigned int)strlen(hoxmlContext->value) + 1); + StringCopy(raytmxState->layer->classString, hoxmlContext->value); + } else if (strcmp(hoxmlContext->attribute, "opacity") == 0) + raytmxState->layer->opacity = atof(hoxmlContext->value); + else if (strcmp(hoxmlContext->attribute, "visible") == 0) + raytmxState->layer->visible = atoi(hoxmlContext->value) != 0 ? true : false; + else if (strcmp(hoxmlContext->attribute, "tintcolor") == 0) { + raytmxState->layer->tintColor = GetColorFromHexString(hoxmlContext->value); + raytmxState->layer->hasTintColor = true; + } else if (strcmp(hoxmlContext->attribute, "offsetx") == 0) + raytmxState->layer->offsetX = atoi(hoxmlContext->value); + else if (strcmp(hoxmlContext->attribute, "offsety") == 0) + raytmxState->layer->offsetY = atoi(hoxmlContext->value); + else if (strcmp(hoxmlContext->attribute, "parallaxx") == 0) + raytmxState->layer->parallaxX = atof(hoxmlContext->value); + else if (strcmp(hoxmlContext->attribute, "parallaxy") == 0) + raytmxState->layer->parallaxY = atof(hoxmlContext->value); + } + } /* strcmp(hoxmlContext->tag, "layer") == 0 || strcmp(hoxmlContext->tag, "objectgroup") == 0 || */ + /* strcmp(hoxmlContext->tag, "imagelayer") == 0 || strcmp(hoxmlContext->tag, "group") == 0 */ +} + +void HandleElementEnd(RaytmxState* raytmxState, hoxml_context_t* hoxmlContext) { + if (raytmxState == NULL || hoxmlContext == NULL) + return; + + /* If the element is one of the layer types which share some common attributes that may need default strings */ + if (strcmp(hoxmlContext->tag, "layer") == 0 || strcmp(hoxmlContext->tag, "objectgroup") == 0 || + strcmp(hoxmlContext->tag, "imagelayer") == 0 || strcmp(hoxmlContext->tag, "group") == 0) { + TmxLayer* layer = raytmxState->layer; + if (layer == NULL && raytmxState->groupNode != NULL) + layer = &raytmxState->groupNode->layer; + + if (layer != NULL) { + /* Apply default values for the attribute(s) that aren't covered by a simple memset(x, 0, sizeof(x)) */ + if (layer->name == NULL) { /* If this layer didn't have a 'name' attribute */ + /* The default value for 'name' is "" (an empty string) */ + layer->name = (char*)MemAlloc(1); + layer->name[0] = '\0'; + } + if (layer->classString == NULL) { /* If this layer didn't have a 'class' attribute */ + /* The default value for 'class' is "" (an empty string) */ + layer->classString = (char*)MemAlloc(1); + layer->classString[0] = '\0'; + } + } + } /* strcmp(hoxmlContext->tag, "layer") == 0 || strcmp(hoxmlContext->tag, "objectgroup") == 0 || */ + /* strcmp(hoxmlContext->tag, "imagelayer") == 0 || strcmp(hoxmlContext->tag, "group") == 0 */ + + if (strcmp(hoxmlContext->tag, "properties") == 0) { + if (raytmxState->propertiesRoot == NULL) + return; + /* TMX allows nested properties (e.g. <properties><properties><property/></properties></properties>) but */ + /* that is unsupported. Depth is tracked such that all properties, included nested ones, are appended to one */ + /* array when the outermost element ends. */ + raytmxState->propertiesDepth -= 1; + if (raytmxState->propertiesDepth > 0) /* If the outermost <properties> has not yet ended */ + return; + /* Allocate the array and assign NULL to every index to be safe */ + TmxProperty* properties = (TmxProperty*)MemAllocZero(sizeof(TmxProperty) * raytmxState->propertiesLength); + /* Copy the TmxProperty pointers into the array and free the nodes while we're at it */ + RaytmxPropertyNode* iterator = raytmxState->propertiesRoot; + for (uint32_t i = 0; iterator != NULL; i++) { + properties[i] = iterator->property; + RaytmxPropertyNode* parent = iterator; + iterator = iterator->next; + MemFree(parent); + } + /* Add the properties array to the element it applies to */ + /* A <property>, or rather its parent <properties>, can be within 10+ other elements. The order of the checks */ + /* here is slightly important as the most-nested elements must come first in cases where one may contain */ + /* another (e.g. an <object> within an <objectgroup> layer). */ + if (raytmxState->tilesetTile != NULL && raytmxState->tilesetTile->properties == NULL) { + raytmxState->tilesetTile->properties = properties; + raytmxState->tilesetTile->propertiesLength = raytmxState->propertiesLength; + } + /* else if (raytmxState->wangColor != NULL && raytmxState->wangColor->properties == NULL) { + raytmxState->wangColor->properties = properties; + raytmxState->wangColor->propertiesLength = raytmxState->propertiesLength; + } + else if (raytmxState->wangSet != NULL && raytmxState->wangSet->properties == NULL) { + raytmxState->wangSet->properties = properties; + raytmxState->wangSet->propertiesLength = raytmxState->propertiesLength; + } */ /* TODO: Wang sets. Low priority. */ + else if (raytmxState->tileset != NULL && raytmxState->tileset->properties == NULL) { + raytmxState->tileset->properties = properties; + raytmxState->tileset->propertiesLength = raytmxState->propertiesLength; + } else if (raytmxState->object != NULL && raytmxState->object->properties == NULL) { + raytmxState->object->properties = properties; + raytmxState->object->propertiesLength = raytmxState->propertiesLength; + } else if (raytmxState->layer != NULL && raytmxState->layer->properties == NULL) { + raytmxState->layer->properties = properties; + raytmxState->layer->propertiesLength = raytmxState->propertiesLength; + } else { + raytmxState->mapProperties = properties; + raytmxState->mapPropertiesLength = raytmxState->propertiesLength; + } + /* Clean up the state object */ + raytmxState->propertiesRoot = NULL; + raytmxState->propertiesTail = NULL; + raytmxState->propertiesLength = 0; + } /* strcmp(hoxmlContext->tag, "properties") == 0 */ + else if (strcmp(hoxmlContext->tag, "property") == 0) { + if (raytmxState->property != NULL) { + /* Apply default values for the attribute(s) that aren't covered by a simple memset(x, 0, sizeof(x)) */ + /* Properties are cast and assigned to type-specific variables at the end of the element due to the order */ + /* of the property's attributes not being guaranteed, so we wait until we have all the information */ + switch (raytmxState->property->type) { + case PROPERTY_TYPE_STRING: + default: /* The default type of a property is 'string' */ + if (raytmxState->property->stringValue == NULL) { + if (hoxmlContext->content != NULL) { /* If Tiled opted to put the value in the element's content */ + /* From the documentation: "When a string property contains newlines, the current version of */ + /* Tiled will write out the value as characters contained inside the property element rather */ + /* than as the value attribute." */ + raytmxState->property->stringValue = + (char*)MemAlloc((unsigned int)strlen(hoxmlContext->content) + 1); + StringCopy(raytmxState->property->stringValue, hoxmlContext->content); + } else { /* If the string's value was neither provided as an attribute nor content */ + /* The default value for 'string' is an empty string */ + raytmxState->property->stringValue = (char*)MemAlloc(1); + raytmxState->property->stringValue[0] = '\0'; + } + } break; + case PROPERTY_TYPE_INT: + case PROPERTY_TYPE_OBJECT: + /* The default value for 'int' and 'object' is 0 */ + raytmxState->property->intValue = atoi(raytmxState->property->stringValue); /* Defaults to 0 */ + break; + case PROPERTY_TYPE_FLOAT: + /* The default value for 'float' is 0 */ + raytmxState->property->floatValue = (float)atof(raytmxState->property->stringValue); /* Defaults to 0 */ + break; + case PROPERTY_TYPE_BOOL: + /* The default value for 'bool' is false */ + if (raytmxState->property->stringValue == NULL || + strcmp(raytmxState->property->stringValue, "true") != 0) + raytmxState->property->boolValue = false; + else + raytmxState->property->boolValue = true; + break; + case PROPERTY_TYPE_COLOR: + /* The default value for 'color' is the color #00000000 */ + if (raytmxState->property->stringValue == NULL) + raytmxState->property->colorValue = BLANK; /* #defined by raylib as { 0, 0, 0, 0 } */ + else + raytmxState->property->colorValue = GetColorFromHexString(raytmxState->property->stringValue); + break; + case PROPERTY_TYPE_FILE: + /* The default value for 'file' is "." */ + if (raytmxState->property->stringValue == NULL) { + raytmxState->property->stringValue = (char*)MemAlloc(2); + raytmxState->property->stringValue[0] = '.'; + raytmxState->property->stringValue[1] = '\0'; + } break; + } /* switch (raytmxState->property->type) */ + + /* If the type was neither 'string' nor 'file' and 'stringValue' is set */ + if (raytmxState->property->type != PROPERTY_TYPE_STRING && + raytmxState->property->type != PROPERTY_TYPE_FILE && raytmxState->property->stringValue != NULL) { + /* Properties of types other than 'string' and 'file' are placed in 'stringValue' temporarily. Now */ + /* that they have been cast and assigned appropriately, 'stringValue' can be freed. */ + MemFree(raytmxState->property->stringValue); + raytmxState->property->stringValue = NULL; + } + } + raytmxState->property = NULL; + } /* strcmp(hoxmlContext->tag, "property") == 0 */ + else if (strcmp(hoxmlContext->tag, "tileset") == 0) { + if (raytmxState->tileset != NULL) { + /* Apply default values for the attribute(s) that aren't covered by a simple memset(x, 0, sizeof(x)) */ + if (raytmxState->tileset->name == NULL) { /* If this <tileset> didn't have a 'name' attribute */ + /* The default value for 'name' is "" (an empty string) */ + raytmxState->tileset->name = (char*)MemAlloc(1); + raytmxState->tileset->name[0] = '\0'; + } + if (raytmxState->tileset->classString == NULL) { /* If this <tileset> didn't have a 'class' attribute */ + /* The default value for 'class' is "" (an empty string) */ + raytmxState->tileset->classString = (char*)MemAlloc(1); + raytmxState->tileset->classString[0] = '\0'; + } + if (raytmxState->tileset->objectAlignment == OBJECT_ALIGNMENT_UNSPECIFIED) { + /* There are default object alignments for orthogonal and isometric modes */ + if (raytmxState->mapOrientation == ORIENTATION_ORTHOGONAL) + raytmxState->tileset->objectAlignment = OBJECT_ALIGNMENT_BOTTOM_LEFT; + else if (raytmxState->mapOrientation == ORIENTATION_ISOMETRIC) + raytmxState->tileset->objectAlignment = OBJECT_ALIGNMENT_BOTTOM; + } + + if (raytmxState->tilesetTilesRoot != NULL) { + /* Allocate the array and zeroize every index as initialization */ + TmxTilesetTile* tiles = (TmxTilesetTile*)MemAllocZero(sizeof(TmxTilesetTile) * + raytmxState->tilesetTilesLength); + /* Copy the TmxTilesetTile pointers into the array and free the nodes while we're at it */ + RaytmxTilesetTileNode* iterator = raytmxState->tilesetTilesRoot; + for (uint32_t i = 0; i < raytmxState->tilesetTilesLength && iterator != NULL; i++) { + tiles[i] = iterator->tile; + RaytmxTilesetTileNode* parent = iterator; + iterator = iterator->next; + MemFree(parent); + } + /* Add the tiles array to the tileset */ + raytmxState->tileset->tiles = tiles; + raytmxState->tileset->tilesLength = raytmxState->tilesetTilesLength; + /* Clean up the state object */ + raytmxState->tilesetTilesRoot = NULL; + raytmxState->tilesetTilesTail = NULL; + raytmxState->tilesetTilesLength = 0; + } + } + raytmxState->tileset = NULL; + } /* strcmp(hoxmlContext->tag, "tileset") == 0 */ + else if (strcmp(hoxmlContext->tag, "image") == 0) + raytmxState->image = NULL; + else if (strcmp(hoxmlContext->tag, "animation") == 0) { + if (raytmxState->tilesetTile != NULL && raytmxState->tilesetTile->hasAnimation) { + if (raytmxState->animationFramesRoot == NULL) + return; + /* Allocate the array and zeroize every index as initialization */ + TmxAnimationFrame* frames = (TmxAnimationFrame*)MemAllocZero(sizeof(TmxAnimationFrame) * + raytmxState->animationFramesLength); + /* Copy the TmxAnimationFrame pointers into the array and free the nodes while we're at it */ + RaytmxAnimationFrameNode* iterator = raytmxState->animationFramesRoot; + for (uint32_t i = 0; iterator != NULL; i++) { + frames[i] = iterator->frame; + RaytmxAnimationFrameNode* parent = iterator; + iterator = iterator->next; + MemFree(parent); + } + /* Add the frames array to the tile's animation */ + raytmxState->tilesetTile->animation.frames = frames; + raytmxState->tilesetTile->animation.framesLength = raytmxState->animationFramesLength; + /* Clean up the state object */ + raytmxState->animationFramesRoot = NULL; + raytmxState->animationFramesTail = NULL; + raytmxState->animationFramesLength = 0; + } + } /* strcmp(hoxmlContext->tag, "animation") == 0 */ + else if (strcmp(hoxmlContext->tag, "frame") == 0) + raytmxState->animationFrame = NULL; + else if (strcmp(hoxmlContext->tag, "layer") == 0) { + if (raytmxState->tileLayer != NULL) { + /* If there were 1+ <tile>s within this <layer> but this <layer> already has tiles (from a <data>?) */ + if (raytmxState->layerTilesRoot != NULL && raytmxState->tileLayer->tiles != NULL) { + TraceLog(LOG_WARNING, "RAYTMX: layer \"%s\" has more than one source of tile data - the latter tiles " + "for this layer will be dropped", raytmxState->layer->name); + /* Free the nodes and tiles therein */ + RaytmxTileLayerTileNode* iterator = raytmxState->layerTilesRoot; + while (iterator != NULL) { + RaytmxTileLayerTileNode* parent = iterator; + iterator = iterator->next; + MemFree(parent); + } + } else { + /* Allocate the array and zeroize every index as initialization */ + uint32_t* tiles = (uint32_t*)MemAllocZero(sizeof(uint32_t) * raytmxState->layerTilesLength); + /* Copy the GID into the array and free the nodes while we're at it */ + RaytmxTileLayerTileNode* iterator = raytmxState->layerTilesRoot; + for (uint32_t i = 0; iterator != NULL; i++) { + tiles[i] = iterator->gid; + RaytmxTileLayerTileNode* parent = iterator; + iterator = iterator->next; + MemFree(parent); + } + /* Add the tiles array to the tile layer */ + raytmxState->tileLayer->tiles = tiles; + raytmxState->tileLayer->tilesLength = raytmxState->layerTilesLength; + } + /* Clean up the state object */ + raytmxState->layerTilesRoot = NULL; + raytmxState->layerTilesTail = NULL; + raytmxState->layerTilesLength = 0; + } + raytmxState->tileLayer = NULL; + raytmxState->layer = NULL; + } /* strcmp(hoxmlContext->tag, "layer") == 0 */ + else if (strcmp(hoxmlContext->tag, "tile") == 0) { + if (raytmxState->tilesetTile != NULL) { + /* Apply default values for the attribute(s) that aren't covered by a simple memset(x, 0, sizeof(x)) */ + if (raytmxState->tilesetTile->classString == NULL) { /* If this <tile> didn't have a 'class' attribute */ + /* The default value for 'class' is "" (an empty string) */ + raytmxState->tilesetTile->classString = (char*)MemAlloc(1); + raytmxState->tilesetTile->classString[0] = '\0'; + } + if (raytmxState->tilesetTile->hasImage) { + /* The 'width' and 'height' attributes default to the tile's image's width and height, respectively */ + if (raytmxState->tilesetTile->width == 0) + raytmxState->tilesetTile->width = raytmxState->tilesetTile->image.width; + if (raytmxState->tilesetTile->height == 0) + raytmxState->tilesetTile->height = raytmxState->tilesetTile->image.height; + } + raytmxState->tilesetTile = NULL; + } + } /* strcmp(hoxmlContext->tag, "tile") == 0 */ + else if (strcmp(hoxmlContext->tag, "data") == 0) { + if (raytmxState->image != NULL) { + /* TODO (?): The TMX map format documentation says an <image> can contain a <data> element but doesn't */ + /* provide any more information than that. Tiled doesn't seem to have a feature for this either. */ + } else if (raytmxState->tileLayer != NULL && raytmxState->tileLayer->tiles != NULL) { + TraceLog(LOG_WARNING, "RAYTMX: layer \"%s\" has more than one source of tile data - the latter tiles for " + "this layer will be dropped", raytmxState->layer->name); + } else if (raytmxState->tileLayer != NULL && raytmxState->tileLayer->encoding != NULL) { + RaytmxTileLayerTileNode* tiles = NULL; + if (strcmp(raytmxState->tileLayer->encoding, "base64") == 0) { + /* The layer's data is a series of unsigned, 32-bit integers encoded as a Base64 string. But, XML */ + /* considers everything between <data> and </data> to be content meaning there is probably some */ + /* whitespace on both ends of the content we need to ignore. So, find the actual start and stop: */ + char *encodedStart = hoxmlContext->content, *encodedEnd; + while (isspace(*encodedStart)) + encodedStart++; + encodedEnd = encodedStart + strlen(encodedStart) - 1; + while (encodedEnd > encodedStart && isspace(*encodedEnd)) + encodedEnd--; + + /* With the string of encoded Base64 data trimmed, decode it */ + int decodedLength; + unsigned char* decoded = DecodeDataBase64((const unsigned char*)encodedStart, &decodedLength); + if (decoded != NULL) { + if (raytmxState->tileLayer->compression == NULL) { /* If the Base64-encoded data is uncompressed */ + /* Iterate through N bytes ('decodedLength') with every four bytes being a single GID */ + /* resulting in N / 4 tiles */ + uint32_t* iterator = (uint32_t*)decoded; + for (int i = 0; i < decodedLength / 4; i++) { + AddTileLayerTile(raytmxState, *iterator); + iterator += 1; /* Point to the next unsigned, 32-bit integer in the decoded data */ + } + } else { /* If the Base-64encoded data is also compressed */ + if (strcmp(raytmxState->tileLayer->compression, "gzip") == 0 || + strcmp(raytmxState->tileLayer->compression, "zlib") == 0) { + unsigned char* postHeaderDecoded = NULL; + if (strcmp(raytmxState->tileLayer->compression, "gzip") == 0) { + /* The first two bytes of a GZIP header are expected to be a magic number, 0x1F8B, */ + /* identifying the format and the third is expected to indicate the compression */ + /* method where 0x08 is DEFLATE. */ + /* If these values are found, decompression can continue */ + if (decoded[0] == 0x1F && decoded[1] == 0x8B && decoded[2] == 0x08) { + /* Skip past the GZIP header. The header is typically ten bytes. The bytes not */ + /* checked are unimportant things like a timestamp and OS ID. Additional optional */ + /* headers are possible but not used by Tiled so they are assumed to be missing. */ + postHeaderDecoded = decoded + 10; + } else { /* If the GZIP header doesn't match a decompressable one */ + TraceLog(LOG_ERROR, "RAYTMX: Layer \"%s\" uses GZIP compression but the stream's " + "header doesn't indicate DEFLATE compression", raytmxState->layer->name); + } + } else /* if (strcmp(raytmxState->tileLayer->compression, "zlib") == 0) */ { + /* The first byte of a ZLIB header is expected to be 0x78 where the 8 indicates the */ + /* DEFLATE compression method and the 7 is "compression info" that indicates a 32K */ + /* LZ77 window size and, in practice, cannot be anything else. */ + /* If these values are found, decompression can continue */ + if (decoded[0] == 0x78) { + /* Skip past the ZLIB header. The header is two bytes. */ + postHeaderDecoded = decoded + 2; + } else { /* If the ZLIB header doesn't match a decompressable one */ + TraceLog(LOG_ERROR, "RAYTMX: Layer \"%s\" uses ZLIB compression but the stream's " + "header doesn't indicate DEFLATE compression", raytmxState->layer->name); + } + } + + if (postHeaderDecoded != NULL) { + /* "zlib" and "gzip" both use the DEFLATE algorithm and raylib provides a */ + /* decompression function when it's built with SUPPORT_COMPRESSION_API (default) */ + int decompressedLength; + unsigned char* decompressed = DecompressData(postHeaderDecoded, decodedLength, + &decompressedLength); + if (decompressed != NULL && decompressedLength > 0) { + uint32_t* iterator = (uint32_t*)decompressed; + for (int i = 0; i < decompressedLength / 4; i++) { + AddTileLayerTile(raytmxState, *iterator); + iterator += 1; /* Point to the next unsigned integer in the decompressed data */ + } + MemFree(decompressed); /* Free the memory allocated by DecompressData() */ + } else { /* raylib wasn't built with compression or allocation failed */ + TraceLog(LOG_ERROR, "RAYTMX: Layer \"%s\" compressed with \"%s\" cannot be parsed " + "because DEFLATE decompression failed - either raylib was not built with " + "SUPPORT_COMPRESSION_API or memory allocation failed", raytmxState->layer->name, + raytmxState->tileLayer->compression); + } + } + } else { + TraceLog(LOG_ERROR, "RAYTMX: Layer \"%s\" cannot be parsed because the compression method " + "\"%s\" is unsupported", raytmxState->layer->name, raytmxState->tileLayer->compression); + } + } + MemFree(decoded); /* Free the memory allocated by DecodeDataBase64() */ + } else { + TraceLog(LOG_ERROR, "RAYTMX: Unable to decode Base64 data for layer \"%s\"", + raytmxState->layer->name); + } + } /* strcmp(raytmxState->tileLayer->encoding, "base64") == 0 */ + else if (strcmp(raytmxState->tileLayer->encoding, "csv") == 0) { + /* The Comma-Separated Value (CSV) list herein is a series of Global IDs (GIDs) of tiles in the form */ + /* "31,32,33" where 31, 32, and 33 are GIDs */ + char valueAsString[16]; /* Needs to fit all digits of a single value - should be more than enough */ + char* iterator = hoxmlContext->content; + while (iterator != NULL && *iterator != '\0') { /* While not pointing to the end of the string */ + memset(valueAsString, '\0', 16); /* Reset the value-as-a-string buffer with all zeroes */ + /* Copy each character into the buffer until either a comma or the terminator is reached */ + for (int i = 0; *iterator != ',' && *iterator != '\0'; i++) { + memcpy(valueAsString + i, iterator, 1); + iterator++; + } + if (*iterator == ',') /* If iteration was paused by a comma */ + iterator++; /* Advance one character so the comma isn't parsed with the value next iteration */ + AddTileLayerTile(raytmxState, atoi(valueAsString)); /* Read the value as an integer GID */ + } + } /* strcmp(raytmxState->tileLayer->encoding, "csv") == 0 */ + + if (tiles != NULL) { /* If there was no error in parsing the data and there's a linked list of tiles */ + /* Allocate the array and assign 0 to every index to be safe */ + uint32_t* tiles = (uint32_t*)MemAlloc(sizeof(uint32_t) * raytmxState->layerTilesLength); + memset(tiles, 0, sizeof(uint32_t) * raytmxState->layerTilesLength); + /* Copy the GIDs into the array and free the nodes while we're at it */ + RaytmxTileLayerTileNode* layerTilesIterator = raytmxState->layerTilesRoot; + for (uint32_t i = 0; i < raytmxState->layerTilesLength && layerTilesIterator != NULL; i++) { + tiles[i] = layerTilesIterator->gid; + RaytmxTileLayerTileNode* layerTilesTemp = layerTilesIterator; + layerTilesIterator = layerTilesIterator->next; + MemFree(layerTilesTemp); + } + /* Add the tiles array to the element it applies to */ + raytmxState->tileLayer->tiles = tiles; + raytmxState->tileLayer->tilesLength = raytmxState->layerTilesLength; + /* Clean up the state object */ + raytmxState->layerTilesRoot = NULL; + raytmxState->layerTilesTail = NULL; + raytmxState->layerTilesLength = 0; + } + } /* raytmxState->tileLayer != NULL && raytmxState->tileLayer->encoding != NULL */ + } /* strcmp(hoxmlContext->tag, "data") == 0 */ + else if (strcmp(hoxmlContext->tag, "objectgroup") == 0) { + if (raytmxState->objectGroup != NULL) { + if (raytmxState->objectsRoot == NULL) + return; + /* Allocate the arrays and zeroize every index as initialization */ + TmxObject* objects = (TmxObject*)MemAllocZero(sizeof(TmxObject) * raytmxState->objectsLength); + uint32_t* ySortedObjects = (uint32_t*)MemAllocZero(sizeof(uint32_t) * raytmxState->objectsLength); + /* Create a contiguous array of TmxObjects, create a sorted linked list of indexes within that array of */ + /* TmxObjects (sorted by ascending y-coordinate), and free the object linked list */ + RaytmxObjectNode *objectsIterator = raytmxState->objectsRoot, *objectsTemp; + RaytmxObjectSortingNode *sortingRoot = NULL, *sortingIterator, *sortingTemp, *newSortingNode; + for (uint32_t i = 0; objectsIterator != NULL; i++) { + objects[i] = objectsIterator->object; + /* Add a new node into the sorted list */ + newSortingNode = (RaytmxObjectSortingNode*)MemAllocZero(sizeof(RaytmxObjectSortingNode)); + newSortingNode->y = objects[i].y; + newSortingNode->index = i; + if (sortingRoot == NULL) /* If this is the first node */ + sortingRoot = newSortingNode; + else if (sortingRoot->y >= newSortingNode->y) { /* If replacing the root node */ + newSortingNode->next = sortingRoot; + sortingRoot = newSortingNode; + } else { /* If inserting at some location after the root */ + sortingIterator = sortingRoot; + while (sortingIterator->next != NULL && newSortingNode->y > sortingIterator->next->y) + sortingIterator = sortingIterator->next; + newSortingNode->next = sortingIterator->next; + sortingIterator->next = newSortingNode; + } + /* Free the object node */ + objectsTemp = objectsIterator; + objectsIterator = objectsIterator->next; + MemFree(objectsTemp); + } + /* Create a contiguous array from the sorted linked list such that index 0 of this array points to the */ + /* TmxObject (via its index in 'objects') with the lowest (visually, highest) y-coordinate */ + sortingIterator = sortingRoot; + for (uint32_t i = 0; sortingIterator != NULL; i++) { + ySortedObjects[i] = sortingIterator->index; + sortingTemp = sortingIterator; + sortingIterator = sortingIterator->next; + MemFree(sortingTemp); + } + /* Add the objects and ySortedObjects array to the object layer */ + raytmxState->objectGroup->objects = objects; + raytmxState->objectGroup->objectsLength = raytmxState->objectsLength; + raytmxState->objectGroup->ySortedObjects = ySortedObjects; + /* Clean up the state object */ + raytmxState->objectsRoot = NULL; + raytmxState->objectsTail = NULL; + raytmxState->objectsLength = 0; + } + raytmxState->objectGroup = NULL; + raytmxState->layer = NULL; + } else if (strcmp(hoxmlContext->tag, "object") == 0) { + if (raytmxState->object != NULL) { + /* Apply default values for the attribute(s) that aren't covered by a simple memset(x, 0, sizeof(x)) */ + if (raytmxState->object->name == NULL) { /* If this <object> didn't have a 'name' attribute */ + /* The default value for 'name' is "" (an empty string) */ + raytmxState->object->name = (char*)MemAlloc(1); + raytmxState->object->name[0] = '\0'; + } + if (raytmxState->object->typeString == NULL) { /* If this <object> didn't have a 'type' attribute */ + /* The default value for 'type' is "" (an empty string) */ + raytmxState->object->typeString = (char*)MemAlloc(1); + raytmxState->object->typeString[0] = '\0'; + } + + if (raytmxState->object->templateString != NULL) { + /* During editing, objects can be created from templates with pre-defined values and/or <properties>, */ + /* a concept similar to copying and pasting or instancing. These templates are saved in external TX */ + /* files that the 'template' attribute points to. Try to load it. */ + RaytmxCachedTemplateNode* cachedTemplate = LoadCachedTemplate(raytmxState, + raytmxState->object->templateString); + if (cachedTemplate != NULL) { + RaytmxObjectTemplate objectTemplate = cachedTemplate->objectTemplate; + /* The template's <object> will contain some subset of values and/or <properties> that apply to */ + /* the instanced <object> referencing it. For example, if the template's <object> has a 'name' */ + /* attribute with the value "cactus" then the instanced <object> will also have a 'name' */ + /* attribute. "cactus" is a default value that will be used if the instanced <object> does not */ + /* define one of its own. The template's <object> needs to be checked for non-default values and */ + /* <properties> and they need to be applied to the instanced <object> where none exist. */ + if (objectTemplate.object.name != NULL && raytmxState->object->name == NULL) { + raytmxState->object->name = + (char*)MemAllocZero((unsigned int)strlen(objectTemplate.object.name) + 1); + StringCopy(raytmxState->object->name, objectTemplate.object.name); + } + if (objectTemplate.object.typeString != NULL && raytmxState->object->typeString != NULL) { + raytmxState->object->typeString = + (char*)MemAllocZero((unsigned int)strlen(objectTemplate.object.typeString) + 1); + StringCopy(raytmxState->object->typeString, objectTemplate.object.typeString); + } + if (objectTemplate.object.x != 0.0 && raytmxState->object->x == 0.0) + raytmxState->object->x = objectTemplate.object.x; + if (objectTemplate.object.y != 0.0 && raytmxState->object->y == 0.0) + raytmxState->object->y = objectTemplate.object.y; + if (objectTemplate.object.width != 0.0 && raytmxState->object->width == 0.0) + raytmxState->object->width = objectTemplate.object.width; + if (objectTemplate.object.height != 0.0 && raytmxState->object->height == 0.0) + raytmxState->object->height = objectTemplate.object.height; + if (objectTemplate.object.rotation != 0.0 && raytmxState->object->rotation == 0.0) + raytmxState->object->rotation = objectTemplate.object.rotation; + if (objectTemplate.object.gid != 0 && raytmxState->object->gid == 0) + raytmxState->object->gid = objectTemplate.object.gid; + if (!objectTemplate.object.visible && raytmxState->object->visible) + raytmxState->object->visible = false; + /* TMX documentation doesn't specify some particulars of templates, namely if it is possible for */ + /* a template to be type-specific. In other words, can a template be a polygon? If so, are points */ + /* merged or replaced? Based on Tiled's behavior, templates do not have type-specific values or, */ + /* at least, they are ignored if they exist. However, <properties> are definitely merged. */ + /* If the template's <object> has <properties> */ + if (objectTemplate.object.properties != NULL) { + /* There are two cases here: the instanced <object> already has properties, or it doesn't */ + if (raytmxState->object->properties == NULL) { /* If the instance doesn't have <properties> */ + /* This is the easy case. Just copy the pointer to the existing array. */ + raytmxState->object->properties = objectTemplate.object.properties; + raytmxState->object->propertiesLength = objectTemplate.object.propertiesLength; + } else { + /* The two <properties> need to be merged keeping in mind that they may, or probably, */ + /* have overlapping entries. (When both have properties of the same name, the instanced */ + /* <object>'s takes priority.) Create a linked list from which to create this new, merged */ + /* properties array. */ + RaytmxPropertyNode *propertiesRoot = NULL, *propertiesTail = NULL, *node; + uint32_t propertiesLength = 0; + /* Add the properties from the instanced <object> */ + for (uint32_t i = 0; i < raytmxState->object->propertiesLength; i++) { + node = (RaytmxPropertyNode*)MemAllocZero(sizeof(RaytmxPropertyNode)); + node->property = raytmxState->object->properties[i]; + if (propertiesRoot == NULL) + propertiesRoot = node; + else + propertiesTail->next = node; + propertiesTail = node; + propertiesLength += 1; + } + /* Add the properties from the template's <object> if they do not already exist */ + for (uint32_t i = 0; i < objectTemplate.object.propertiesLength; i++) { + RaytmxPropertyNode* propertiesIterator = propertiesRoot; + bool isNew = true; + while (propertiesIterator != NULL) { + if (strcmp(objectTemplate.object.properties[i].name, + propertiesIterator->property.name) == 0) { + isNew = false; + break; + } + propertiesIterator = propertiesIterator->next; + } + if (isNew) { + node = (RaytmxPropertyNode*)MemAllocZero(sizeof(RaytmxPropertyNode)); + node->property = objectTemplate.object.properties[i]; + if (propertiesRoot == NULL) + propertiesRoot = node; + else + propertiesTail->next = node; + propertiesTail = node; + propertiesLength += 1; + } + } + /* Free the separate array that was previously allocated */ + MemFree(raytmxState->object->properties); + /* Allocate a new array to be populated with the merged properties */ + raytmxState->object->properties = + (TmxProperty*)MemAllocZero(sizeof(TmxProperty) * propertiesLength); + raytmxState->object->propertiesLength = propertiesLength; + /* Copy the TmxProperty entires into the array and free the nodes while we're at it */ + RaytmxPropertyNode* propertiesIterator = propertiesRoot; + for (uint32_t i = 0; propertiesIterator != NULL; i++) { + raytmxState->object->properties[i] = propertiesIterator->property; + RaytmxPropertyNode* propertiesTemp = propertiesIterator; + propertiesIterator = propertiesIterator->next; + MemFree(propertiesTemp); + } + } + } + } else + TraceLog(LOG_WARNING, "RAYTMX: Unable to apply template to object ID %u", raytmxState->object->id); + } + + /* Calculate the Axis-Aligned Bounding Box (AABB) of the object. This can vary by object type. Due to the */ + /* possibility of 'width' and 'height' being derived from a template, these must be calculated after a */ + /* template is applied to the object. */ + switch (raytmxState->object->type) { + case OBJECT_TYPE_RECTANGLE: + case OBJECT_TYPE_ELLIPSE: + case OBJECT_TYPE_TEXT: + raytmxState->object->aabb.x = (float)raytmxState->object->x; + raytmxState->object->aabb.y = (float)raytmxState->object->y; + raytmxState->object->aabb.width = (float)raytmxState->object->width; + raytmxState->object->aabb.height = (float)raytmxState->object->height; + break; + case OBJECT_TYPE_POINT: + { + raytmxState->object->aabb.x = (float)raytmxState->object->x; + raytmxState->object->aabb.y = (float)raytmxState->object->y; + raytmxState->object->aabb.width = 0.0f; + raytmxState->object->aabb.height = 0.0f; + } + break; + case OBJECT_TYPE_POLYGON: + case OBJECT_TYPE_POLYLINE: + { + float minX = INFINITY, maxX = -INFINITY, minY = INFINITY, maxY = -INFINITY; + for (uint32_t i = 1; i < raytmxState->object->pointsLength; i++) { + Vector2 point = raytmxState->object->points[i]; + if (point.x < minX) + minX = point.x; + if (point.x > maxX) + maxX = point.x; + if (point.y < minY) + minY = point.y; + if (point.y > maxY) + maxY = point.y; + } + /* Note: Poly(gon|line) objects' vertices are stored with relative positions whereas AABBs use */ + /* absolute values. The object's X and Y values must be added. */ + raytmxState->object->aabb.x = minX + (float)raytmxState->object->x; + raytmxState->object->aabb.y = minY + (float)raytmxState->object->y; + raytmxState->object->aabb.width = maxX - minX; + raytmxState->object->aabb.height = maxY - minY; + } + break; + case OBJECT_TYPE_TILE: + /* The tile object type can have varying sizes, depending on the tile. While most will have the width */ + /* and height defined in the top-level <map>, a "collection of images" tileset will have tiles with */ + /* arbitrary dimensions. For that reason, an AABB cannot be calculated. */ + break; + } + } + raytmxState->object = NULL; + } /* strcmp(hoxmlContext->tag, "object") == 0 */ + else if (strcmp(hoxmlContext->tag, "text") == 0) { + /* Apply default values for the attribute(s) that aren't covered by a simple memset(x, 0, sizeof(x)) */ + if (raytmxState->object != NULL && raytmxState->object->text != NULL) { + TmxObject* object = raytmxState->object; + TmxText* objectText = object->text; + if (hoxmlContext->content != NULL) { /* If the element had content e.g. <text>Content here</text> */ + objectText->content = (char*)MemAllocZero((unsigned int)strlen(hoxmlContext->content) + 1); + StringCopy(objectText->content, hoxmlContext->content); + } + + if (objectText->fontFamily == NULL) { /* If this <text> didn't have a 'fontfamily' attribute */ + /* The default value for 'fontfamily' is "sans-serif" */ + objectText->fontFamily = (char*)MemAllocZero((unsigned int)strlen("sans-serif") + 1); + StringCopy(objectText->fontFamily, "sans-serif"); + } + + if (objectText->content != NULL) { /* If there's text to be drawn */ + RaytmxTextLineNode *linesRoot = NULL, *linesTail = NULL; + uint32_t linesLength = 0; + + /* There's some aligning and allocating to be done in order to draw the text. This isn't something */ + /* that should be done per-draw and, given the text is static, can be done ahead of time. */ + Font font = GetFontDefault(); + float spacing = 1.0f * (objectText->kerning ? 1.0f : 0.0f); + + unsigned int bufferLength = (unsigned int)strlen(objectText->content) + 1; + /* This buffer will hold hold subsets of the content while iterating through it. The string in this */ + /* buffer may exceed the bounds. */ + char* testingBuffer = (char*)MemAllocZero(bufferLength); + /* This one will hold the last known good string whose graphical text would fit within the bounds */ + char* validBuffer = (char*)MemAllocZero(bufferLength); + /* This one will hold space-delimited subsets of the above */ + char* delimtedBuffer = (char*)MemAllocZero(bufferLength); + bool isDelimited = false; + + char *start = objectText->content, *end = start, *validEnd = start, *delimitedEnd = start; + /* While the 'end' iterator hasn't reached the end of the content AND further lines will fit within */ + /* the Y bounds of the object */ + while (*end != '\0' && + object->y + (objectText->pixelSize * (linesLength + 1)) <= object->y + object->height) { + end++; + int length = (int)(end - start); + if (length <= 0) /* Quick error check for a case that is hopefully impossible in practice */ + continue; + + StringCopyN(testingBuffer, start, length); + /* Remove any trailing whitespace by iterating backwards and adding a terminator in place of any */ + /* whitespace character */ + for (char* i = testingBuffer + length - 1; i > testingBuffer && isspace(*i); i--) + *i = '\0'; + + /* Measure the dimensions of the now-widest text */ + Vector2 textSize = MeasureTextEx(font, testingBuffer, (float)objectText->pixelSize, spacing); + /* If this text still fits within the width of the object (the bounds) */ + if (textSize.x <= object->width) { + /* This string is still valid. Remember the string and where it ends. Iteration may return to */ + /* last known good position. */ + validEnd = end; + StringCopy(validBuffer, testingBuffer); + /* Note: Because the testing buffer is stripped of trailing whitespace, so too is this one */ + + /* If the end of the current string is whitespace but the last character wasn't */ + if (isspace(*end) && !isspace(*(end - 1))) { + /* Taking "Hello, from TMX!" as an example, this would be triggered by the first space */ + /* with "Hello," being the string to remember. In the event that the would-be text is too */ + /* wide, meaning "Hello, from" is too long to fit width-wise, then this delimted "Hello," */ + /* will be the line and iteration will return to "from." */ + isDelimited = true; + delimitedEnd = end; + StringCopy(delimtedBuffer, validBuffer); + } + } + + /* If it's time to create a visual line of text from the string either because: 1) the string has */ + /* become too wide to fit in the bounds of the object, or 2) the end of the content was reached */ + if (textSize.x > object->width || *end == '\0') { + char* sourceBuffer; + if (*end == '\0' || !isDelimited) { + /* The valid buffer is used in cases where the would-be text is simply the remainder of */ + /* the content or the line does not have a clear separator (whitespace), like an extra */ + /* long word that continues to the next line */ + sourceBuffer = validBuffer; + start = validEnd; + } else { + /* The delimited buffer is used in cases where one or more words were identified using */ + /* space between them as a delimiter */ + sourceBuffer = delimtedBuffer; + start = delimitedEnd; + /* Skip over any whitespace at the delimiter */ + while (isspace(*start) && *start != '\0') + start++; + } + + /* The 'start' pointer is pointing to the first character of the next line. Point 'end' */ + /* to the same place and continue from there so nothing is skipped. Note: 'end' will be */ + /* incremented at the start of the next loop. */ + end = start; + + TmxTextLine line; + line.content = (char*)MemAllocZero((unsigned int)strlen(sourceBuffer) + 1); + StringCopy(line.content, sourceBuffer); + line.font = font; + line.spacing = spacing; + /* Note: The number of lines is not yet known but needs to be for Y positioning */ + + RaytmxTextLineNode* node = (RaytmxTextLineNode*)MemAllocZero(sizeof(RaytmxTextLineNode)); + node->line = line; + if (linesRoot == NULL) + linesRoot = node; + else + linesTail->next = node; + linesTail = node; + linesLength += 1; + + /* Reset variables */ + memset(testingBuffer, '\0', bufferLength); + memset(validBuffer, '\0', bufferLength); + memset(delimtedBuffer, '\0', bufferLength); + isDelimited = false; + + if (!objectText->wrap) { /* If word wrapping is disabled */ + /* It's unclear why this would be done but, having hit the end of what can be displayed */ + /* on a single line, no more text can be appended */ + break; + } + } /* textSize.x > object->width || *end == '\0' */ + } /* *end != '\0' && */ + /* object->y + (objectText->pixelSize * (linesLength + 1)) <= object->y + object->height */ + + MemFree(testingBuffer); + MemFree(validBuffer); + MemFree(delimtedBuffer); + + if (linesRoot != NULL) { + /* Allocate the array and zero out every value as initialization */ + TmxTextLine* lines = (TmxTextLine*)MemAllocZero(sizeof(TmxTextLine) * linesLength); + /* Copy the TmxTextLines into the array and free the nodes while we're at it */ + RaytmxTextLineNode* iterator = linesRoot; + for (uint32_t i = 0; i < linesLength && iterator != NULL; i++) { + lines[i] = iterator->line; + Vector2 textSize = MeasureTextEx(font, lines[i].content, (float)objectText->pixelSize, + lines[i].spacing); + + /* Horizontal alignment */ + if (objectText->halign == HORIZONTAL_ALIGNMENT_RIGHT) + lines[i].position.x = (float)(object->x + object->width) - textSize.x; + else if (objectText->halign == HORIZONTAL_ALIGNMENT_CENTER) + lines[i].position.x = (float)(object->x + (object->width / 2.0)) - (textSize.x / 2.0f); + else if (objectText->halign == HORIZONTAL_ALIGNMENT_JUSTIFY) { + /* Horizontally justified text extends from the left bound to the right bound. Typically, */ + /* this is done by adding space betweens words or, where there is only one word, adding */ + /* space between letters. All additional space is distributed evenly. However, the method */ + /* here is a hybrid: because control over spacing between words is coarse, spacing */ + /* between letters is added on top of it. */ + lines[i].position.x = (float)object->x; /* Place the text on the left bound */ + /* Count the number of spaces between words */ + uint32_t numSpaces = 0; + for (uint32_t j = 0; lines[i].content[j] != '\0'; j++) { + if (isspace(lines[i].content[j])) + numSpaces++; + } + size_t length = strlen(lines[i].content); + if (numSpaces > 0) { /* If there's more than one word in the line */ + /* Measure the dimensions of a single space using this line's configuration */ + Vector2 originalTextSize = textSize; + textSize = MeasureTextEx(font, " ", (float)objectText->pixelSize, lines[i].spacing); + /* Calculate the number of new spaces to add between words, per existing space */ + float idealNumAdditionalSpaces = + ((float)object->width - originalTextSize.x) / textSize.x; + uint32_t numSpacesToAddPer = + (uint32_t)floor((idealNumAdditionalSpaces - (float)numSpaces) / (float)numSpaces); + /* Create a new string with the additional space */ + size_t justifiedLength = length + (numSpacesToAddPer * numSpaces); + char* justifiedContent = (char*)MemAllocZero((unsigned int)justifiedLength + 1); + uint32_t sourceIndex = 0, destinationIndex = 0; + while (lines[i].content[sourceIndex] != '\0') { + justifiedContent[destinationIndex++] = lines[i].content[sourceIndex]; + /* If the current character is whitespace but the next one is not */ + if (sourceIndex < length && !isspace(lines[i].content[sourceIndex]) && + isspace(lines[i].content[sourceIndex + 1])) { + /* Add 'numSpacesToAddPer' spaces to the justified string */ + for (uint32_t j = 0; j < numSpacesToAddPer; j++) + justifiedContent[destinationIndex++] = ' '; + } + sourceIndex++; + } + /* Free the original content buffer and replace it with the justified one */ + MemFree(lines[i].content); + lines[i].content = justifiedContent; + length = justifiedLength; + } + /* Calculate a spacing, between each letter, with which the drawn text will span the full */ + /* width of the bounds */ + textSize = MeasureTextEx(font, lines[i].content, (float)objectText->pixelSize, 0.0f); + lines[i].spacing = (float)((object->width - textSize.x) / (double)(length - 1)); + } /* objectText->halign == HORIZONTAL_ALIGNMENT_JUSTIFY */ + else /* if (objectText->halign == HORIZONTAL_ALIGNMENT_LEFT) */ + lines[i].position.x = (float)object->x; + + /* Vertical alignment */ + if (objectText->valign == VERTICAL_ALIGNMENT_BOTTOM) { + lines[i].position.y = (float)(object->y + object->height) - + (float)(objectText->pixelSize * (i + 1)); + } else if (objectText->valign == VERTICAL_ALIGNMENT_CENTER) { + float totalLineHeight = (float)(objectText->pixelSize * linesLength); /* All N lines */ + lines[i].position.y = (float)object->y + /* Top of the <object>'s bounds */ + ((float)object->height / 2.0f) + /* Vertical center of the <object>'s bounds */ + (totalLineHeight / 2.0f) - /* Bottom of the centered lines' bounds */ + (float)(objectText->pixelSize * (i + 1)); + } else /* if (objectText->valign == VERTICAL_ALIGNMENT_TOP) */ + lines[i].position.y = (float)object->y + (float)(objectText->pixelSize * i); + + RaytmxTextLineNode* parent = iterator; + iterator = iterator->next; + MemFree(parent); + } + /* Add the lines array to the text object */ + objectText->lines = lines; + objectText->linesLength = linesLength; + } + } /* objectText->content != NULL */ + } + } /* strcmp(hoxmlContext->tag, "text") == 0 */ + else if (strcmp(hoxmlContext->tag, "imagelayer") == 0) { + raytmxState->imageLayer = NULL; + raytmxState->layer = NULL; + } else if (strcmp(hoxmlContext->tag, "group") == 0) { + /* <group>s can be nested so we must return to processing its parent, if it exists */ + if (raytmxState->groupNode != NULL) + raytmxState->groupNode = raytmxState->groupNode->parent; /* Will be null when returning to the root map */ + } +} + +void FreeStateLayers(RaytmxLayerNode* layers) { + RaytmxLayerNode *layersIterator = layers, *layersTemp; + while (layersIterator != NULL) { + /* Group layers may have children, forming a tree-like structure. Free the children. */ + FreeStateLayers(layersIterator->childrenRoot); + /* Iterate to the next node and free this one */ + layersTemp = layersIterator; + layersIterator = layersIterator->next; + MemFree(layersTemp); + } +} + +void FreeState(RaytmxState* raytmxState) { + if (raytmxState == NULL) + return; + + /* Clear the caches. These allow for quick lookups of previously-loaded textures and object templates. They */ + /* aren't needed once loading is complete. */ + RaytmxCachedTextureNode *cachedTextureIterator = raytmxState->texturesRoot, *cachedTextureTemp; + while (cachedTextureIterator != NULL) { + cachedTextureTemp = cachedTextureIterator; + cachedTextureIterator = cachedTextureIterator->next; + if (cachedTextureTemp->fileName != NULL) /* Just in case. Should always be set. */ + MemFree(cachedTextureTemp->fileName); + MemFree(cachedTextureTemp); + } + raytmxState->texturesRoot = NULL; + RaytmxCachedTemplateNode *cachedTemplateIterator = raytmxState->templatesRoot, *cachedTemplateTemp; + while (cachedTemplateIterator != NULL) { + cachedTemplateTemp = cachedTemplateIterator; + cachedTemplateIterator = cachedTemplateIterator->next; + FreeObject(cachedTemplateTemp->objectTemplate.object); + if (cachedTemplateTemp->fileName != NULL) /* Just in case. Should always be set. */ + MemFree(cachedTemplateTemp->fileName); + MemFree(cachedTemplateTemp); + } + raytmxState->templatesRoot = NULL; + + raytmxState->property = NULL; + raytmxState->tileset = NULL; + raytmxState->image = NULL; + raytmxState->tilesetTile = NULL; + raytmxState->animationFrame = NULL; + /* raytmxState->wangSet = NULL; */ /* TODO: Wang sets. Low priority. */ + /* raytmxState->wangColor = NULL; */ /* TODO: Wang sets. Low priority. */ + raytmxState->layer = NULL; + raytmxState->tileLayer = NULL; + raytmxState->objectGroup = NULL; + raytmxState->imageLayer = NULL; + raytmxState->object = NULL; + + /* Free each property in the linked list of properties */ + RaytmxPropertyNode *propertiesIterator = raytmxState->propertiesRoot, *propertiesTemp; + while (propertiesIterator != NULL) { + propertiesTemp = propertiesIterator; + propertiesIterator = propertiesIterator->next; + MemFree(propertiesTemp); + } + /* Zeroize this linked list's properties */ + raytmxState->propertiesRoot = NULL; + raytmxState->propertiesTail = NULL; + raytmxState->propertiesLength = 0; + + /* Free each node in the linked list of tilesets */ + RaytmxTilesetNode *tilesetsIterator = raytmxState->tilesetsRoot, *tilesetsTemp; + while (tilesetsIterator != NULL) { + tilesetsTemp = tilesetsIterator; + tilesetsIterator = tilesetsIterator->next; + MemFree(tilesetsTemp); + } + /* Zeroize this linked list's properties */ + raytmxState->tilesetsRoot = NULL; + raytmxState->tilesetsTail = NULL; + raytmxState->tilesetsLength = 0; + + /* Free each node in the linked list of tileset tiles */ + RaytmxTilesetTileNode *tilesetTilesIterator = raytmxState->tilesetTilesRoot, *tilesetTilesTemp; + while (tilesetTilesIterator != NULL) { + tilesetTilesTemp = tilesetTilesIterator; + tilesetTilesIterator = tilesetTilesIterator->next; + MemFree(tilesetTilesTemp); + } + /* Zeroize this linked list's properties */ + raytmxState->tilesetTilesRoot = NULL; + raytmxState->tilesetTilesTail = NULL; + raytmxState->tilesetTilesLength = 0; + + /* Free each node in the linked list of animation frames */ + RaytmxAnimationFrameNode *animationFramesIterator = raytmxState->animationFramesRoot, *animationFramesTemp; + while (animationFramesIterator != NULL) { + animationFramesTemp = animationFramesIterator; + animationFramesIterator = animationFramesIterator->next; + MemFree(animationFramesTemp); + } + /* Zeroize this linked list's properties */ + raytmxState->animationFramesRoot = NULL; + raytmxState->animationFramesTail = NULL; + raytmxState->animationFramesLength = 0; + + /* Layers may be groups. The resulting collection isn't a link list as much as it is a tree. So freeing layers is */ + /* best done with a recursive approach. */ + FreeStateLayers(raytmxState->layersRoot); + /* Zeroize this linked list's properties */ + raytmxState->layersRoot = NULL; + raytmxState->layersTail = NULL; + raytmxState->layersLength = 0; + + /* Free each node in the linked list of layer tiles */ + RaytmxTileLayerTileNode *layerTilesIterator = raytmxState->layerTilesRoot, *layerTilesTemp; + while (layerTilesIterator != NULL) { + layerTilesTemp = layerTilesIterator; + layerTilesIterator = layerTilesIterator->next; + MemFree(layerTilesTemp); + } + /* Zeroize this linked list's properties */ + raytmxState->layerTilesRoot = NULL; + raytmxState->layerTilesTail = NULL; + raytmxState->layerTilesLength = 0; + + /* Free each node in the linked list of objects */ + RaytmxObjectNode *objectsIterator = raytmxState->objectsRoot, *objectsTemp; + while (objectsIterator != NULL) { + objectsTemp = objectsIterator; + objectsIterator = objectsIterator->next; + MemFree(objectsTemp); + } + /* Zeroize this linked list's properties */ + raytmxState->objectsRoot = NULL; + raytmxState->objectsTail = NULL; + raytmxState->objectsLength = 0; +} + +void inline FreeString(char* str) { + if (str != NULL) + MemFree(str); +} + +void FreeTileset(TmxTileset tileset) { + FreeString(tileset.source); + FreeString(tileset.name); + FreeString(tileset.classString); + if (tileset.hasImage) { + FreeString(tileset.image.source); + UnloadTexture(tileset.image.texture); + } + if (tileset.properties != NULL) { + for (uint32_t i = 0; i < tileset.propertiesLength; i++) + FreeProperty(tileset.properties[i]); + MemFree(tileset.properties); + } + for (uint32_t i = 0; i < tileset.tilesLength; i++) { + TmxTilesetTile tile = tileset.tiles[i]; + FreeString(tile.classString); + if (tile.hasImage) { + FreeString(tile.image.source); + UnloadTexture(tile.image.texture); + if (tile.properties != NULL) { + for (uint32_t j = 0; j < tile.propertiesLength; j++) + FreeProperty(tile.properties[j]); + MemFree(tile.properties); + } + } + if (tile.animation.frames != NULL) + MemFree(tile.animation.frames); + } +} + +void FreeProperty(TmxProperty property) { + FreeString(property.name); + FreeString(property.stringValue); +} + +void FreeLayer(TmxLayer layer) { + FreeString(layer.name); + FreeString(layer.classString); + if (layer.properties != NULL) { + for (uint32_t i = 0; i < layer.propertiesLength; i++) + FreeProperty(layer.properties[i]); + MemFree(layer.properties); + } + switch (layer.type) { + case LAYER_TYPE_TILE_LAYER: + FreeString(layer.exact.tileLayer.encoding); + FreeString(layer.exact.tileLayer.compression); + MemFree(layer.exact.tileLayer.tiles); + break; + case LAYER_TYPE_OBJECT_GROUP: + for (uint32_t j = 0; j < layer.exact.objectGroup.objectsLength; j++) + FreeObject(layer.exact.objectGroup.objects[j]); + MemFree(layer.exact.objectGroup.objects); + break; + case LAYER_TYPE_IMAGE_LAYER: + if (layer.exact.imageLayer.hasImage) + UnloadTexture(layer.exact.imageLayer.image.texture); + break; + case LAYER_TYPE_GROUP: break; /* Nothing to do for this case but compilers like to complain */ + } + /* <group> layers are expected to have child layers, or child <group>s, so recursively free them too */ + for (uint32_t i = 0; i < layer.layersLength; i++) + FreeLayer(layer.layers[i]); +} + +void FreeObject(TmxObject object) { + FreeString(object.name); + FreeString(object.typeString); + FreeString(object.templateString); + if (object.points != NULL) + MemFree(object.points); + if (object.text != NULL) { + if (object.text->lines != NULL) { + for (uint32_t j = 0; j < object.text->linesLength; j++) + FreeString(object.text->lines[j].content); + MemFree(object.text->lines); + } /* object.text->lines != NULL */ + MemFree(object.text); + } /* object.text != NULL */ +} + +#define SIGN(x) (x < 0 ? -1 : +1) + +/** + * Helper function that keeps an integer within the given range. + * Note: A function named Clamp() exists in raylib but uses floats, hence Clampi(). + * + * @param value The preferred value that will be used as long as it is between the minimum and maximum values. + * @param minimum The smallest acceptable value, inclusive. + * @param maximum The largest acceptable value, inclusive. + * @return The preferred value if within the range or the nearest acceptable value. + */ +int Clampi(int value, int minimum, int maximum) { + if (value < minimum) + return minimum; + else if (value > maximum) + return maximum; + return value; +} + +/** + * Scary-looking helper function that does something kind of simple: iterates through the tiles of the given tile layer + * overlapping with the given viewport, one tile per call. This function returns true while iteration is still ongoing + * and false when done. This allows the function to be used e.g. "while (IterateTileLayer( { ...} ))". Details of the + * current tile are returned to the caller with output parameters. Iteration is done row-by-row. + * + * @param map A loaded map model containing the given tile layer. + * @param layer The tile layer within the given map whose tiles will be iterated. + * @param viewport A rectangle representing the region being drawn to. This could also be considered a search area. + * @param transform Position/offset numbers that affect where the tile is ultimately drawn. + * @param rawGid Optional output. The Global ID (GID) with possible flip flags. Pass NULL if not wanted. + * @param tile Optional output. Metadata of the current tile. Pass NULL if not wanted. + * @param tileRect Optional output. The destination rectangle, in pixels, of the current tile. Pass NULL if not wanted. + * @return True if the next tile is being provided via the output parameters, or false if iteration is done. + */ +bool IterateTileLayer(const TmxMap* map, const TmxTileLayer* layer, Rectangle viewport, RaytmxTransform transform, + uint32_t* rawGid, TmxTile* tile, Rectangle* tileRect) { + /* Static variables whose values will persist between calls. These are needed to initialize and iterate. */ + static const TmxTileLayer* currentLayer = NULL; /* Tile layer being iterated */ + static int fromX = 0; /* Initial X position, tile not pixel, that row-by-row iteration begins at */ + static int fromY = 0; /* Initial Y position, tile not pixel, that row-by-row iteration begins at */ + static int toX = 0; /* Final X position, tile not pixel, that iteration ends at */ + static int toY = 0; /* Final Y position, tile not pixel, that iteration ends at */ + static int currentX = 0; /* Current tile X position (column) within the iteration */ + static int currentY = 0; /* Current tile Y position (row) within the iteration */ + + if (map == NULL || map->width == 0 || map->height == 0 || map->tileWidth == 0 || map->tileHeight == 0 || + layer == NULL || layer->tilesLength == 0) + return false; + + if (currentLayer != layer) { /* If the layer has changed (i.e. iteration should initialize) */ + currentLayer = layer; /* Remember this layer */ + + /* Create an adjusted viewport that effectively removes the map's drawn position. With this, it doesn't need */ + /* to be a factor in the math below. */ + const Rectangle viewport2 = { + .x = viewport.x - transform.position.x, + .y = viewport.y - transform.position.y, + .width = viewport.width, + .height = viewport.height + }; + + switch (map->renderOrder) { + case RENDER_ORDER_RIGHT_DOWN: + /* Start at the top-left, iterate right, then iterate down, ending at the bottom-right. */ + /* In other words, this is the order in which English is read. */ + fromX = (int)viewport2.x / (int)map->tileWidth; + fromY = (int)viewport2.y / (int)map->tileHeight; + toX = (int)(viewport2.x + viewport2.width) / (int)map->tileWidth; + toY = (int)(viewport2.y + viewport2.height) / (int)map->tileHeight; + break; + case RENDER_ORDER_RIGHT_UP: + /* Start at the bottom-left, iterate right, then iterate up, ending at the top-right */ + fromX = (int)viewport2.x / (int)map->tileWidth; + fromY = (int)(viewport2.y + viewport2.height) / (int)map->tileHeight; + toX = (int)(viewport2.x + viewport2.width) / (int)map->tileWidth; + toY = (int)viewport2.y / (int)map->tileHeight; + break; + case RENDER_ORDER_LEFT_DOWN: + /* Start at the top-right, iterate left, then iterate down, ending at the bottom-left */ + fromX = (int)(viewport2.x + viewport2.width) / (int)map->tileWidth; + fromY = (int)viewport2.y / (int)map->tileHeight; + toX = (int)viewport2.x / (int)map->tileWidth; + toY = (int)(viewport2.y + viewport2.height) / (int)map->tileHeight; + break; + case RENDER_ORDER_LEFT_UP: + /* Start at the bottom-right, iterate left, then iterate up, ending at the top-left */ + fromX = (int)(viewport2.x + viewport2.width) / (int)map->tileWidth; + fromY = (int)(viewport2.y + viewport2.height) / (int)map->tileHeight; + toX = (int)viewport2.x / (int)map->tileWidth; + toY = (int)viewport2.y / (int)map->tileHeight; + break; + } /* switch (map->renderOrder) */ + /* Restrain the the tile positions to those within the map in case of rounding mistakes */ + fromX = Clampi(fromX, 0, (int)map->width - 1); + fromY = Clampi(fromY, 0, (int)map->height - 1); + toX = Clampi(toX, 0, (int)map->width - 1); + toY = Clampi(toY, 0, (int)map->height - 1); + /* Begin iteration from both "from" tile positions */ + currentX = fromX; + currentY = fromY; + } else if (currentX == toX) { /* If the end of the current row was reached */ + /* Rendering is done row-by-row. This row is done so move to the next one. */ + currentX = fromX; + currentY += SIGN(toY - fromY); /* Either +1 or -1 */ + } else { /* If still iterating through the current row */ + /* Move to the right or left by one tile */ + currentX += SIGN(toX - fromX); /* Either +1 or -1 */ + } + + /* If iteration has gone beyond the final row */ + if ((toY - fromY > 0 && currentY > toY) || (toY - fromY < 0 && currentY < toY)) { + /* This is the termination condition. Zero all values and return false so the caller exits its loop. */ + currentLayer = NULL; + fromX = fromY = toX = toY = currentX = currentY = 0; + return false; + } + + /* Calculate the index in the tile layer from knowing the tile's X and Y position (in tiles, not pixels) */ + int index = (currentY * (int)map->width) + currentX; + if (index < 0 || index >= (int)layer->tilesLength) { /* Bounds check */ + currentLayer = NULL; + fromX = fromY = toX = toY = currentX = currentY = 0; + return false; + } + + /* Get the raw Global ID (GID) of the tile at this position from the layer's list of tiles. This list's order */ + /* matches the map's render order. */ + uint32_t localRawGid = layer->tiles[index]; + if (rawGid != NULL) + *rawGid = localRawGid; /* Assign the value to he output parameter */ + if (tile != NULL) { + /* The raw GID may have bit flags on it. They need to be removed in order to get the actual GID value.*/ + uint32_t gid = GetGid(localRawGid, NULL, NULL, NULL, NULL); + /* Get the tile's metadata from knowing its GID */ + if (gid < map->gidsToTilesLength) /* Bounds check */ + *tile = map->gidsToTiles[gid]; + } + if (tileRect != NULL) { + /* Calculate the tile's destination rectangle, in pixels */ + *tileRect = (Rectangle) { + .x = (float)((uint32_t)currentX * map->tileWidth), + .y = (float)((uint32_t)currentY * map->tileHeight), + .width = (float)map->tileWidth, + .height = (float)map->tileHeight + }; + } + + return true; +} + +void DrawTMXLayersInternal(const TmxMap* map, const Camera2D* camera, const Rectangle* viewport, const TmxLayer* layers, + uint32_t layersLength, RaytmxTransform transform, Color tint) +{ + if (map == NULL || layers == NULL || layersLength == 0) + return; + + for (uint32_t i = 0; i < layersLength; i++) { + TmxLayer layer = layers[i]; + if (!layers[i].visible) /* If the layer is not visible */ + continue; /* Skip it - it's literally invisible */ + + /* All types of layers can have a couple attributes that affect color: 'opacity' and 'tintcolor' */ + Color layerTint = tint; + layerTint.a = (unsigned char)((double)layerTint.a * layer.opacity); + if (layer.hasTintColor) + layerTint = ColorTint(layerTint, layer.tintColor); + + /* Determine the viewport. This will depend on a couple parameters. If 'viewport' was assigned, it's used */ + /* directly. If 'camera' was assigned, its target and zoom are used to derive a reasonable viewport from the */ + /* screen's dimensions. If neither is assigned, the map's bounds are used. */ + Rectangle viewport2; + if (viewport != NULL) + viewport2 = *viewport; + else if (camera != NULL) { + viewport2.width = (float)GetScreenWidth() / camera->zoom; + viewport2.height = (float)GetScreenHeight() / camera->zoom; + viewport2.x = camera->target.x - (viewport2.width / 2.0f); + viewport2.y = camera->target.y - (viewport2.height / 2.0f); + } else { + viewport2.x = transform.position.x; + viewport2.y = transform.position.y; + viewport2.width = (float)(map->width * map->tileWidth); + viewport2.height = (float)(map->height * map->tileHeight); + } + + /* Create an updated transform for this layer. This will be the sum of the position the map is drawn at, the */ + /* layer's offset, and the effect of parallax scrolling if conditions are met. */ + RaytmxTransform transform2 = transform; + /* "The effective parallax scrolling factor of a layer is determined by multiplying the parallax scrolling */ + /* factor by the scrolling factors of all parent layers." */ + transform2.parallax.x *= (float)layer.parallaxX; + transform2.parallax.y *= (float)layer.parallaxY; + /* Apply the offset specific to this layer and/or parallax scrolling. Also applies to child layers. */ + transform2.position.x += (float)layer.offsetX + (transform2.cameraOffset.x * (1.0f - transform2.parallax.x)); + transform2.position.y += (float)layer.offsetY + (transform2.cameraOffset.y * (1.0f - transform2.parallax.y)); + + switch (layer.type) { + case LAYER_TYPE_TILE_LAYER: DrawTMXTileLayer(map, viewport2, layer, transform2, layerTint); break; + case LAYER_TYPE_OBJECT_GROUP: DrawTMXObjectGroup(map, viewport2, layer, transform2, layerTint); break; + case LAYER_TYPE_IMAGE_LAYER: DrawTMXImageLayer(map, viewport2, layer, transform2, layerTint); break; + case LAYER_TYPE_GROUP: + DrawTMXLayersInternal(map, camera, &viewport2, layer.layers, layer.layersLength, transform2, layerTint); + break; + } + } +} + +void DrawTMXTileLayer(const TmxMap* map, Rectangle viewport, TmxLayer layer, RaytmxTransform transform, Color tint) +{ + if (map == NULL || layer.type != LAYER_TYPE_TILE_LAYER || layer.exact.tileLayer.tilesLength == 0) + return; + + /* Iterate through each tile that the screen rectangle overlaps with */ + uint32_t rawGid; + Rectangle tileRect; + while (IterateTileLayer(/* map: */ map, /* layer: */ &layer.exact.tileLayer, /* viewport: */ viewport, + /* transform: */ transform, /* rawGid: */ &rawGid, /* tile: */ NULL, /* tileRect: */ &tileRect)) + { + tileRect.x += transform.position.x; + tileRect.y += transform.position.y; + DrawTMXLayerTile(/* map: */ map, /* viewport: */ viewport, /* rawGid: */ rawGid, /* destRect: */ tileRect, + /* tint: */ tint); + } +} + +void DrawTextureTile(Texture2D texture, Rectangle source, Rectangle dest, bool flipX, bool flipY, bool flipDiag, + Color tint) { + if (texture.id == 0) /* If the texture is invalid */ + return; + + float textureWidth = (float)texture.width; + float textureHeight = (float)texture.height; + + /* Determine the area within the texture to be drawn */ + /* Note: The coordinates here are in the [0.0, 1.0] range where (0.0, 0.0) is the bottom-left corner of the */ + /* texture, (1.0, 0.0) is the bottom-right, and (1.0, 1.0) is the top-right. In other words, the coordinates are */ + /* a ratio of the dimensions making (0.5, 0.5) the center of the texture regardless of its aspect ratio. */ + Vector2 sourceTopLeft, sourceTopRight, sourceBottomLeft, sourceBottomRight; + sourceTopLeft.x = source.x / textureWidth; + sourceTopLeft.y = source.y / textureHeight; + sourceTopRight.x = (source.x + source.width) / textureWidth; + sourceTopRight.y = source.y / textureHeight; + sourceBottomLeft.x = source.x / textureWidth; + sourceBottomLeft.y = (source.y + source.height) / textureHeight; + sourceBottomRight.x = (source.x + source.width) / textureWidth; + sourceBottomRight.y = (source.y + source.height) / textureHeight; + if (flipDiag) { /* If the tile uses a diagonal flip */ + /* "The diagonal flip should flip the bottom left and top right corners of the tile..." */ + Vector2 temp = sourceBottomLeft; + sourceBottomLeft = sourceTopRight; + sourceTopRight = temp; + } + + /* Determine the area on the screen to be drawn to */ + Vector2 destTopLeft, destTopRight, destBottomLeft, destBottomRight; + destTopLeft.x = dest.x; + destTopLeft.y = dest.y; + destTopRight.x = dest.x + dest.width; + destTopRight.y = dest.y; + destBottomLeft.x = dest.x; + destBottomLeft.y = dest.y + dest.height; + destBottomRight.x = dest.x + dest.width; + destBottomRight.y = dest.y + dest.height; + + rlSetTexture(texture.id); + rlBegin(RL_QUADS); + { + rlColor4ub(tint.r, tint.g, tint.b, tint.a); + rlNormal3f(0.0f, 0.0f, 1.0f); /* Normal vector pointing towards viewer */ + + /* Top-left corner of the quad */ + if (flipX && !flipY) + rlTexCoord2f(sourceTopRight.x, sourceTopRight.y); + else if (flipY && !flipX) + rlTexCoord2f(sourceBottomLeft.x, sourceBottomLeft.y); + else + rlTexCoord2f(sourceTopLeft.x, sourceTopLeft.y); + if (flipX && flipY) + rlVertex2f(destBottomRight.x, destBottomRight.y); + else + rlVertex2f(destTopLeft.x, destTopLeft.y); + + /* Bottom-left corner of the quad */ + if (flipX && !flipY) + rlTexCoord2f(sourceBottomRight.x, sourceBottomRight.y); + else if (flipY && !flipX) + rlTexCoord2f(sourceTopLeft.x, sourceTopLeft.y); + else + rlTexCoord2f(sourceBottomLeft.x, sourceBottomLeft.y); + if (flipX && flipY) + rlVertex2f(destTopRight.x, destTopRight.y); + else + rlVertex2f(destBottomLeft.x, destBottomLeft.y); + + /* Bottom-right corner of the quad */ + if (flipX && !flipY) + rlTexCoord2f(sourceBottomLeft.x, sourceBottomLeft.y); + else if (flipY && !flipX) + rlTexCoord2f(sourceTopRight.x, sourceTopRight.y); + else + rlTexCoord2f(sourceBottomRight.x, sourceBottomRight.y); + if (flipX && flipY) + rlVertex2f(destTopLeft.x, destTopLeft.y); + else + rlVertex2f(destBottomRight.x, destBottomRight.y); + + /* Top-right corner of the quad */ + if (flipX && !flipY) + rlTexCoord2f(sourceTopLeft.x, sourceTopLeft.y); + else if (flipY && !flipX) + rlTexCoord2f(sourceBottomRight.x, sourceBottomRight.y); + else + rlTexCoord2f(sourceTopRight.x, sourceTopRight.y); + if (flipX && flipY) + rlVertex2f(destBottomLeft.x, destBottomLeft.y); + else + rlVertex2f(destTopRight.x, destTopRight.y); + } + rlEnd(); + rlSetTexture(0); +} + +void DrawTMXLayerTile(const TmxMap* map, Rectangle viewport, uint32_t rawGid, Rectangle destRect, Color tint) { + if (map == NULL || tint.a == 0) + return; + + bool isFlippedHorizontally, isFlippedVertically, isFlippedDiagonally, isRotatedHexagonal120; + /* Tile Global IDs (GIDs) can have several bit flags that indicate transforms. This function is used to both get */ + /* those possible transform flags as well as the actual GID value without those bit flags. */ + uint32_t gid = GetGid(rawGid, &isFlippedHorizontally, &isFlippedVertically, &isFlippedDiagonally, + &isRotatedHexagonal120); + if (gid >= map->gidsToTilesLength) /* If the GID is outside the range of known GIDs */ + return; /* Do not attempt to draw this time */ + /* With the GID, grab the relevant tile information (texture, animation, etc.) from the global mapping */ + TmxTile tile = map->gidsToTiles[gid]; + if (tile.gid == 0) /* If the GID is not known to exist in any tilesets within the map */ + return; /* Do not attempt to draw this tile */ + if (tile.hasAnimation && tile.frameIndex < tile.animation.framesLength && + tile.animation.frames[tile.frameIndex].gid < map->gidsToTilesLength) { /* If the tile is/has an animation */ + /* Animation tiles are meta; they contain a list of other GIDs to be drawn. Get the actual tile to draw this */ + /* frame from that list. */ + gid = tile.animation.frames[tile.frameIndex].gid; + if (gid < map->gidsToTilesLength) + tile = map->gidsToTiles[gid]; + } + + /* Determine where the tile will be drawn. raylib's coordinates consider (X, Y) to be the top-left corner of the */ + /* rectangle being drawn. The TMX documentation complicates things a bit saying "Larger tiles will extend at the */ + /* top and right (anchored to the bottom left)" meaning that TMX considers (X, Y) to be the bottom-left corner. */ + /* The simplest way to reconcile the Y coordinate differences is to substract the texture's height at Y + 1. This */ + /* way, tiles larger than the map's tile height values will be drawn further up (negative Y direction). */ + destRect.x += tile.offset.x; + destRect.y += tile.offset.y + map->tileHeight - tile.sourceRect.height; + + /* If the viewport and destination rectangles are overlapping to any degree (i.e. if the tile is visible) */ + if (CheckCollisionRecs(viewport, destRect)) { + DrawTextureTile(/* texture: */ tile.texture, /* source: */ tile.sourceRect, /* dest: */ destRect, + /* flipX: */ isFlippedHorizontally, /* flipY: */ isFlippedVertically, /* flipDiag: */ isFlippedDiagonally, + /* tint: */ tint); + } +} + +void DrawTMXObjectTile(const TmxMap* map, Rectangle viewport, uint32_t rawGid, RaytmxTransform transform, float width, + float height, Color tint) { + if (map == NULL || width <= 0 || height <= 0 || tint.a == 0) + return; + + bool isFlippedHorizontally, isFlippedVertically, isFlippedDiagonally, isRotatedHexagonal120; + /* Tile Global IDs (GIDs) can have several bit flags that indicate transforms. This function is used to both get */ + /* those possible transform flags as well as the actual GID value without those bit flags. */ + uint32_t gid = GetGid(rawGid, &isFlippedHorizontally, &isFlippedVertically, &isFlippedDiagonally, + &isRotatedHexagonal120); + if (gid >= map->gidsToTilesLength) /* If the GID is outside the range of known GIDs */ + return; /* Do not attempt to draw this time */ + /* With the GID, grab the relevant tile information (texture, animation, etc.) from the global mapping */ + TmxTile tile = map->gidsToTiles[gid]; + if (tile.gid == 0) /* If the GID is not known to exist in any tilesets within the map */ + return; /* Do not attempt to draw this time */ + if (tile.hasAnimation && tile.frameIndex < tile.animation.framesLength && + tile.animation.frames[tile.frameIndex].gid < map->gidsToTilesLength) { /* If the tile is/has an animation */ + /* Animation tiles are meta; they contain a list of other GIDs to be drawn. Get the actual tile to draw this */ + /* frame from that list. */ + gid = tile.animation.frames[tile.frameIndex].gid; + if (gid < map->gidsToTilesLength) + tile = map->gidsToTiles[gid]; + } + + /* Determine the area in which to draw, and potentially stretch, the texture. This area matches that of the */ + /* <object>, not the tile size. This also means that the Y coordinate needs consideration because raylib */ + /* considers (X, Y) to the be top-left corner of any area but the TMX format considers it the bottom-left. */ + const Rectangle destRect = + { + .x = transform.position.x + tile.offset.x, + .y = transform.position.y + tile.offset.y - height, + .width = width, + .height = height + }; + + /* If the viewport and destination rectangles are overlapping to any degree (i.e. if the tile is visible) */ + if (CheckCollisionRecs(viewport, destRect)) { + DrawTextureTile(/* texture: */ tile.texture, /* source: */ tile.sourceRect, /* dest: */ destRect, + /* flipX: */ isFlippedHorizontally, /* flipY: */ isFlippedVertically, /* flipDiag: */ isFlippedDiagonally, + /* tint: */ tint); + } +} + +void DrawTMXObjectGroup(const TmxMap* map, Rectangle viewport, TmxLayer layer, RaytmxTransform transform, Color tint) { + if (map == NULL || layer.type != LAYER_TYPE_OBJECT_GROUP || tint.a == 0) + return; + + TmxObjectGroup objectGroup = layer.exact.objectGroup; + for (int32_t i = 0; i < (int32_t)objectGroup.objectsLength; i++) { + /* Select the object to draw based on the <objectgroup>'s draw order */ + TmxObject object; + if (objectGroup.drawOrder == OBJECT_GROUP_DRAW_ORDER_INDEX) + object = objectGroup.objects[i]; + else /* if (objectGroup.drawOrder == OBJECT_GROUP_DRAW_ORDER_TOP_DOWN) */ + object = objectGroup.objects[objectGroup.ySortedObjects[i]]; + + if (object.type == OBJECT_TYPE_TILE) /* If the object is a tile with an abitrary GID and dimensions */ + { + RaytmxTransform transform2 = transform; + transform2.position.x += (float)object.x; + transform2.position.y += (float)object.y; + /* Note: This draw method handles occlusion culling so it doesn't need to be done here */ + DrawTMXObjectTile(map, /* viewport: */ viewport, /* rawGid: */ object.gid, /* transform: */ transform2, + /* width: */ (float)object.width, /* height: */ (float)object.height, /* color: */ tint); + } + else /* If the object is any type other than a tile */ + { + Rectangle offsetAabb = object.aabb; + offsetAabb.x += transform.position.x; + offsetAabb.y += transform.position.y; + /* If the viewport and the polygon's AABB are overlapping to any degree (i.e. it is visible) */ + if (CheckCollisionRecs(viewport, offsetAabb)) { + switch (object.type) { + case OBJECT_TYPE_RECTANGLE: + { + const Rectangle rectangle = + { + .x = transform.position.x + (float)object.x, + .y = transform.position.y + (float)object.y, + .width = (float)object.width, + .height = (float)object.height + }; + DrawRectangleRec(/* rec: */ rectangle, /* color: */ objectGroup.color); + } + break; + case OBJECT_TYPE_ELLIPSE: + { + /* The width and height of the object determine the semi major and minor axes. That means the */ + /* object's width is twice the horizontal radius and its height is twice the vertical radius. */ + const float halfWidth = (float)object.width / 2.0f; + const float halfHeight = (float)object.height / 2.0f; + DrawEllipse(/* centerX: */ transform.position.x + (float)object.x + halfWidth, + /* centerY: */ transform.position.y + (float)object.y + halfHeight, /* radiusH: */ halfWidth, + /* radiusV: */ halfHeight, /* color: */ objectGroup.color); + } + break; + case OBJECT_TYPE_POINT: + { + /* A point is a single pixel but the TMX format doesn't require the point must be drawn any */ + /* particular way. Tiled uses a pin icon. Drawing the exact pixel is tempting but hard to see so */ + /* the point is drawn with a circle, with a diameter equal to a quarter of a tile's width. */ + const Vector2 center = + { + .x = transform.position.x + (float)object.x, + .y = transform.position.y + (float)object.y + }; + DrawCircleV(/* center: */ center, /* radius: */ (float)map->tileWidth / 4.0f, + /*color: */ objectGroup.color); + } + break; + case OBJECT_TYPE_POLYGON: + case OBJECT_TYPE_POLYLINE: + /* Copy the 'points' array to the 'drawPoints' array and apply the drawing position, an offset */ + /* applied by the layer and/or draw call. The 'drawPoints' array was allocated at the same time */ + /* as 'points' with the same size. This improves draw call times by reducing memory allocations. */ + memcpy(object.drawPoints, object.points, sizeof(Vector2) * object.pointsLength); + for (uint32_t i = 0; i < object.pointsLength; i++) { + /* Polygons' and polyglines' vertices are stored with relative positions. To get the absolute */ + /* position needed for drawing, just add the object's position and offset. */ + object.drawPoints[i].x += transform.position.x + (float)object.x; + object.drawPoints[i].y += transform.position.y + (float)object.y; + } + /* Use the offset points to draw the poly(gon|line) */ + if (object.type == OBJECT_TYPE_POLYGON) { + /* Note: Polygons' first elements are their centroids. DrawTriangleFan() requires this. */ + /* And, the last element in 'drawPoints' is a duplicate of the first, non-centroid point. */ + DrawTriangleFan(/* points: */ object.drawPoints, /* pointCount: */ object.pointsLength, + /* color: */ objectGroup.color); + } else /* if (object.type == OBJECT_TYPE_POLYLINE) */ { + /* Note: The last element in 'drawPoints' is a duplicate of the first point */ + for (uint32_t i = 1; i < object.pointsLength; i++) { + DrawLineEx(/* startPos: */ object.drawPoints[i - 1], /* endPos: */ object.drawPoints[i], + /* thick: */ TMX_LINE_THICKNESS, /* color: */ objectGroup.color); + } + } + break; + case OBJECT_TYPE_TEXT: + for (uint32_t i = 0; i < object.text->linesLength; i++) { + Vector2 position = object.text->lines[i].position; + position.x += transform.position.x; + position.y += transform.position.y; + DrawTextEx(/* font: */ object.text->lines[i].font, /* text: */ object.text->lines[i].content, + /* position: */ position, /* fontSize: */ (float)object.text->pixelSize, + /* spacing: */ object.text->lines[i].spacing, /* tint: */ object.text->color); + } + break; + case OBJECT_TYPE_TILE: + /* Object tiles are handled in the 'if' case of this 'else' block because the use of an AABB for */ + /* occlusion culling is not reliable for them */ + break; + } + } + } + } +} + +void DrawTMXImageLayer(const TmxMap* map, Rectangle viewport, TmxLayer layer, RaytmxTransform transform, Color tint) { + if (map == NULL || layer.type != LAYER_TYPE_IMAGE_LAYER || !layer.exact.imageLayer.hasImage || + layer.exact.imageLayer.image.width == 0 || layer.exact.imageLayer.image.height == 0 || tint.a == 0) + return; + + TmxImageLayer imageLayer = layer.exact.imageLayer; + /* Determine where the image of this image layer would be drawn, assuming no repetitions */ + Rectangle imageRect = + { + .x = transform.position.x, + .y = transform.position.y, + .width = (float)imageLayer.image.width, + .height = (float)imageLayer.image.height + }; + + if (!imageLayer.repeatX && !imageLayer.repeatY && CheckCollisionRecs(viewport, imageRect)) /* If visible */ + DrawTextureV(/* texture: */ imageLayer.image.texture, /* position: */ transform.position, /* tint: */ tint); + else if (imageLayer.repeatX || imageLayer.repeatY) { /* If the image might be drawn across a whole axis, or both */ + /* Use integer division to determine the X and Y positions at which a the image would appear if it were */ + /* repeated across the whole axis (i.e. if "Repeat X" and/or "Repeat Y" are enabled) */ + int coefficientX = (int)(viewport.x - imageRect.x) / (int)imageRect.width; + int coefficientY = (int)(viewport.y - imageRect.y) / (int)imageRect.height; + float x0 = imageRect.x + (imageRect.width * (float)coefficientX); /* Initial X position */ + float y0 = imageRect.y + (imageRect.height * (float)coefficientY); /* Initial Y position */ + + if (imageLayer.repeatX) /* If repeating along the X axis */ + imageRect.x = x0; /* Move the image's representative rectangle to that X */ + if (imageLayer.repeatY) + imageRect.y = y0; + if (CheckCollisionRecs(viewport, imageRect)) { /* If the repeating image would be visible in the viewport */ + /* Take a step back on each axis that the image repeats on. This ensures we don't leave any empty space */ + /* along the left and/or top edge of the viewport. */ + if (imageLayer.repeatX) + x0 -= imageRect.width; + if (imageLayer.repeatY) + y0 -= imageRect.height; + + /* Create some unchanging objects that the draw function will need */ + Rectangle sourceRect; /* Region within the texture to be drawn. We'll use the whole texture. */ + sourceRect.x = 0.0f; + sourceRect.y = 0.0f; + sourceRect.width = (float)imageLayer.image.width; + sourceRect.height = (float)imageLayer.image.height; + Vector2 origin; /* Reference point used for rotations. We're not rotating so we'll just use all zeroes. */ + origin.x = 0.0f; + origin.y = 0.0f; + + if (imageLayer.repeatX && imageLayer.repeatY) { /* If repeating on both axes */ + /* Loop over both the X and Y axes to draw an array of repeated images */ + for (float x = x0; x <= viewport.x + viewport.width; x += imageRect.width) { + for (float y = y0; y <= viewport.y + viewport.height; y += imageRect.height) { + imageRect.x = x; + imageRect.y = y; + DrawTexturePro(/* texture: */ imageLayer.image.texture, /* source: */ sourceRect, + /* dest: */ imageRect, /* origin: */ origin, /* rotation: */ 0.0f, /* tint: */ tint); + } + } + } else if (imageLayer.repeatX) { /* If repeating on just the X axis */ + /* Loop over just the X axis to draw a horizontal line of repeated images */ + for (float x = x0; x <= viewport.x + viewport.width; x += imageRect.width) { + imageRect.x = x; + DrawTexturePro(/* texture: */ imageLayer.image.texture, /* source: */ sourceRect, + /* dest: */ imageRect, /* origin: */ origin, /* rotation: */ 0.0f, /* tint: */ tint); + } + } else if (imageLayer.repeatY) { /* If repeating on just the Y axis */ + /* Loop over just the Y axis to draw a vertical line of repeated images */ + for (float y = y0; y <= viewport.y + viewport.height; y += imageRect.height) { + imageRect.y = y; + DrawTexturePro(/* texture: */ imageLayer.image.texture, /* source: */ sourceRect, + /* dest: */ imageRect, /* origin: */ origin, /* rotation: */ 0.0f, /* tint: */ tint); + } + } + } + } +} + +/** + * Helper function for detecting collisions between a line and a polygon given the line's start and end points and an + * array and count of the polygon's vertices. + * + * @param startPos One of the two points forming the line. + * @param endPos The other point forming the line. + * @param polyPos Position, in pixels, of the polygon. Its points are relative to this position. + * @param points Array of vertices of the polygon. + * @param pointCount Number of verticies in the polygon and, therefore, length of the array of vertices. + * @return True if the line and polygon collide with one another, or false if there is no collision. + */ +bool CheckCollisionLinePoly(Vector2 startPos, Vector2 endPos, Vector2 polyPos, Vector2* points, int pointCount) { + if (pointCount < 3) + return false; + + /* Cycle through each edge of the polygon */ + int nextIndex = 0; + for (int currentIndex = 0; currentIndex < pointCount; currentIndex++) { + /* Get the next index. If the current index is the last, wrap around. */ + nextIndex = currentIndex + 1; + if (nextIndex == pointCount) + nextIndex = 0; + /* Get the current and next points. These two points form an edge of the polygon. */ + Vector2 currentPoint = points[currentIndex]; + Vector2 nextPoint = points[nextIndex]; + /* Check these edges for collisions. Note: The last parameter is unused, hence zero. */ + if (CheckCollisionLines(/* startPos1: */ startPos, /* endPos1: */ endPos, /* startPos2: */ currentPoint, + /* endPos: */ nextPoint, /* collisionPoint: */ NULL)) + return true; + } + + return false; +} + +/** + * Helper function for detecting collisions between two polygons given arrays and counts of their vertices. + * + * @param polyPos1 Position, in pixels, of the first polygon. Its points are relative to this position. + * @param points1 Array of vertices of the first polygon. + * @param pointCount1 Number of vertices in the first polygon and, therefore, length of the array of vertices. + * @param polyPos2 Position, in pixels, of the second polygon. Its points are relative to this position. + * @param points2 Array of vertices of the second polygon. + * @param pointCount2 Number of vertices in the second polygon and, therefore, length of the array of vertices. + * @return True if the given polygons collide with one another, or false if there is no collision. + */ +bool CheckCollisionPolyPoly(Vector2 polyPos1, Vector2* points1, int pointCount1, Vector2 polyPos2, Vector2* points2, + int pointCount2) { + /* If either vertex array is missing or does not contain enough vertices to define a polygon */ + if (points1 == NULL || pointCount1 < 3 || points2 == NULL || pointCount2 < 3) + return false; + + /* Cycle through each edge of polygon 1 */ + int nextIndex = 0; + for (int currentIndex = 0; currentIndex < pointCount1; currentIndex++) { + /* Get the next index. If the current index is the last, wrap around. */ + nextIndex = currentIndex + 1; + if (nextIndex == pointCount1) + nextIndex = 0; + /* Get the current and next points. These two points form an edge of polygon 1. */ + Vector2 currentPoint = points1[currentIndex]; + currentPoint.x += polyPos1.x; + currentPoint.y += polyPos1.y; + Vector2 nextPoint = points1[nextIndex]; + nextPoint.x += polyPos1.x; + nextPoint.y += polyPos1.y; + /* Check this edge for collisions against edges of polygon 2 */ + if (CheckCollisionLinePoly(currentPoint, nextPoint, polyPos2, points2, pointCount2)) + return true; + } + + /* Check if polygon 1 is fully inside polygon 2 */ + if (CheckCollisionPointPoly(/* point: */ points1[0], /* points: */ points2, /* pointCount: */ pointCount2)) + return true; + + /* Check if polygon 2 is fully inside polygon 1 */ + if (CheckCollisionPointPoly(/* point: */ points2[0], /* points: */ points1, /* pointCount: */ pointCount1)) + return true; + + return false; +} + +RAYTMX_DEC bool CheckCollisionTMXObjects(TmxObject object1, TmxObject object2) { + /* Perform a quick collision check on the Axis-Aligned Bounding Boxes (AABB) before more accurate checks */ + if (!CheckCollisionRecs(/* rec1: */ object1.aabb, /* rec2: */ object2.aabb)) + return false; /* The AABBs do not collide so the objects cannot possibly collide */ + + switch (object1.type) { + case OBJECT_TYPE_RECTANGLE: /* Object 1's type */ + case OBJECT_TYPE_ELLIPSE: /* Object 1's type (treated as a rectangle due to difficulty) */ + case OBJECT_TYPE_TEXT: /* Object 1's type */ + case OBJECT_TYPE_TILE: /* Object 1's type */ + switch (object2.type) { + case OBJECT_TYPE_RECTANGLE: /* Object 2's type */ + case OBJECT_TYPE_ELLIPSE: /* Object 2's type (treated as a rectangle due to difficulty) */ + case OBJECT_TYPE_TEXT: /* Object 2's type */ + case OBJECT_TYPE_TILE: /* Object 2's type */ + /* The objects' shapes, as far as collisions are concerned, are identical to the AABB. We already */ + /* determined the AABBs collide so these objects collide. */ + return true; + + case OBJECT_TYPE_POINT: /* Object 2's type */ + return CheckCollisionPointRec(/* point: */ (Vector2){(float)object2.x, (float)object2.y}, + /* rec: */ object1.aabb); + + case OBJECT_TYPE_POLYGON: /* Object 2's type */ + case OBJECT_TYPE_POLYLINE: /* Object 2's type */ + { + /* A rectangle is a polygon. Create an array of points to treat it as a polygon keeping in mind polygon */ + /* vertices are relative so the top-left corner is always (0, 0). */ + Vector2 points[4]; + points[0] = (Vector2){0.0f, 0.0f}; + points[1] = (Vector2){(float)object1.width, 0.0f}; + points[2] = (Vector2){(float)object1.width, (float)object1.height}; + points[3] = (Vector2){0.0f, (float)object1.height}; + return CheckCollisionPolyPoly(/* polyPos1: */ (Vector2){(float)object1.x, (float)object1.y}, + /* points1: */ points, /* pointCount: */ 4, + /* polyPos2: */ (Vector2){(float)object2.x, (float)object2.y}, /* points2: */ object2.points, + /* pointCount2: */ object2.pointsLength); + } + } + break; + + case OBJECT_TYPE_POINT: /* Object 1's type */ + switch (object2.type) { + case OBJECT_TYPE_RECTANGLE: /* Object 2's type */ + case OBJECT_TYPE_ELLIPSE: /* Object 2's type (treated as a rectangle due to difficulty) */ + case OBJECT_TYPE_TEXT: /* Object 2's type */ + case OBJECT_TYPE_TILE: /* Object 2's type */ + return CheckCollisionPointRec(/* point: */ (Vector2){(float)object1.x, (float)object2.y}, + /* rec: */ object2.aabb); + + case OBJECT_TYPE_POINT: /* Object 2's type */ + return object1.x == object2.x && object1.y == object2.y; + + case OBJECT_TYPE_POLYGON: /* Object 2's type */ + case OBJECT_TYPE_POLYLINE: /* Object 2's type */ + return CheckCollisionPointPoly((Vector2){(float)object1.x, (float)object1.y}, object2.points, + object2.pointsLength); + } + break; + + case OBJECT_TYPE_POLYGON: /* Object 1's type */ + case OBJECT_TYPE_POLYLINE: /* Object 1's type */ + switch (object2.type) { + case OBJECT_TYPE_RECTANGLE: /* Object 2's type */ + case OBJECT_TYPE_ELLIPSE: /* Object 2's type (treated as a rectangle due to difficulty) */ + case OBJECT_TYPE_TEXT: /* Object 2's type */ + case OBJECT_TYPE_TILE: /* Object 2's type */ + { + /* A rectangle is a polygon. Create an array of points to treat it as a polygon keeping in mind polygon */ + /* vertices are relative so the top-left corner is always (0, 0). */ + Vector2 points[4]; + points[0] = (Vector2){0.0f, 0.0f}; + points[1] = (Vector2){(float)object2.width, 0.0f}; + points[2] = (Vector2){(float)object2.width, (float)object2.height}; + points[3] = (Vector2){0.0f, (float)object2.height}; + return CheckCollisionPolyPoly(/* polyPos1: */ (Vector2){(float)object1.x, (float)object1.y}, + /* points1: */ object1.points, /* pointCount1: */ object1.pointsLength, + /* polyPos2: */ (Vector2){(float)object2.x, (float)object2.y}, /* points2: */ points, + /* pointCount2: */ 4); + } + + case OBJECT_TYPE_POINT: /* Object 2's type */ + return CheckCollisionPointPoly(/* point: */ (Vector2){(float)object2.x, (float)object2.y}, + /* points: */ object1.points, /* pointCount: */ object1.pointsLength); + + case OBJECT_TYPE_POLYGON: /* Object 2's type */ + case OBJECT_TYPE_POLYLINE: /* Object 2's type */ + return CheckCollisionPolyPoly(/* polyPos1: */ (Vector2){(float)object1.x, (float)object1.y}, + /* points1: */ object1.points, /* pointCount1: */ object1.pointsLength, + /* polyPos2: */ (Vector2){(float)object2.x, (float)object2.y}, /* points2: */ object2.points, + /* pointCount2: */ object2.pointsLength); + } + break; + } + + return false; +} + +/** + * Helper function for applying a translation to a given object by the given deltas. + * + * @param object The object whose position(s) should be translated. + * @param dx The X delta, in pixels, to translate. + * @param dy The Y delta, in pixels, to translate. + * @return A translated copy of the given object. + */ +TmxObject TranslateObject(TmxObject object, float dx, float dy) { + /* Translate the position of the object by the different X and Y deltas */ + object.x += (double)dx; + object.y += (double)dy; + /* Translate the Axis-Aligned Bounding Box (AABB) to match */ + object.aabb.x += dx; + object.aabb.y += dy; + /* Note: Although polygons and polylines have a series of vertices, they are relative to the object. This means */ + /* nothing more needs to be done. Translating the object's X and Y effectively translates each vertex. */ + return object; +} + +/** + * Helper function for checking for collisions between 1+ tile layers, or groups potentially containing 1+ tile layers, + * and an object of arbitrary type. These checks use the collision information associated with tiles as object groups. + * + * @param map A loaded map model containing the given layers. + * @param layers An array of select tile layers or group layers to be checked for collisions. + * @param layersLength Length of the given array of tile layers. + * @param object A TMX <object> to be checked for a collision. + * @param outputObject Output parameter assigned with the object in the tile layer that the given object collided with. + * NULL if not wanted. + * @return True if one of the given tile layers collides with the given object, or false if there is no collision. + */ +bool CheckCollisionTMXTileLayerObject(const TmxMap* map, const TmxLayer* layers, uint32_t layersLength, + TmxObject object, TmxObject* outputObject) { + if (map == NULL || layers == NULL || layersLength == 0) + return false; + + /* Iterate through each layer and check their tiles for collisions with the given object */ + for (uint32_t i = 0; i < layersLength; i++) { + if (layers[i].type == LAYER_TYPE_TILE_LAYER) { /* If the layer has tiles */ + /* Iterate through each tile that the object's Axis-Aligned Bounding Box (AABB) overlaps with */ + TmxTile tile; + Rectangle tileRect; + RaytmxTransform transform = { .position = {0.0f, 0.0f}, .parallax = {1.0f, 1.0f} }; + while (IterateTileLayer(/* map: */ map, /* layer: */ &layers[i].exact.tileLayer, + /* viewport: */ object.aabb, /* transform: */ transform, /* rawGid: */ NULL, /* tile: */ &tile, + /* tileRect: */ &tileRect)) + { + /* Iterate through each object associated with the tile */ + for (uint32_t j = 0; j < tile.objectGroup.objectsLength; j++) { + /* This object, the tile's collision information, has a relative position so this object must be */ + /* translated to the position of the tile as it would be drawn with the layer */ + TmxObject positionedObject = TranslateObject(tile.objectGroup.objects[j], tileRect.x, tileRect.y); + /* If this tile's object collides with the given object */ + if (CheckCollisionTMXObjects(positionedObject, object)) { + if (outputObject != NULL) + *outputObject = positionedObject; + return true; /* Found a collision. Exit now to save some CPU cycles. */ + } + } + } + } else if (layers[i].type == LAYER_TYPE_GROUP) { /* If the layer contains other layers */ + if (CheckCollisionTMXTileLayerObject(map, layers[i].layers, layers[i].layersLength, object, outputObject)) + return true; + } + } + + return false; +} + +/** + * Helper function for checking for collisions between an object group and an object of arbitrary type. + * + * @param group The object group whose 0+ objects will be checked for collisions. + * @param object A TMX <object> to be checked for collision. + * @param outputObject Output parameter assigned with the object in the object group that the given object collided + * with. NULL if not wanted. + * @return True if an object in the object group collides with the given object, or false if there is no collision. + */ +bool CheckCollisionTMXObjectGroupObject(TmxObjectGroup group, TmxObject object, TmxObject* outputObject) { + for (size_t i = 0; i < group.objectsLength; i++) { + if (CheckCollisionTMXObjects(group.objects[i], object)) { + if (outputObject != NULL) + *outputObject = group.objects[i]; + return true; + } + } + + return false; +} + +void TraceLogTMXTilesets(int logLevel, TmxOrientation orientation, TmxTileset* tilesets, uint32_t tilesetsLength, + int numSpaces) { + for (uint32_t i = 0; i < tilesetsLength; i++) { + TmxTileset tileset = tilesets[i]; + if (i == 0) + TraceLog(logLevel, "tilesets:"); + TraceLog(logLevel, " \"%s\":", tileset.name); + TraceLog(logLevel, " first GID: %u", tileset.firstGid); + TraceLog(logLevel, " last GID: %u", tileset.lastGid); + if (tileset.source != NULL) + TraceLog(logLevel, " source: \"%s\"", tileset.source); + if (tileset.classString[0] != '\0') + TraceLog(logLevel, " class: \"%s\"", tileset.classString); + TraceLog(logLevel, " tile width: %u", tileset.tileWidth); + TraceLog(logLevel, " tile height: %u", tileset.tileHeight); + if (tileset.spacing != 0) + TraceLog(logLevel, " spacing: %u", tileset.spacing); + if (tileset.margin != 0) + TraceLog(logLevel, " margin: %u", tileset.margin); + TraceLog(logLevel, " tile count: %u", tileset.tileCount); + TraceLog(logLevel, " columns: %u", tileset.columns); + if (tileset.tileOffsetX != 0) + TraceLog(logLevel, " tile offset X: %d", tileset.tileOffsetX); + if (tileset.tileOffsetY != 0) + TraceLog(logLevel, " tile offset Y: %d", tileset.tileOffsetY); + if ((orientation == ORIENTATION_ORTHOGONAL && tileset.objectAlignment != OBJECT_ALIGNMENT_BOTTOM_LEFT) || + (orientation == ORIENTATION_ISOMETRIC && tileset.objectAlignment != OBJECT_ALIGNMENT_BOTTOM)) { + switch (tileset.objectAlignment) { + case OBJECT_ALIGNMENT_UNSPECIFIED: TraceLog(logLevel, " object alignment: unspecified"); break; + case OBJECT_ALIGNMENT_TOP_LEFT: TraceLog(logLevel, " object alignment: top-left"); break; + case OBJECT_ALIGNMENT_TOP: TraceLog(logLevel, " object alignment: top"); break; + case OBJECT_ALIGNMENT_TOP_RIGHT: TraceLog(logLevel, " object alignment: top-right"); break; + case OBJECT_ALIGNMENT_LEFT: TraceLog(logLevel, " object alignment: left"); break; + case OBJECT_ALIGNMENT_CENTER: TraceLog(logLevel, " object alignment: center"); break; + case OBJECT_ALIGNMENT_RIGHT: TraceLog(logLevel, " object alignment: right"); break; + case OBJECT_ALIGNMENT_BOTTOM_LEFT: TraceLog(logLevel, " object alignment: bottom-left"); break; + case OBJECT_ALIGNMENT_BOTTOM: TraceLog(logLevel, " object alignment: bottom"); break; + case OBJECT_ALIGNMENT_BOTTOM_RIGHT: TraceLog(logLevel, " object alignment: bottom-right"); break; + } + } + if (tileset.hasImage) { + TraceLog(logLevel, " image:"); + TraceLog(logLevel, " source: \"%s\"", tileset.image.source); + if (tileset.image.hasTrans) + TraceLog(logLevel, " trans: 0x%08X", tileset.image.trans); + if (tileset.image.width != 0) + TraceLog(logLevel, " width: %u", tileset.image.width); + if (tileset.image.height != 0) + TraceLog(logLevel, " height: %u", tileset.image.height); + TraceLog(logLevel, " texture (ID): %u", tileset.image.texture.id); + } + for (uint32_t j = 0; j < tileset.tilesLength; j++) { + TmxTilesetTile tile = tileset.tiles[j]; + if (j == 0) + TraceLog(logLevel, " tiles:"); + TraceLog(logLevel, " ID: %u", tile.id); + if (tile.hasAnimation) { + for (uint32_t i = 0; i < tile.animation.framesLength; i++) { + if (i == 0) + TraceLog(logLevel, " frames:"); + TraceLog(logLevel, " (G)ID: %u", tile.animation.frames[i].gid); + if (tileset.classString[0] != '\0') + TraceLog(logLevel, " class: \"%s\"", tileset.classString); + TraceLog(logLevel, " duration: %f", tile.animation.frames[i].duration); + } + } + TraceLogTMXProperties(logLevel, tile.properties, tile.propertiesLength, 8); + if (tile.hasImage && tile.image.texture.id != tileset.image.texture.id) { + /* The 'x,' 'y,' 'width,' and 'height' attributes relate to the image so only log them if one exists */ + if (tile.x != 0) + TraceLog(logLevel, " x: %d", tile.x); + if (tile.y != 0) + TraceLog(logLevel, " y: %d", tile.y); + if (tile.width != tile.image.width) + TraceLog(logLevel, " width: %u", tile.width); + if (tile.height != tile.image.height) + TraceLog(logLevel, " height: %u", tile.height); + TraceLog(logLevel, " image:"); + TraceLog(logLevel, " source: \"%s\"", tile.image.source); + if (tile.image.hasTrans) + TraceLog(logLevel, " trans: 0x%08X", tile.image.trans); + if (tile.image.width != 0) + TraceLog(logLevel, " width: %u", tile.image.width); + if (tile.image.height != 0) + TraceLog(logLevel, " height: %u", tile.image.height); + TraceLog(logLevel, " texture (ID): %u", tile.image.texture.id); + } + if (tile.objectGroup.objectsLength > 0) { + if (tmxLogFlags & LOG_SKIP_OBJECTS) + TraceLog(logLevel, " skipping %u objects", tile.objectGroup.objectsLength); + else { + TraceLog(logLevel, " objects:"); + for (uint32_t k = 0; k < tile.objectGroup.objectsLength; k++) + TraceLogTMXObject(logLevel, tile.objectGroup.objects[k], numSpaces + 2); + } + } + } + TraceLogTMXProperties(logLevel, tileset.properties, tileset.propertiesLength, 2); + } +} + +void TraceLogTMXProperties(int logLevel, TmxProperty* properties, uint32_t propertiesLength, int numSpaces) { + char padding[16]; + memset(padding, '\0', 16); + StringCopyN(padding, " ", numSpaces); + + if (tmxLogFlags & LOG_SKIP_PROPERTIES) + TraceLog(logLevel, "%sskipped %u properties", padding, propertiesLength); + else { + for (uint32_t i = 0; i < propertiesLength; i++) { + TmxProperty property = properties[i]; + if (i == 0) + TraceLog(logLevel, "%sproperties:", padding); + switch (property.type) { + case PROPERTY_TYPE_STRING: + case PROPERTY_TYPE_FILE: + TraceLog(logLevel, "%s \"%s\": \"%s\"", padding, property.name, property.stringValue); + break; + case PROPERTY_TYPE_INT: + case PROPERTY_TYPE_OBJECT: + TraceLog(logLevel, "%s \"%s\": %d", padding, property.name, property.intValue); + break; + case PROPERTY_TYPE_FLOAT: + TraceLog(logLevel, "%s \"%s\": %f", padding, property.name, property.floatValue); + break; + case PROPERTY_TYPE_BOOL: + TraceLog(logLevel, "%s \"%s\": %s", padding, property.name, property.boolValue ? "true" : "false"); + break; + case PROPERTY_TYPE_COLOR: + TraceLog(logLevel, "%s \"%s\": 0x%08X", padding, property.name, property.colorValue); + break; + } + } + } +} + +void TraceLogTMXLayers(int logLevel, TmxLayer* layers, uint32_t layersLength, int numSpaces) { + char padding[16]; + memset(padding, '\0', 16); + StringCopyN(padding, " ", numSpaces); + + if (tmxLogFlags & LOG_SKIP_LAYERS) + TraceLog(logLevel, "%sskipped %u layers", padding, layersLength); + else { + uint32_t numTileLayers = 0, numObjectGroups = 0, numImageLayers = 0; + for (uint32_t i = 0; i < layersLength; i++) { + TmxLayer layer = layers[i]; + if (i == 0) + TraceLog(logLevel, "%slayers:", padding); + /* If, based on the layer type, this layer isn't one that should be skipped */ + if ((layer.type == LAYER_TYPE_GROUP) || + (layer.type == LAYER_TYPE_TILE_LAYER && !(tmxLogFlags & LOG_SKIP_TILE_LAYERS)) || + (layer.type == LAYER_TYPE_OBJECT_GROUP && !(tmxLogFlags & LOG_SKIP_OBJECT_GROUPS)) || + (layer.type == LAYER_TYPE_IMAGE_LAYER && !(tmxLogFlags & LOG_SKIP_IMAGE_LAYERS))) { + /* Log the attributes of this layer common to all layers */ + TraceLog(logLevel, "%s \"%s\":", padding, layer.name); + switch (layer.type) { + case LAYER_TYPE_TILE_LAYER: TraceLog(logLevel, "%s type: tile layer", padding); break; + case LAYER_TYPE_OBJECT_GROUP: TraceLog(logLevel, "%s type: object layer", padding); break; + case LAYER_TYPE_IMAGE_LAYER: TraceLog(logLevel, "%s type: image layer", padding); break; + case LAYER_TYPE_GROUP: TraceLog(logLevel, "%s type: group", padding); break; + } + if (layer.id > 0) + TraceLog(logLevel, "%s ID: %u", padding, layer.id); + if (layer.visible == false) + TraceLog(logLevel, "%s visible: false", padding); + if (layer.classString[0] != '\0') + TraceLog(logLevel, "%s class: \"%s\"", padding, layer.classString); + if (layer.offsetX != 0) + TraceLog(logLevel, "%s offset X: %d", padding, layer.offsetX); + if (layer.offsetY != 0) + TraceLog(logLevel, "%s offset Y: %d", padding, layer.offsetY); + if (layer.parallaxX != 1.0) + TraceLog(logLevel, "%s parallax X: %f", padding, layer.parallaxX); + if (layer.parallaxY != 1.0) + TraceLog(logLevel, "%s parallax Y: %f", padding, layer.parallaxY); + if (layer.opacity != 1.0) + TraceLog(logLevel, "%s opacity: %f", padding, layer.opacity); + if (layer.hasTintColor) + TraceLog(logLevel, "%s tint color: 0x%08X", padding, layer.tintColor); + TraceLogTMXProperties(logLevel, layer.properties, layer.propertiesLength, numSpaces + 4); + } + + /* Log attributes specific to the layer's type (tile layer, object layer, image layer, or group) */ + switch (layer.type) { + case LAYER_TYPE_TILE_LAYER: + numTileLayers += 1; + if (tmxLogFlags & LOG_SKIP_TILE_LAYERS) + continue; + if (layer.exact.tileLayer.width != 0) + TraceLog(logLevel, "%s width: %u", padding, layer.exact.tileLayer.width); + if (layer.exact.tileLayer.height != 0) + TraceLog(logLevel, "%s height: %u", padding, layer.exact.tileLayer.height); + if (tmxLogFlags & LOG_SKIP_TILES) + TraceLog(logLevel, "%s skipping %u tiles", padding, layer.exact.tileLayer.tilesLength); + else { + for (uint32_t j = 0; j < layer.exact.tileLayer.tilesLength; j++) { + if (j == 0) + TraceLog(logLevel, "%s tiles:", padding); + TraceLog(logLevel, "%s GID: %u", padding, layer.exact.tileLayer.tiles[j]); + } + } + break; + case LAYER_TYPE_OBJECT_GROUP: + numObjectGroups += 1; + if (tmxLogFlags & LOG_SKIP_OBJECT_GROUPS) + continue; + if (layer.exact.objectGroup.hasColor) + TraceLog(logLevel, "%s color: 0x%08X", padding, layer.exact.objectGroup.color); + /* if (layer.exact.objectGroup.width != 0) + TraceLog(logLevel, "%s width: %u", padding, layer.exact.objectGroup.width); */ + /* if (layer.exact.objectGroup.height != 0) + TraceLog(logLevel, "%s height: %u", padding, layer.exact.objectGroup.height); */ + if (layer.exact.objectGroup.drawOrder != OBJECT_GROUP_DRAW_ORDER_TOP_DOWN) { + switch (layer.exact.objectGroup.drawOrder) { + case OBJECT_GROUP_DRAW_ORDER_TOP_DOWN: + default: + TraceLog(logLevel, "%s draw order: top-down", padding); + break; + case OBJECT_GROUP_DRAW_ORDER_INDEX: + TraceLog(logLevel, "%s draw order: index", padding); + break; + } + } + if (tmxLogFlags & LOG_SKIP_OBJECTS) + TraceLog(logLevel, "%s skipping %u objects", padding, layer.exact.objectGroup.objectsLength); + else { + for (uint32_t j = 0; j < layer.exact.objectGroup.objectsLength; j++) { + TmxObject object = layer.exact.objectGroup.objects[j]; + if (j == 0) + TraceLog(logLevel, "%s objects:", padding); + TraceLogTMXObject(logLevel, object, numSpaces); + } + } + break; + case LAYER_TYPE_IMAGE_LAYER: + numImageLayers += 1; + if (tmxLogFlags & LOG_SKIP_IMAGE_LAYERS) + continue; + if (layer.exact.imageLayer.repeatX) + TraceLog(logLevel, "%s repeat X: true", padding); + if (layer.exact.imageLayer.repeatY) + TraceLog(logLevel, "%s repeat Y: true", padding); + if (layer.exact.imageLayer.hasImage) { + TraceLog(logLevel, "%s image:", padding); + TraceLog(logLevel, "%s source: \"%s\"", padding, layer.exact.imageLayer.image.source); + if (layer.exact.imageLayer.image.hasTrans) + TraceLog(logLevel, "%s trans: 0x%08X", padding, layer.exact.imageLayer.image.trans); + if (layer.exact.imageLayer.image.width != 0) + TraceLog(logLevel, "%s width: %u", padding, layer.exact.imageLayer.image.width); + if (layer.exact.imageLayer.image.height != 0) + TraceLog(logLevel, "%s height: %u", padding, layer.exact.imageLayer.image.height); + TraceLog(logLevel, "%s texture (ID): %u", padding, layer.exact.imageLayer.image.texture.id); + } + break; + case LAYER_TYPE_GROUP: + TraceLogTMXLayers(logLevel, layer.layers, layer.layersLength, numSpaces + 4); + break; + } + } + if (tmxLogFlags & LOG_SKIP_TILE_LAYERS && numTileLayers > 0) + TraceLog(logLevel, "%s skipped %u tile layers", padding, numTileLayers); + if (tmxLogFlags & LOG_SKIP_OBJECT_GROUPS && numObjectGroups > 0) + TraceLog(logLevel, "%s skipped %u object layers", padding, numObjectGroups); + if (tmxLogFlags & LOG_SKIP_IMAGE_LAYERS && numImageLayers > 0) + TraceLog(logLevel, "%s skipped %u image layers", padding, numImageLayers); + } +} + +void TraceLogTMXObject(int logLevel, TmxObject object, int numSpaces) { + char padding[16]; + memset(padding, '\0', 16); + StringCopyN(padding, " ", numSpaces); + + TraceLog(logLevel, "%s ID: %u", padding, object.id); + TraceLog(logLevel, "%s name: \"%s\"", padding, object.name); + switch (object.type) { + case OBJECT_TYPE_RECTANGLE: TraceLog(logLevel, "%s type: quad", padding); break; + case OBJECT_TYPE_ELLIPSE: TraceLog(logLevel, "%s type: ellipse", padding); break; + case OBJECT_TYPE_POINT: TraceLog(logLevel, "%s type: point", padding); break; + case OBJECT_TYPE_POLYGON: TraceLog(logLevel, "%s type: polygon", padding); break; + case OBJECT_TYPE_POLYLINE: TraceLog(logLevel, "%s type: polyline", padding); break; + case OBJECT_TYPE_TEXT: TraceLog(logLevel, "%s type: text", padding); break; + case OBJECT_TYPE_TILE: TraceLog(logLevel, "%s type: tile", padding); break; + } + if (object.typeString[0] != '\0') + TraceLog(logLevel, "%s type: \"%s\"", padding, object.typeString); + if (object.x != 0.0) + TraceLog(logLevel, "%s x: %f", padding, object.x); + if (object.y != 0.0) + TraceLog(logLevel, "%s y: %f", padding, object.y); + if (object.width != 0.0) + TraceLog(logLevel, "%s width: %f", padding, object.width); + if (object.height != 0.0) + TraceLog(logLevel, "%s height: %f", padding, object.height); + if (object.rotation != 0.0) + TraceLog(logLevel, "%s rotation: %f", padding, object.rotation); + if (object.gid > 0) + TraceLog(logLevel, "%s GID: %u", padding, object.gid); + if (!object.visible) + TraceLog(logLevel, "%s visible: false", padding); + if (object.templateString != NULL) + TraceLog(logLevel, "%s template: \"%s\"", padding, object.templateString); + for (uint32_t k = 0; k < object.pointsLength; k++) { + if (k == 0) + TraceLog(logLevel, "%s points:", padding); + TraceLog(logLevel, "%s [%f, %f]", padding, object.points[k].x, object.points[k].y); + } + TraceLogTMXProperties(logLevel, object.properties, object.propertiesLength, numSpaces + 8); + if (object.text != NULL) { + TraceLog(logLevel, "%s font family: \"%s\"", padding, object.text->fontFamily); + TraceLog(logLevel, "%s pixel size: %u", padding, object.text->pixelSize); + if (object.text->wrap) + TraceLog(logLevel, "%s wrap: true", padding); + TraceLog(logLevel, "%s color: 0x%08X", padding, object.text->color); + if (object.text->bold) + TraceLog(logLevel, "%s bold: true", padding); + if (object.text->italic) + TraceLog(logLevel, "%s italic: true", padding); + if (object.text->underline) + TraceLog(logLevel, "%s underline: true", padding); + if (object.text->strikeOut) + TraceLog(logLevel, "%s strike out: true", padding); + if (object.text->kerning) + TraceLog(logLevel, "%s kerning: true", padding); + switch (object.text->halign) { + case HORIZONTAL_ALIGNMENT_LEFT: + TraceLog(logLevel, "%s horizontal align: left", padding); + break; + case HORIZONTAL_ALIGNMENT_CENTER: + TraceLog(logLevel, "%s horizontal align: center", padding); + break; + case HORIZONTAL_ALIGNMENT_RIGHT: + TraceLog(logLevel, "%s horizontal align: right", padding); + break; + case HORIZONTAL_ALIGNMENT_JUSTIFY: + TraceLog(logLevel, "%s horizontal align: justify", padding); + break; + } + switch (object.text->valign) { + case VERTICAL_ALIGNMENT_TOP: + TraceLog(logLevel, "%s vertical align: top", padding); + break; + case VERTICAL_ALIGNMENT_CENTER: + TraceLog(logLevel, "%s vertical align: center", padding); + break; + case VERTICAL_ALIGNMENT_BOTTOM: + TraceLog(logLevel, "%s vertical align: bottom", padding); + break; + } + if (object.text->content[0] != '\0') + TraceLog(logLevel, "%s content: \"%s\"", padding, object.text->content); + } +} + +TmxProperty* AddProperty(RaytmxState* raytmxState) { + RaytmxPropertyNode* node = (RaytmxPropertyNode*)MemAllocZero(sizeof(RaytmxPropertyNode)); + + if (raytmxState->propertiesRoot == NULL) + raytmxState->propertiesRoot = node; + else + raytmxState->propertiesTail->next = node; + raytmxState->propertiesTail = node; + raytmxState->propertiesLength += 1; + + return &node->property; +} + +void AddTileLayerTile(RaytmxState* raytmxState, uint32_t gid) { + RaytmxTileLayerTileNode* node = (RaytmxTileLayerTileNode*)MemAllocZero(sizeof(RaytmxTileLayerTileNode)); + node->gid = gid; + + if (raytmxState->layerTilesRoot == NULL) + raytmxState->layerTilesRoot = node; + else + raytmxState->layerTilesTail->next = node; + raytmxState->layerTilesTail = node; + raytmxState->layerTilesLength += 1; +} + +TmxTileset* AddTileset(RaytmxState* raytmxState) { + RaytmxTilesetNode* node = (RaytmxTilesetNode*)MemAllocZero(sizeof(RaytmxTilesetNode)); + + if (raytmxState->tilesetsRoot == NULL) + raytmxState->tilesetsRoot = node; + else + raytmxState->tilesetsTail->next = node; + raytmxState->tilesetsTail = node; + raytmxState->tilesetsLength += 1; + + return &node->tileset; +} + +TmxTilesetTile* AddTilesetTile(RaytmxState* raytmxState) { + RaytmxTilesetTileNode* node = (RaytmxTilesetTileNode*)MemAllocZero(sizeof(RaytmxTilesetTileNode)); + + if (raytmxState->tilesetTilesRoot == NULL) + raytmxState->tilesetTilesRoot = node; + else + raytmxState->tilesetTilesTail->next = node; + raytmxState->tilesetTilesTail = node; + raytmxState->tilesetTilesLength += 1; + + return &node->tile; +} + +TmxAnimationFrame* AddAnimationFrame(RaytmxState* raytmxState) { + RaytmxAnimationFrameNode* node = (RaytmxAnimationFrameNode*)MemAllocZero(sizeof(RaytmxAnimationFrameNode)); + + if (raytmxState->animationFramesRoot == NULL) + raytmxState->animationFramesRoot = node; + else + raytmxState->animationFramesTail->next = node; + raytmxState->animationFramesTail = node; + raytmxState->animationFramesLength += 1; + + return &node->frame; +} + +TmxLayer* AddGenericLayer(RaytmxState* raytmxState, bool isGroup) { + RaytmxLayerNode* node = (RaytmxLayerNode*)MemAllocZero(sizeof(RaytmxLayerNode)); + /* There are some non-zero default values for several layer attributes: */ + node->layer.opacity = 1.0; + node->layer.visible = true; + node->layer.parallaxX = 1.0; + node->layer.parallaxY = 1.0; + + if (raytmxState->groupNode != NULL) { /* If the layer is being appended to a <group> */ + node->parent = raytmxState->groupNode; + if (raytmxState->groupNode->childrenRoot == NULL) /* If the layer is the first in the group */ + raytmxState->groupNode->childrenRoot = node; + else + raytmxState->groupNode->childrenTail->next = node; + raytmxState->groupNode->childrenTail = node; + raytmxState->groupNode->childrenLength += 1; + } else { /* If there is no <group> to append to, meaning the layer belongs to the top-level <map> */ + if (raytmxState->layersRoot == NULL) /* If the layer is the first in the map */ + raytmxState->layersRoot = node; + else + raytmxState->layersTail->next = node; + raytmxState->layersTail = node; + raytmxState->layersLength += 1; + } + + if (isGroup) + raytmxState->groupNode = node; + + return &node->layer; +} + +TmxObject* AddObject(RaytmxState* raytmxState) { + RaytmxObjectNode* node = (RaytmxObjectNode*)MemAllocZero(sizeof(RaytmxObjectNode)); + /* <object> elements have one non-zero default value: */ + node->object.visible = true; + + if (raytmxState->objectsRoot == NULL) + raytmxState->objectsRoot = node; + else + raytmxState->objectsTail->next = node; + raytmxState->objectsTail = node; + raytmxState->objectsLength += 1; + + return &node->object; +} + +void AppendLayerTo(TmxMap* map, RaytmxLayerNode* groupNode, RaytmxLayerNode* layersRoot, uint32_t layersLength) { + if (map == NULL || layersRoot == NULL || layersLength == 0) + return; + + TmxLayer* groupLayer = NULL; + if (groupNode != NULL) + groupLayer = &(groupNode->layer); + + /* Allocate the array and zerioze every index as initialization */ + TmxLayer* layers = (TmxLayer*)MemAllocZero(sizeof(TmxLayer) * layersLength); + /* Copy the TmxLayers into the array */ + RaytmxLayerNode* layersIterator = layersRoot; + for (uint32_t i = 0; layersIterator != NULL; i++) { + if (layersIterator->childrenRoot != NULL) + AppendLayerTo(map, layersIterator, layersIterator->childrenRoot, layersIterator->childrenLength); + layers[i] = layersIterator->layer; + layersIterator = layersIterator->next; + } + + if (groupLayer != NULL) { /* If the list of layers is being appended to a group */ + groupLayer->layers = layers; + groupLayer->layersLength = layersLength; + } else { /* If the list of layers is being appended to the top-level map */ + map->layers = layers; + map->layersLength = layersLength; + } +} + +RaytmxCachedTextureNode* LoadCachedTexture(RaytmxState* raytmxState, const char* fileName) { + if (raytmxState == NULL || fileName == NULL) + return NULL; + + /* First try to find an already-loaded texture identified by the file name */ + RaytmxCachedTextureNode* cachedTextureNode = raytmxState->texturesRoot; + while (cachedTextureNode != NULL) { + /* If the file name associated with the node matches the given file name */ + if (strcmp(cachedTextureNode->fileName, fileName) == 0) + return cachedTextureNode; + cachedTextureNode = cachedTextureNode->next; + } + + /* Try to load the texture */ + char* fullPath = JoinPath(raytmxState->documentDirectory, fileName); + Texture2D texture = loadTextureOverride ? loadTextureOverride(fullPath) : LoadTexture(fullPath); + if (texture.id == 0) { /* If loading the texture failed */ + TraceLog(LOG_ERROR, "RAYTMX: Unable to load texture \"%s\"", fullPath); + return NULL; + } + + /* Create a new node in the list of known textures */ + cachedTextureNode = (RaytmxCachedTextureNode*)MemAllocZero(sizeof(RaytmxCachedTextureNode)); + cachedTextureNode->fileName = (char*)MemAllocZero((unsigned int)strlen(fileName) + 1); + StringCopy(cachedTextureNode->fileName, fileName); + cachedTextureNode->texture = texture; + + /* Add to the cache */ + if (raytmxState->texturesRoot == NULL) + raytmxState->texturesRoot = cachedTextureNode; + else { + RaytmxCachedTextureNode* cachedTextureIterator = raytmxState->texturesRoot; + while (cachedTextureIterator->next != NULL) + cachedTextureIterator = cachedTextureIterator->next; + cachedTextureIterator->next = cachedTextureNode; + } + + return cachedTextureNode; +} + +RaytmxCachedTemplateNode* LoadCachedTemplate(RaytmxState* raytmxState, const char* fileName) { + if (raytmxState == NULL || fileName == NULL) + return NULL; + + /* First try to find an already-loaded template identified by the file name */ + RaytmxCachedTemplateNode* cachedTemplateNode = raytmxState->templatesRoot; + while (cachedTemplateNode != NULL) { + /* If the file name associated with the node matches the given file name */ + if (strcmp(cachedTemplateNode->fileName, fileName) == 0) + return cachedTemplateNode; + cachedTemplateNode = cachedTemplateNode->next; + } + + /* Load the template from the external TX file */ + char* fullPath = JoinPath(raytmxState->documentDirectory, fileName); + RaytmxObjectTemplate objectTemplate = LoadTX(fullPath); + if (!objectTemplate.isSuccess) { /* If loading the template failed */ + TraceLog(LOG_ERROR, "RAYTMX: Unable to load template \"%s\"", fullPath); + return NULL; + } + + /* Create a new node in the list of known templates */ + cachedTemplateNode = (RaytmxCachedTemplateNode*)MemAllocZero(sizeof(RaytmxCachedTemplateNode)); + cachedTemplateNode->fileName = (char*)MemAllocZero((unsigned int)strlen(fileName) + 1); + StringCopy(cachedTemplateNode->fileName, fileName); + cachedTemplateNode->objectTemplate = objectTemplate; + + if (objectTemplate.hasTileset) { /* If the template contains a tileset in addition to an object */ + /* In cases where the template's object references a tile (i.e. its 'gid' attribute is set), the template */ + /* will have at most one tileset. Search the state object's list of tilesets and add this one if it's new. */ + RaytmxTilesetNode* tilesetsIterator = raytmxState->tilesetsRoot; + bool isNew = true; + while (tilesetsIterator != NULL) { + /* If the existing tileset has a name, the template's tileset has a name, and they match OR if the */ + /* existing tileset has a source, template's tileset has a source, and they match */ + /* TODO: The comparison of sources without respect to directory should be reviewed as it could result in */ + /* false negatives and duplicate tileset instances */ + if ((tilesetsIterator->tileset.name != NULL && objectTemplate.tileset.name != NULL && + strcmp(tilesetsIterator->tileset.name, objectTemplate.tileset.name) == 0) || + (tilesetsIterator->tileset.source != NULL && objectTemplate.tileset.source != NULL && + strcmp(tilesetsIterator->tileset.source, objectTemplate.tileset.source) == 0)) { + isNew = false; + break; + } + } + if (isNew) { + TmxTileset* tileset = AddTileset(raytmxState); + *tileset = objectTemplate.tileset; + } + } + + /* Add to the cache */ + if (raytmxState->templatesRoot == NULL) + raytmxState->templatesRoot = cachedTemplateNode; + else { + RaytmxCachedTemplateNode* cachedTemplateIterator = raytmxState->templatesRoot; + while (cachedTemplateIterator->next != NULL) + cachedTemplateIterator = cachedTemplateIterator->next; + cachedTemplateIterator->next = cachedTemplateNode; + } + + return cachedTemplateNode; +} + +Color GetColorFromHexString(const char* hex) { + Color color = BLACK; /* #define'd by raylib as { 0, 0, 0, 255 } */ + + size_t length = strlen(hex); + if (length < 6) /* If the hex string is too short to contain at least R, G, and B components */ + return color; + + /* Hex strings are in the form #AARRGGBB or #RRGGBB meaning alpha is optional. To avoid any special logic for the */ + /* two cases where alpha is or isn't given, parsing will just be done backwards. Here, 'hex' is used as an */ + /* iterator so it will begin at the end of the string. For example, for "#789abc" this begins at 'c'. */ + hex += length - 1; + + char component[] = "\0\0\0"; /* Used to hold the two-digit component (e.g. "55" or "ff") */ + for (size_t i = 0; i < length / 2; i++) { /* Iterate three or four times with four meaning alpha was included */ + component[1] = hex[0]; /* Store the char 'hex' points to. For "#789abc" this is 'c', 'a', or '8'. */ + hex--; /* Point to the previous char in the string. For "#789abc" this now points to 'b', '9' or '7'. */ + component[0] = hex[0]; /* Store the other char of the current component */ + hex--; /* Iterate backwards again in preparation for the next component */ + /* For "#789abc" the 'component' array is now "bc", "9a", or "78" */ + switch (i) { + case 0: color.b = (unsigned char)strtoul(component, NULL, 16); break; /* e.g. "ff" -> 255 for blue */ + case 1: color.g = (unsigned char)strtoul(component, NULL, 16); break; /* ...for green */ + case 2: color.r = (unsigned char)strtoul(component, NULL, 16); break; /* ...for red */ + case 3: color.a = (unsigned char)strtoul(component, NULL, 16); break; /* ...for alpha, maybe */ + } + } + + return color; +} + +uint32_t GetGid(uint32_t rawGid, bool* isFlippedHorizontally, bool* isFlippedVertically, bool* isFlippedDiagonally, + bool* isRotatedHexagonal120) { + /* If the output parameters can be written to, output the status of the flip flags */ + if (isFlippedHorizontally != NULL) + *isFlippedHorizontally = rawGid & FLIP_FLAG_HORIZONTAL; + if (isFlippedVertically != NULL) + *isFlippedVertically = rawGid & FLIP_FLAG_VERTICAL; + if (isFlippedDiagonally != NULL) + *isFlippedDiagonally = rawGid & FLIP_FLAG_DIAGONAL; + if (isRotatedHexagonal120 != NULL) + *isRotatedHexagonal120 = rawGid & FLIP_FLAG_ROTATE_120; + + /* Return the integer with flip flags removed */ + return rawGid & ~(FLIP_FLAG_HORIZONTAL | FLIP_FLAG_VERTICAL | FLIP_FLAG_DIAGONAL | FLIP_FLAG_ROTATE_120); +} + +void* MemAllocZero(unsigned int size) { + void* buffer = MemAlloc(size); /* Reserve 'size' bytes of memory */ + memset(buffer, 0, size); /* Initialize any values to zero, NULL, false, or an equivalent enum value */ + return buffer; +} + +/* "Get directory for a given filePath" */ +/* raylib's GetDirectoryPath() doesn't work as described so this is used in its place */ +char* GetDirectoryPath2(const char* filePath) { + static char directoryPath[260]; /* Max path length on Windows, the bottleneck, is 260 characters */ + memset(directoryPath, '\0', 260); + size_t length = strlen(filePath); + /* Paths beginning with a Windows drive letter (C:\, D:\, etc.) or beginning with a slash are absolute paths */ + if (length >= 2 && (filePath[1] == ':' || filePath[0] == '\\' || filePath[0] == '/')) { /* If absolute */ + if (IsPathFile(filePath)) /* If filePath points to a file, and we already know it's absolute */ + StringCopy(directoryPath, filePath); + else { /* If filePath points to a directory, and we already know it's absolute */ + StringCopy(directoryPath, filePath); + return directoryPath; + } + } else /* If filePath is relative */ + StringCopy(directoryPath, JoinPath(GetWorkingDirectory(), filePath)); + + /* The goal is to return part of filePath, up to the last slash */ + length = strlen(directoryPath); + char* iterator = directoryPath + length - 1; + /* Iterate backwards until a slash is found */ + while (iterator != directoryPath && *iterator != '\0' && *iterator != '\\' && *iterator != '/') + iterator -= 1; + *(iterator + 1) = '\0'; /* Place a null terminator after the slash to effectively end the string there */ + return directoryPath; +} + +char* JoinPath(const char* prefix, const char* suffix) { + static char joinedPath[260]; /* Max path length on Windows, the bottleneck, is 260 characters */ + memset(joinedPath, '\0', 260); + StringCopy(joinedPath, prefix); + size_t prefixLength = strlen(prefix); + if ((prefixLength >= 1) && (joinedPath[prefixLength - 1] != '/') && (joinedPath[prefixLength - 1] != '\\')) +#ifdef _WIN32 + joinedPath[prefixLength] = '\\'; /* Append the path with a '\\' separator */ +#else + joinedPath[prefixLength] = '/'; /* Append the path with a '/' separator */ +#endif + const char* suffixStart = suffix; + size_t suffixLength = strlen(suffix); + if (suffixLength >= 2 && suffix[0] == '.' && (suffix[1] == '/' || suffix[1] == '\\')) + suffixStart += 2; /* Skip over the "this directory" part (e.g. "./a.tsx" -> "a.tsx") */ + /* Note: ".." is kept in the joined path intentionally although it is possible to exceed 260 characters. TODO? */ + StringConcatenate(joinedPath, suffixStart); + return joinedPath; +} + +void StringCopy(char* destination, const char* source) { +#if (!defined _MSC_VER || defined _CRT_SECURE_NO_WARNINGS) + /* This is for build environments where "[M]icro[S]oft [C]ompiler [VER]sion" is not defined, meaning the compiler */ + /* is one other than MSVC, or where MSVC is being used but the security deprecation warning has been disabled. */ + /* MSVC does not consider strcpy() to be secure because it's somewhat unbounded. In practice, it's fine. */ + strcpy(destination, source); +#else + /* This keeps MSVC happy by providing the supposed size of the destination buffer. However, these buffers are */ + /* dynamically allocated so there's no quick way to get the actual size. The best solution is to use strlen() */ + /* but this makes the function O(2n) vs. the alternative's O(n). */ + strcpy_s(destination, strlen(source) + 1, source); /* + 1 for the terminator */ +#endif +} + +void StringCopyN(char* destination, const char* source, size_t number) { +#if (!defined _MSC_VER || defined _CRT_SECURE_NO_WARNINGS) + strncpy(destination, source, number); +#else + strncpy_s(destination, number + 1, source, number); +#endif +} + +void StringConcatenate(char* destination, const char* source) { +#if (!defined _MSC_VER || defined _CRT_SECURE_NO_WARNINGS) + strcat(destination, source); +#else + strcat_s(destination, strlen(destination) + strlen(source) + 1, source); +#endif +} + +#endif /* RAYTMX_IMPLEMENTATION */ + +#endif /* RAYTMX_H */ diff --git a/include/rlgl.h b/include/rlgl.h new file mode 100644 index 0000000..756656e --- /dev/null +++ b/include/rlgl.h @@ -0,0 +1,5262 @@ +/********************************************************************************************** +* +* rlgl v5.0 - A multi-OpenGL abstraction layer with an immediate-mode style API +* +* DESCRIPTION: +* An abstraction layer for multiple OpenGL versions (1.1, 2.1, 3.3 Core, 4.3 Core, ES 2.0) +* that provides a pseudo-OpenGL 1.1 immediate-mode style API (rlVertex, rlTranslate, rlRotate...) +* +* ADDITIONAL NOTES: +* When choosing an OpenGL backend different than OpenGL 1.1, some internal buffer are +* initialized on rlglInit() to accumulate vertex data +* +* When an internal state change is required all the stored vertex data is renderer in batch, +* additionally, rlDrawRenderBatchActive() could be called to force flushing of the batch +* +* Some resources are also loaded for convenience, here the complete list: +* - Default batch (RLGL.defaultBatch): RenderBatch system to accumulate vertex data +* - Default texture (RLGL.defaultTextureId): 1x1 white pixel R8G8B8A8 +* - Default shader (RLGL.State.defaultShaderId, RLGL.State.defaultShaderLocs) +* +* Internal buffer (and resources) must be manually unloaded calling rlglClose() +* +* CONFIGURATION: +* #define GRAPHICS_API_OPENGL_11 +* #define GRAPHICS_API_OPENGL_21 +* #define GRAPHICS_API_OPENGL_33 +* #define GRAPHICS_API_OPENGL_43 +* #define GRAPHICS_API_OPENGL_ES2 +* #define GRAPHICS_API_OPENGL_ES3 +* Use selected OpenGL graphics backend, should be supported by platform +* Those preprocessor defines are only used on rlgl module, if OpenGL version is +* required by any other module, use rlGetVersion() to check it +* +* #define RLGL_IMPLEMENTATION +* Generates the implementation of the library into the included file +* If not defined, the library is in header only mode and can be included in other headers +* or source files without problems. But only ONE file should hold the implementation +* +* #define RLGL_RENDER_TEXTURES_HINT +* Enable framebuffer objects (fbo) support (enabled by default) +* Some GPUs could not support them despite the OpenGL version +* +* #define RLGL_SHOW_GL_DETAILS_INFO +* Show OpenGL extensions and capabilities detailed logs on init +* +* #define RLGL_ENABLE_OPENGL_DEBUG_CONTEXT +* Enable debug context (only available on OpenGL 4.3) +* +* rlgl capabilities could be customized just defining some internal +* values before library inclusion (default values listed): +* +* #define RL_DEFAULT_BATCH_BUFFER_ELEMENTS 8192 // Default internal render batch elements limits +* #define RL_DEFAULT_BATCH_BUFFERS 1 // Default number of batch buffers (multi-buffering) +* #define RL_DEFAULT_BATCH_DRAWCALLS 256 // Default number of batch draw calls (by state changes: mode, texture) +* #define RL_DEFAULT_BATCH_MAX_TEXTURE_UNITS 4 // Maximum number of textures units that can be activated on batch drawing (SetShaderValueTexture()) +* +* #define RL_MAX_MATRIX_STACK_SIZE 32 // Maximum size of internal Matrix stack +* #define RL_MAX_SHADER_LOCATIONS 32 // Maximum number of shader locations supported +* #define RL_CULL_DISTANCE_NEAR 0.01 // Default projection matrix near cull distance +* #define RL_CULL_DISTANCE_FAR 1000.0 // Default projection matrix far cull distance +* +* When loading a shader, the following vertex attributes and uniform +* location names are tried to be set automatically: +* +* #define RL_DEFAULT_SHADER_ATTRIB_NAME_POSITION "vertexPosition" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION +* #define RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD "vertexTexCoord" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD +* #define RL_DEFAULT_SHADER_ATTRIB_NAME_NORMAL "vertexNormal" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL +* #define RL_DEFAULT_SHADER_ATTRIB_NAME_COLOR "vertexColor" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_COLOR +* #define RL_DEFAULT_SHADER_ATTRIB_NAME_TANGENT "vertexTangent" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_TANGENT +* #define RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD2 "vertexTexCoord2" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD2 +* #define RL_DEFAULT_SHADER_ATTRIB_NAME_BONEIDS "vertexBoneIds" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEIDS +* #define RL_DEFAULT_SHADER_ATTRIB_NAME_BONEWEIGHTS "vertexBoneWeights" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEWEIGHTS +* #define RL_DEFAULT_SHADER_UNIFORM_NAME_MVP "mvp" // model-view-projection matrix +* #define RL_DEFAULT_SHADER_UNIFORM_NAME_VIEW "matView" // view matrix +* #define RL_DEFAULT_SHADER_UNIFORM_NAME_PROJECTION "matProjection" // projection matrix +* #define RL_DEFAULT_SHADER_UNIFORM_NAME_MODEL "matModel" // model matrix +* #define RL_DEFAULT_SHADER_UNIFORM_NAME_NORMAL "matNormal" // normal matrix (transpose(inverse(matModelView))) +* #define RL_DEFAULT_SHADER_UNIFORM_NAME_COLOR "colDiffuse" // color diffuse (base tint color, multiplied by texture color) +* #define RL_DEFAULT_SHADER_UNIFORM_NAME_BONE_MATRICES "boneMatrices" // bone matrices +* #define RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE0 "texture0" // texture0 (texture slot active 0) +* #define RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE1 "texture1" // texture1 (texture slot active 1) +* #define RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE2 "texture2" // texture2 (texture slot active 2) +* +* DEPENDENCIES: +* - OpenGL libraries (depending on platform and OpenGL version selected) +* - GLAD OpenGL extensions loading library (only for OpenGL 3.3 Core, 4.3 Core) +* +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2014-2024 Ramon Santamaria (@raysan5) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#ifndef RLGL_H +#define RLGL_H + +#define RLGL_VERSION "5.0" + +// Function specifiers in case library is build/used as a shared library +// NOTE: Microsoft specifiers to tell compiler that symbols are imported/exported from a .dll +// NOTE: visibility(default) attribute makes symbols "visible" when compiled with -fvisibility=hidden +#if defined(_WIN32) && defined(BUILD_LIBTYPE_SHARED) + #define RLAPI __declspec(dllexport) // We are building the library as a Win32 shared library (.dll) +#elif defined(BUILD_LIBTYPE_SHARED) + #define RLAPI __attribute__((visibility("default"))) // We are building the library as a Unix shared library (.so/.dylib) +#elif defined(_WIN32) && defined(USE_LIBTYPE_SHARED) + #define RLAPI __declspec(dllimport) // We are using the library as a Win32 shared library (.dll) +#endif + +// Function specifiers definition +#ifndef RLAPI + #define RLAPI // Functions defined as 'extern' by default (implicit specifiers) +#endif + +// Support TRACELOG macros +#ifndef TRACELOG + #define TRACELOG(level, ...) (void)0 + #define TRACELOGD(...) (void)0 +#endif + +// Allow custom memory allocators +#ifndef RL_MALLOC + #define RL_MALLOC(sz) malloc(sz) +#endif +#ifndef RL_CALLOC + #define RL_CALLOC(n,sz) calloc(n,sz) +#endif +#ifndef RL_REALLOC + #define RL_REALLOC(n,sz) realloc(n,sz) +#endif +#ifndef RL_FREE + #define RL_FREE(p) free(p) +#endif + +// Security check in case no GRAPHICS_API_OPENGL_* defined +#if !defined(GRAPHICS_API_OPENGL_11) && \ + !defined(GRAPHICS_API_OPENGL_21) && \ + !defined(GRAPHICS_API_OPENGL_33) && \ + !defined(GRAPHICS_API_OPENGL_43) && \ + !defined(GRAPHICS_API_OPENGL_ES2) && \ + !defined(GRAPHICS_API_OPENGL_ES3) + #define GRAPHICS_API_OPENGL_33 +#endif + +// Security check in case multiple GRAPHICS_API_OPENGL_* defined +#if defined(GRAPHICS_API_OPENGL_11) + #if defined(GRAPHICS_API_OPENGL_21) + #undef GRAPHICS_API_OPENGL_21 + #endif + #if defined(GRAPHICS_API_OPENGL_33) + #undef GRAPHICS_API_OPENGL_33 + #endif + #if defined(GRAPHICS_API_OPENGL_43) + #undef GRAPHICS_API_OPENGL_43 + #endif + #if defined(GRAPHICS_API_OPENGL_ES2) + #undef GRAPHICS_API_OPENGL_ES2 + #endif +#endif + +// OpenGL 2.1 uses most of OpenGL 3.3 Core functionality +// WARNING: Specific parts are checked with #if defines +#if defined(GRAPHICS_API_OPENGL_21) + #define GRAPHICS_API_OPENGL_33 +#endif + +// OpenGL 4.3 uses OpenGL 3.3 Core functionality +#if defined(GRAPHICS_API_OPENGL_43) + #define GRAPHICS_API_OPENGL_33 +#endif + +// OpenGL ES 3.0 uses OpenGL ES 2.0 functionality (and more) +#if defined(GRAPHICS_API_OPENGL_ES3) + #define GRAPHICS_API_OPENGL_ES2 +#endif + +// Support framebuffer objects by default +// NOTE: Some driver implementation do not support it, despite they should +#define RLGL_RENDER_TEXTURES_HINT + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- + +// Default internal render batch elements limits +#ifndef RL_DEFAULT_BATCH_BUFFER_ELEMENTS + #if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33) + // This is the maximum amount of elements (quads) per batch + // NOTE: Be careful with text, every letter maps to a quad + #define RL_DEFAULT_BATCH_BUFFER_ELEMENTS 8192 + #endif + #if defined(GRAPHICS_API_OPENGL_ES2) + // We reduce memory sizes for embedded systems (RPI and HTML5) + // NOTE: On HTML5 (emscripten) this is allocated on heap, + // by default it's only 16MB!...just take care... + #define RL_DEFAULT_BATCH_BUFFER_ELEMENTS 2048 + #endif +#endif +#ifndef RL_DEFAULT_BATCH_BUFFERS + #define RL_DEFAULT_BATCH_BUFFERS 1 // Default number of batch buffers (multi-buffering) +#endif +#ifndef RL_DEFAULT_BATCH_DRAWCALLS + #define RL_DEFAULT_BATCH_DRAWCALLS 256 // Default number of batch draw calls (by state changes: mode, texture) +#endif +#ifndef RL_DEFAULT_BATCH_MAX_TEXTURE_UNITS + #define RL_DEFAULT_BATCH_MAX_TEXTURE_UNITS 4 // Maximum number of textures units that can be activated on batch drawing (SetShaderValueTexture()) +#endif + +// Internal Matrix stack +#ifndef RL_MAX_MATRIX_STACK_SIZE + #define RL_MAX_MATRIX_STACK_SIZE 32 // Maximum size of Matrix stack +#endif + +// Shader limits +#ifndef RL_MAX_SHADER_LOCATIONS + #define RL_MAX_SHADER_LOCATIONS 32 // Maximum number of shader locations supported +#endif + +// Projection matrix culling +#ifndef RL_CULL_DISTANCE_NEAR + #define RL_CULL_DISTANCE_NEAR 0.01 // Default near cull distance +#endif +#ifndef RL_CULL_DISTANCE_FAR + #define RL_CULL_DISTANCE_FAR 1000.0 // Default far cull distance +#endif + +// Texture parameters (equivalent to OpenGL defines) +#define RL_TEXTURE_WRAP_S 0x2802 // GL_TEXTURE_WRAP_S +#define RL_TEXTURE_WRAP_T 0x2803 // GL_TEXTURE_WRAP_T +#define RL_TEXTURE_MAG_FILTER 0x2800 // GL_TEXTURE_MAG_FILTER +#define RL_TEXTURE_MIN_FILTER 0x2801 // GL_TEXTURE_MIN_FILTER + +#define RL_TEXTURE_FILTER_NEAREST 0x2600 // GL_NEAREST +#define RL_TEXTURE_FILTER_LINEAR 0x2601 // GL_LINEAR +#define RL_TEXTURE_FILTER_MIP_NEAREST 0x2700 // GL_NEAREST_MIPMAP_NEAREST +#define RL_TEXTURE_FILTER_NEAREST_MIP_LINEAR 0x2702 // GL_NEAREST_MIPMAP_LINEAR +#define RL_TEXTURE_FILTER_LINEAR_MIP_NEAREST 0x2701 // GL_LINEAR_MIPMAP_NEAREST +#define RL_TEXTURE_FILTER_MIP_LINEAR 0x2703 // GL_LINEAR_MIPMAP_LINEAR +#define RL_TEXTURE_FILTER_ANISOTROPIC 0x3000 // Anisotropic filter (custom identifier) +#define RL_TEXTURE_MIPMAP_BIAS_RATIO 0x4000 // Texture mipmap bias, percentage ratio (custom identifier) + +#define RL_TEXTURE_WRAP_REPEAT 0x2901 // GL_REPEAT +#define RL_TEXTURE_WRAP_CLAMP 0x812F // GL_CLAMP_TO_EDGE +#define RL_TEXTURE_WRAP_MIRROR_REPEAT 0x8370 // GL_MIRRORED_REPEAT +#define RL_TEXTURE_WRAP_MIRROR_CLAMP 0x8742 // GL_MIRROR_CLAMP_EXT + +// Matrix modes (equivalent to OpenGL) +#define RL_MODELVIEW 0x1700 // GL_MODELVIEW +#define RL_PROJECTION 0x1701 // GL_PROJECTION +#define RL_TEXTURE 0x1702 // GL_TEXTURE + +// Primitive assembly draw modes +#define RL_LINES 0x0001 // GL_LINES +#define RL_TRIANGLES 0x0004 // GL_TRIANGLES +#define RL_QUADS 0x0007 // GL_QUADS + +// GL equivalent data types +#define RL_UNSIGNED_BYTE 0x1401 // GL_UNSIGNED_BYTE +#define RL_FLOAT 0x1406 // GL_FLOAT + +// GL buffer usage hint +#define RL_STREAM_DRAW 0x88E0 // GL_STREAM_DRAW +#define RL_STREAM_READ 0x88E1 // GL_STREAM_READ +#define RL_STREAM_COPY 0x88E2 // GL_STREAM_COPY +#define RL_STATIC_DRAW 0x88E4 // GL_STATIC_DRAW +#define RL_STATIC_READ 0x88E5 // GL_STATIC_READ +#define RL_STATIC_COPY 0x88E6 // GL_STATIC_COPY +#define RL_DYNAMIC_DRAW 0x88E8 // GL_DYNAMIC_DRAW +#define RL_DYNAMIC_READ 0x88E9 // GL_DYNAMIC_READ +#define RL_DYNAMIC_COPY 0x88EA // GL_DYNAMIC_COPY + +// GL Shader type +#define RL_FRAGMENT_SHADER 0x8B30 // GL_FRAGMENT_SHADER +#define RL_VERTEX_SHADER 0x8B31 // GL_VERTEX_SHADER +#define RL_COMPUTE_SHADER 0x91B9 // GL_COMPUTE_SHADER + +// GL blending factors +#define RL_ZERO 0 // GL_ZERO +#define RL_ONE 1 // GL_ONE +#define RL_SRC_COLOR 0x0300 // GL_SRC_COLOR +#define RL_ONE_MINUS_SRC_COLOR 0x0301 // GL_ONE_MINUS_SRC_COLOR +#define RL_SRC_ALPHA 0x0302 // GL_SRC_ALPHA +#define RL_ONE_MINUS_SRC_ALPHA 0x0303 // GL_ONE_MINUS_SRC_ALPHA +#define RL_DST_ALPHA 0x0304 // GL_DST_ALPHA +#define RL_ONE_MINUS_DST_ALPHA 0x0305 // GL_ONE_MINUS_DST_ALPHA +#define RL_DST_COLOR 0x0306 // GL_DST_COLOR +#define RL_ONE_MINUS_DST_COLOR 0x0307 // GL_ONE_MINUS_DST_COLOR +#define RL_SRC_ALPHA_SATURATE 0x0308 // GL_SRC_ALPHA_SATURATE +#define RL_CONSTANT_COLOR 0x8001 // GL_CONSTANT_COLOR +#define RL_ONE_MINUS_CONSTANT_COLOR 0x8002 // GL_ONE_MINUS_CONSTANT_COLOR +#define RL_CONSTANT_ALPHA 0x8003 // GL_CONSTANT_ALPHA +#define RL_ONE_MINUS_CONSTANT_ALPHA 0x8004 // GL_ONE_MINUS_CONSTANT_ALPHA + +// GL blending functions/equations +#define RL_FUNC_ADD 0x8006 // GL_FUNC_ADD +#define RL_MIN 0x8007 // GL_MIN +#define RL_MAX 0x8008 // GL_MAX +#define RL_FUNC_SUBTRACT 0x800A // GL_FUNC_SUBTRACT +#define RL_FUNC_REVERSE_SUBTRACT 0x800B // GL_FUNC_REVERSE_SUBTRACT +#define RL_BLEND_EQUATION 0x8009 // GL_BLEND_EQUATION +#define RL_BLEND_EQUATION_RGB 0x8009 // GL_BLEND_EQUATION_RGB // (Same as BLEND_EQUATION) +#define RL_BLEND_EQUATION_ALPHA 0x883D // GL_BLEND_EQUATION_ALPHA +#define RL_BLEND_DST_RGB 0x80C8 // GL_BLEND_DST_RGB +#define RL_BLEND_SRC_RGB 0x80C9 // GL_BLEND_SRC_RGB +#define RL_BLEND_DST_ALPHA 0x80CA // GL_BLEND_DST_ALPHA +#define RL_BLEND_SRC_ALPHA 0x80CB // GL_BLEND_SRC_ALPHA +#define RL_BLEND_COLOR 0x8005 // GL_BLEND_COLOR + +#define RL_READ_FRAMEBUFFER 0x8CA8 // GL_READ_FRAMEBUFFER +#define RL_DRAW_FRAMEBUFFER 0x8CA9 // GL_DRAW_FRAMEBUFFER + +// Default shader vertex attribute locations +#ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION + #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION 0 +#endif +#ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD + #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD 1 +#endif +#ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL + #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL 2 +#endif +#ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_COLOR + #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_COLOR 3 +#endif + #ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_TANGENT +#define RL_DEFAULT_SHADER_ATTRIB_LOCATION_TANGENT 4 +#endif +#ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD2 + #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD2 5 +#endif +#ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_INDICES + #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_INDICES 6 +#endif +#ifdef RL_SUPPORT_MESH_GPU_SKINNING +#ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEIDS + #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEIDS 7 +#endif +#ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEWEIGHTS + #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEWEIGHTS 8 +#endif +#endif + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- +#if (defined(__STDC__) && __STDC_VERSION__ >= 199901L) || (defined(_MSC_VER) && _MSC_VER >= 1800) + #include <stdbool.h> +#elif !defined(__cplusplus) && !defined(bool) && !defined(RL_BOOL_TYPE) + // Boolean type +typedef enum bool { false = 0, true = !false } bool; +#endif + +#if !defined(RL_MATRIX_TYPE) +// Matrix, 4x4 components, column major, OpenGL style, right handed +typedef struct Matrix { + float m0, m4, m8, m12; // Matrix first row (4 components) + float m1, m5, m9, m13; // Matrix second row (4 components) + float m2, m6, m10, m14; // Matrix third row (4 components) + float m3, m7, m11, m15; // Matrix fourth row (4 components) +} Matrix; +#define RL_MATRIX_TYPE +#endif + +// Dynamic vertex buffers (position + texcoords + colors + indices arrays) +typedef struct rlVertexBuffer { + int elementCount; // Number of elements in the buffer (QUADS) + + float *vertices; // Vertex position (XYZ - 3 components per vertex) (shader-location = 0) + float *texcoords; // Vertex texture coordinates (UV - 2 components per vertex) (shader-location = 1) + float *normals; // Vertex normal (XYZ - 3 components per vertex) (shader-location = 2) + unsigned char *colors; // Vertex colors (RGBA - 4 components per vertex) (shader-location = 3) +#if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33) + unsigned int *indices; // Vertex indices (in case vertex data comes indexed) (6 indices per quad) +#endif +#if defined(GRAPHICS_API_OPENGL_ES2) + unsigned short *indices; // Vertex indices (in case vertex data comes indexed) (6 indices per quad) +#endif + unsigned int vaoId; // OpenGL Vertex Array Object id + unsigned int vboId[5]; // OpenGL Vertex Buffer Objects id (5 types of vertex data) +} rlVertexBuffer; + +// Draw call type +// NOTE: Only texture changes register a new draw, other state-change-related elements are not +// used at this moment (vaoId, shaderId, matrices), raylib just forces a batch draw call if any +// of those state-change happens (this is done in core module) +typedef struct rlDrawCall { + int mode; // Drawing mode: LINES, TRIANGLES, QUADS + int vertexCount; // Number of vertex of the draw + int vertexAlignment; // Number of vertex required for index alignment (LINES, TRIANGLES) + //unsigned int vaoId; // Vertex array id to be used on the draw -> Using RLGL.currentBatch->vertexBuffer.vaoId + //unsigned int shaderId; // Shader id to be used on the draw -> Using RLGL.currentShaderId + unsigned int textureId; // Texture id to be used on the draw -> Use to create new draw call if changes + + //Matrix projection; // Projection matrix for this draw -> Using RLGL.projection by default + //Matrix modelview; // Modelview matrix for this draw -> Using RLGL.modelview by default +} rlDrawCall; + +// rlRenderBatch type +typedef struct rlRenderBatch { + int bufferCount; // Number of vertex buffers (multi-buffering support) + int currentBuffer; // Current buffer tracking in case of multi-buffering + rlVertexBuffer *vertexBuffer; // Dynamic buffer(s) for vertex data + + rlDrawCall *draws; // Draw calls array, depends on textureId + int drawCounter; // Draw calls counter + float currentDepth; // Current depth value for next draw +} rlRenderBatch; + +// OpenGL version +typedef enum { + RL_OPENGL_11 = 1, // OpenGL 1.1 + RL_OPENGL_21, // OpenGL 2.1 (GLSL 120) + RL_OPENGL_33, // OpenGL 3.3 (GLSL 330) + RL_OPENGL_43, // OpenGL 4.3 (using GLSL 330) + RL_OPENGL_ES_20, // OpenGL ES 2.0 (GLSL 100) + RL_OPENGL_ES_30 // OpenGL ES 3.0 (GLSL 300 es) +} rlGlVersion; + +// Trace log level +// NOTE: Organized by priority level +typedef enum { + RL_LOG_ALL = 0, // Display all logs + RL_LOG_TRACE, // Trace logging, intended for internal use only + RL_LOG_DEBUG, // Debug logging, used for internal debugging, it should be disabled on release builds + RL_LOG_INFO, // Info logging, used for program execution info + RL_LOG_WARNING, // Warning logging, used on recoverable failures + RL_LOG_ERROR, // Error logging, used on unrecoverable failures + RL_LOG_FATAL, // Fatal logging, used to abort program: exit(EXIT_FAILURE) + RL_LOG_NONE // Disable logging +} rlTraceLogLevel; + +// Texture pixel formats +// NOTE: Support depends on OpenGL version +typedef enum { + RL_PIXELFORMAT_UNCOMPRESSED_GRAYSCALE = 1, // 8 bit per pixel (no alpha) + RL_PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA, // 8*2 bpp (2 channels) + RL_PIXELFORMAT_UNCOMPRESSED_R5G6B5, // 16 bpp + RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8, // 24 bpp + RL_PIXELFORMAT_UNCOMPRESSED_R5G5B5A1, // 16 bpp (1 bit alpha) + RL_PIXELFORMAT_UNCOMPRESSED_R4G4B4A4, // 16 bpp (4 bit alpha) + RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, // 32 bpp + RL_PIXELFORMAT_UNCOMPRESSED_R32, // 32 bpp (1 channel - float) + RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32, // 32*3 bpp (3 channels - float) + RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32, // 32*4 bpp (4 channels - float) + RL_PIXELFORMAT_UNCOMPRESSED_R16, // 16 bpp (1 channel - half float) + RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16, // 16*3 bpp (3 channels - half float) + RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16, // 16*4 bpp (4 channels - half float) + RL_PIXELFORMAT_COMPRESSED_DXT1_RGB, // 4 bpp (no alpha) + RL_PIXELFORMAT_COMPRESSED_DXT1_RGBA, // 4 bpp (1 bit alpha) + RL_PIXELFORMAT_COMPRESSED_DXT3_RGBA, // 8 bpp + RL_PIXELFORMAT_COMPRESSED_DXT5_RGBA, // 8 bpp + RL_PIXELFORMAT_COMPRESSED_ETC1_RGB, // 4 bpp + RL_PIXELFORMAT_COMPRESSED_ETC2_RGB, // 4 bpp + RL_PIXELFORMAT_COMPRESSED_ETC2_EAC_RGBA, // 8 bpp + RL_PIXELFORMAT_COMPRESSED_PVRT_RGB, // 4 bpp + RL_PIXELFORMAT_COMPRESSED_PVRT_RGBA, // 4 bpp + RL_PIXELFORMAT_COMPRESSED_ASTC_4x4_RGBA, // 8 bpp + RL_PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA // 2 bpp +} rlPixelFormat; + +// Texture parameters: filter mode +// NOTE 1: Filtering considers mipmaps if available in the texture +// NOTE 2: Filter is accordingly set for minification and magnification +typedef enum { + RL_TEXTURE_FILTER_POINT = 0, // No filter, just pixel approximation + RL_TEXTURE_FILTER_BILINEAR, // Linear filtering + RL_TEXTURE_FILTER_TRILINEAR, // Trilinear filtering (linear with mipmaps) + RL_TEXTURE_FILTER_ANISOTROPIC_4X, // Anisotropic filtering 4x + RL_TEXTURE_FILTER_ANISOTROPIC_8X, // Anisotropic filtering 8x + RL_TEXTURE_FILTER_ANISOTROPIC_16X, // Anisotropic filtering 16x +} rlTextureFilter; + +// Color blending modes (pre-defined) +typedef enum { + RL_BLEND_ALPHA = 0, // Blend textures considering alpha (default) + RL_BLEND_ADDITIVE, // Blend textures adding colors + RL_BLEND_MULTIPLIED, // Blend textures multiplying colors + RL_BLEND_ADD_COLORS, // Blend textures adding colors (alternative) + RL_BLEND_SUBTRACT_COLORS, // Blend textures subtracting colors (alternative) + RL_BLEND_ALPHA_PREMULTIPLY, // Blend premultiplied textures considering alpha + RL_BLEND_CUSTOM, // Blend textures using custom src/dst factors (use rlSetBlendFactors()) + RL_BLEND_CUSTOM_SEPARATE // Blend textures using custom src/dst factors (use rlSetBlendFactorsSeparate()) +} rlBlendMode; + +// Shader location point type +typedef enum { + RL_SHADER_LOC_VERTEX_POSITION = 0, // Shader location: vertex attribute: position + RL_SHADER_LOC_VERTEX_TEXCOORD01, // Shader location: vertex attribute: texcoord01 + RL_SHADER_LOC_VERTEX_TEXCOORD02, // Shader location: vertex attribute: texcoord02 + RL_SHADER_LOC_VERTEX_NORMAL, // Shader location: vertex attribute: normal + RL_SHADER_LOC_VERTEX_TANGENT, // Shader location: vertex attribute: tangent + RL_SHADER_LOC_VERTEX_COLOR, // Shader location: vertex attribute: color + RL_SHADER_LOC_MATRIX_MVP, // Shader location: matrix uniform: model-view-projection + RL_SHADER_LOC_MATRIX_VIEW, // Shader location: matrix uniform: view (camera transform) + RL_SHADER_LOC_MATRIX_PROJECTION, // Shader location: matrix uniform: projection + RL_SHADER_LOC_MATRIX_MODEL, // Shader location: matrix uniform: model (transform) + RL_SHADER_LOC_MATRIX_NORMAL, // Shader location: matrix uniform: normal + RL_SHADER_LOC_VECTOR_VIEW, // Shader location: vector uniform: view + RL_SHADER_LOC_COLOR_DIFFUSE, // Shader location: vector uniform: diffuse color + RL_SHADER_LOC_COLOR_SPECULAR, // Shader location: vector uniform: specular color + RL_SHADER_LOC_COLOR_AMBIENT, // Shader location: vector uniform: ambient color + RL_SHADER_LOC_MAP_ALBEDO, // Shader location: sampler2d texture: albedo (same as: RL_SHADER_LOC_MAP_DIFFUSE) + RL_SHADER_LOC_MAP_METALNESS, // Shader location: sampler2d texture: metalness (same as: RL_SHADER_LOC_MAP_SPECULAR) + RL_SHADER_LOC_MAP_NORMAL, // Shader location: sampler2d texture: normal + RL_SHADER_LOC_MAP_ROUGHNESS, // Shader location: sampler2d texture: roughness + RL_SHADER_LOC_MAP_OCCLUSION, // Shader location: sampler2d texture: occlusion + RL_SHADER_LOC_MAP_EMISSION, // Shader location: sampler2d texture: emission + RL_SHADER_LOC_MAP_HEIGHT, // Shader location: sampler2d texture: height + RL_SHADER_LOC_MAP_CUBEMAP, // Shader location: samplerCube texture: cubemap + RL_SHADER_LOC_MAP_IRRADIANCE, // Shader location: samplerCube texture: irradiance + RL_SHADER_LOC_MAP_PREFILTER, // Shader location: samplerCube texture: prefilter + RL_SHADER_LOC_MAP_BRDF // Shader location: sampler2d texture: brdf +} rlShaderLocationIndex; + +#define RL_SHADER_LOC_MAP_DIFFUSE RL_SHADER_LOC_MAP_ALBEDO +#define RL_SHADER_LOC_MAP_SPECULAR RL_SHADER_LOC_MAP_METALNESS + +// Shader uniform data type +typedef enum { + RL_SHADER_UNIFORM_FLOAT = 0, // Shader uniform type: float + RL_SHADER_UNIFORM_VEC2, // Shader uniform type: vec2 (2 float) + RL_SHADER_UNIFORM_VEC3, // Shader uniform type: vec3 (3 float) + RL_SHADER_UNIFORM_VEC4, // Shader uniform type: vec4 (4 float) + RL_SHADER_UNIFORM_INT, // Shader uniform type: int + RL_SHADER_UNIFORM_IVEC2, // Shader uniform type: ivec2 (2 int) + RL_SHADER_UNIFORM_IVEC3, // Shader uniform type: ivec3 (3 int) + RL_SHADER_UNIFORM_IVEC4, // Shader uniform type: ivec4 (4 int) + RL_SHADER_UNIFORM_UINT, // Shader uniform type: unsigned int + RL_SHADER_UNIFORM_UIVEC2, // Shader uniform type: uivec2 (2 unsigned int) + RL_SHADER_UNIFORM_UIVEC3, // Shader uniform type: uivec3 (3 unsigned int) + RL_SHADER_UNIFORM_UIVEC4, // Shader uniform type: uivec4 (4 unsigned int) + RL_SHADER_UNIFORM_SAMPLER2D // Shader uniform type: sampler2d +} rlShaderUniformDataType; + +// Shader attribute data types +typedef enum { + RL_SHADER_ATTRIB_FLOAT = 0, // Shader attribute type: float + RL_SHADER_ATTRIB_VEC2, // Shader attribute type: vec2 (2 float) + RL_SHADER_ATTRIB_VEC3, // Shader attribute type: vec3 (3 float) + RL_SHADER_ATTRIB_VEC4 // Shader attribute type: vec4 (4 float) +} rlShaderAttributeDataType; + +// Framebuffer attachment type +// NOTE: By default up to 8 color channels defined, but it can be more +typedef enum { + RL_ATTACHMENT_COLOR_CHANNEL0 = 0, // Framebuffer attachment type: color 0 + RL_ATTACHMENT_COLOR_CHANNEL1 = 1, // Framebuffer attachment type: color 1 + RL_ATTACHMENT_COLOR_CHANNEL2 = 2, // Framebuffer attachment type: color 2 + RL_ATTACHMENT_COLOR_CHANNEL3 = 3, // Framebuffer attachment type: color 3 + RL_ATTACHMENT_COLOR_CHANNEL4 = 4, // Framebuffer attachment type: color 4 + RL_ATTACHMENT_COLOR_CHANNEL5 = 5, // Framebuffer attachment type: color 5 + RL_ATTACHMENT_COLOR_CHANNEL6 = 6, // Framebuffer attachment type: color 6 + RL_ATTACHMENT_COLOR_CHANNEL7 = 7, // Framebuffer attachment type: color 7 + RL_ATTACHMENT_DEPTH = 100, // Framebuffer attachment type: depth + RL_ATTACHMENT_STENCIL = 200, // Framebuffer attachment type: stencil +} rlFramebufferAttachType; + +// Framebuffer texture attachment type +typedef enum { + RL_ATTACHMENT_CUBEMAP_POSITIVE_X = 0, // Framebuffer texture attachment type: cubemap, +X side + RL_ATTACHMENT_CUBEMAP_NEGATIVE_X = 1, // Framebuffer texture attachment type: cubemap, -X side + RL_ATTACHMENT_CUBEMAP_POSITIVE_Y = 2, // Framebuffer texture attachment type: cubemap, +Y side + RL_ATTACHMENT_CUBEMAP_NEGATIVE_Y = 3, // Framebuffer texture attachment type: cubemap, -Y side + RL_ATTACHMENT_CUBEMAP_POSITIVE_Z = 4, // Framebuffer texture attachment type: cubemap, +Z side + RL_ATTACHMENT_CUBEMAP_NEGATIVE_Z = 5, // Framebuffer texture attachment type: cubemap, -Z side + RL_ATTACHMENT_TEXTURE2D = 100, // Framebuffer texture attachment type: texture2d + RL_ATTACHMENT_RENDERBUFFER = 200, // Framebuffer texture attachment type: renderbuffer +} rlFramebufferAttachTextureType; + +// Face culling mode +typedef enum { + RL_CULL_FACE_FRONT = 0, + RL_CULL_FACE_BACK +} rlCullMode; + +//------------------------------------------------------------------------------------ +// Functions Declaration - Matrix operations +//------------------------------------------------------------------------------------ + +#if defined(__cplusplus) +extern "C" { // Prevents name mangling of functions +#endif + +RLAPI void rlMatrixMode(int mode); // Choose the current matrix to be transformed +RLAPI void rlPushMatrix(void); // Push the current matrix to stack +RLAPI void rlPopMatrix(void); // Pop latest inserted matrix from stack +RLAPI void rlLoadIdentity(void); // Reset current matrix to identity matrix +RLAPI void rlTranslatef(float x, float y, float z); // Multiply the current matrix by a translation matrix +RLAPI void rlRotatef(float angle, float x, float y, float z); // Multiply the current matrix by a rotation matrix +RLAPI void rlScalef(float x, float y, float z); // Multiply the current matrix by a scaling matrix +RLAPI void rlMultMatrixf(const float *matf); // Multiply the current matrix by another matrix +RLAPI void rlFrustum(double left, double right, double bottom, double top, double znear, double zfar); +RLAPI void rlOrtho(double left, double right, double bottom, double top, double znear, double zfar); +RLAPI void rlViewport(int x, int y, int width, int height); // Set the viewport area +RLAPI void rlSetClipPlanes(double nearPlane, double farPlane); // Set clip planes distances +RLAPI double rlGetCullDistanceNear(void); // Get cull plane distance near +RLAPI double rlGetCullDistanceFar(void); // Get cull plane distance far + +//------------------------------------------------------------------------------------ +// Functions Declaration - Vertex level operations +//------------------------------------------------------------------------------------ +RLAPI void rlBegin(int mode); // Initialize drawing mode (how to organize vertex) +RLAPI void rlEnd(void); // Finish vertex providing +RLAPI void rlVertex2i(int x, int y); // Define one vertex (position) - 2 int +RLAPI void rlVertex2f(float x, float y); // Define one vertex (position) - 2 float +RLAPI void rlVertex3f(float x, float y, float z); // Define one vertex (position) - 3 float +RLAPI void rlTexCoord2f(float x, float y); // Define one vertex (texture coordinate) - 2 float +RLAPI void rlNormal3f(float x, float y, float z); // Define one vertex (normal) - 3 float +RLAPI void rlColor4ub(unsigned char r, unsigned char g, unsigned char b, unsigned char a); // Define one vertex (color) - 4 byte +RLAPI void rlColor3f(float x, float y, float z); // Define one vertex (color) - 3 float +RLAPI void rlColor4f(float x, float y, float z, float w); // Define one vertex (color) - 4 float + +//------------------------------------------------------------------------------------ +// Functions Declaration - OpenGL style functions (common to 1.1, 3.3+, ES2) +// NOTE: This functions are used to completely abstract raylib code from OpenGL layer, +// some of them are direct wrappers over OpenGL calls, some others are custom +//------------------------------------------------------------------------------------ + +// Vertex buffers state +RLAPI bool rlEnableVertexArray(unsigned int vaoId); // Enable vertex array (VAO, if supported) +RLAPI void rlDisableVertexArray(void); // Disable vertex array (VAO, if supported) +RLAPI void rlEnableVertexBuffer(unsigned int id); // Enable vertex buffer (VBO) +RLAPI void rlDisableVertexBuffer(void); // Disable vertex buffer (VBO) +RLAPI void rlEnableVertexBufferElement(unsigned int id); // Enable vertex buffer element (VBO element) +RLAPI void rlDisableVertexBufferElement(void); // Disable vertex buffer element (VBO element) +RLAPI void rlEnableVertexAttribute(unsigned int index); // Enable vertex attribute index +RLAPI void rlDisableVertexAttribute(unsigned int index); // Disable vertex attribute index +#if defined(GRAPHICS_API_OPENGL_11) +RLAPI void rlEnableStatePointer(int vertexAttribType, void *buffer); // Enable attribute state pointer +RLAPI void rlDisableStatePointer(int vertexAttribType); // Disable attribute state pointer +#endif + +// Textures state +RLAPI void rlActiveTextureSlot(int slot); // Select and active a texture slot +RLAPI void rlEnableTexture(unsigned int id); // Enable texture +RLAPI void rlDisableTexture(void); // Disable texture +RLAPI void rlEnableTextureCubemap(unsigned int id); // Enable texture cubemap +RLAPI void rlDisableTextureCubemap(void); // Disable texture cubemap +RLAPI void rlTextureParameters(unsigned int id, int param, int value); // Set texture parameters (filter, wrap) +RLAPI void rlCubemapParameters(unsigned int id, int param, int value); // Set cubemap parameters (filter, wrap) + +// Shader state +RLAPI void rlEnableShader(unsigned int id); // Enable shader program +RLAPI void rlDisableShader(void); // Disable shader program + +// Framebuffer state +RLAPI void rlEnableFramebuffer(unsigned int id); // Enable render texture (fbo) +RLAPI void rlDisableFramebuffer(void); // Disable render texture (fbo), return to default framebuffer +RLAPI unsigned int rlGetActiveFramebuffer(void); // Get the currently active render texture (fbo), 0 for default framebuffer +RLAPI void rlActiveDrawBuffers(int count); // Activate multiple draw color buffers +RLAPI void rlBlitFramebuffer(int srcX, int srcY, int srcWidth, int srcHeight, int dstX, int dstY, int dstWidth, int dstHeight, int bufferMask); // Blit active framebuffer to main framebuffer +RLAPI void rlBindFramebuffer(unsigned int target, unsigned int framebuffer); // Bind framebuffer (FBO) + +// General render state +RLAPI void rlEnableColorBlend(void); // Enable color blending +RLAPI void rlDisableColorBlend(void); // Disable color blending +RLAPI void rlEnableDepthTest(void); // Enable depth test +RLAPI void rlDisableDepthTest(void); // Disable depth test +RLAPI void rlEnableDepthMask(void); // Enable depth write +RLAPI void rlDisableDepthMask(void); // Disable depth write +RLAPI void rlEnableBackfaceCulling(void); // Enable backface culling +RLAPI void rlDisableBackfaceCulling(void); // Disable backface culling +RLAPI void rlColorMask(bool r, bool g, bool b, bool a); // Color mask control +RLAPI void rlSetCullFace(int mode); // Set face culling mode +RLAPI void rlEnableScissorTest(void); // Enable scissor test +RLAPI void rlDisableScissorTest(void); // Disable scissor test +RLAPI void rlScissor(int x, int y, int width, int height); // Scissor test +RLAPI void rlEnableWireMode(void); // Enable wire mode +RLAPI void rlEnablePointMode(void); // Enable point mode +RLAPI void rlDisableWireMode(void); // Disable wire (and point) mode +RLAPI void rlSetLineWidth(float width); // Set the line drawing width +RLAPI float rlGetLineWidth(void); // Get the line drawing width +RLAPI void rlEnableSmoothLines(void); // Enable line aliasing +RLAPI void rlDisableSmoothLines(void); // Disable line aliasing +RLAPI void rlEnableStereoRender(void); // Enable stereo rendering +RLAPI void rlDisableStereoRender(void); // Disable stereo rendering +RLAPI bool rlIsStereoRenderEnabled(void); // Check if stereo render is enabled + +RLAPI void rlClearColor(unsigned char r, unsigned char g, unsigned char b, unsigned char a); // Clear color buffer with color +RLAPI void rlClearScreenBuffers(void); // Clear used screen buffers (color and depth) +RLAPI void rlCheckErrors(void); // Check and log OpenGL error codes +RLAPI void rlSetBlendMode(int mode); // Set blending mode +RLAPI void rlSetBlendFactors(int glSrcFactor, int glDstFactor, int glEquation); // Set blending mode factor and equation (using OpenGL factors) +RLAPI void rlSetBlendFactorsSeparate(int glSrcRGB, int glDstRGB, int glSrcAlpha, int glDstAlpha, int glEqRGB, int glEqAlpha); // Set blending mode factors and equations separately (using OpenGL factors) + +//------------------------------------------------------------------------------------ +// Functions Declaration - rlgl functionality +//------------------------------------------------------------------------------------ +// rlgl initialization functions +RLAPI void rlglInit(int width, int height); // Initialize rlgl (buffers, shaders, textures, states) +RLAPI void rlglClose(void); // De-initialize rlgl (buffers, shaders, textures) +RLAPI void rlLoadExtensions(void *loader); // Load OpenGL extensions (loader function required) +RLAPI int rlGetVersion(void); // Get current OpenGL version +RLAPI void rlSetFramebufferWidth(int width); // Set current framebuffer width +RLAPI int rlGetFramebufferWidth(void); // Get default framebuffer width +RLAPI void rlSetFramebufferHeight(int height); // Set current framebuffer height +RLAPI int rlGetFramebufferHeight(void); // Get default framebuffer height + +RLAPI unsigned int rlGetTextureIdDefault(void); // Get default texture id +RLAPI unsigned int rlGetShaderIdDefault(void); // Get default shader id +RLAPI int *rlGetShaderLocsDefault(void); // Get default shader locations + +// Render batch management +// NOTE: rlgl provides a default render batch to behave like OpenGL 1.1 immediate mode +// but this render batch API is exposed in case of custom batches are required +RLAPI rlRenderBatch rlLoadRenderBatch(int numBuffers, int bufferElements); // Load a render batch system +RLAPI void rlUnloadRenderBatch(rlRenderBatch batch); // Unload render batch system +RLAPI void rlDrawRenderBatch(rlRenderBatch *batch); // Draw render batch data (Update->Draw->Reset) +RLAPI void rlSetRenderBatchActive(rlRenderBatch *batch); // Set the active render batch for rlgl (NULL for default internal) +RLAPI void rlDrawRenderBatchActive(void); // Update and draw internal render batch +RLAPI bool rlCheckRenderBatchLimit(int vCount); // Check internal buffer overflow for a given number of vertex + +RLAPI void rlSetTexture(unsigned int id); // Set current texture for render batch and check buffers limits + +//------------------------------------------------------------------------------------------------------------------------ + +// Vertex buffers management +RLAPI unsigned int rlLoadVertexArray(void); // Load vertex array (vao) if supported +RLAPI unsigned int rlLoadVertexBuffer(const void *buffer, int size, bool dynamic); // Load a vertex buffer object +RLAPI unsigned int rlLoadVertexBufferElement(const void *buffer, int size, bool dynamic); // Load vertex buffer elements object +RLAPI void rlUpdateVertexBuffer(unsigned int bufferId, const void *data, int dataSize, int offset); // Update vertex buffer object data on GPU buffer +RLAPI void rlUpdateVertexBufferElements(unsigned int id, const void *data, int dataSize, int offset); // Update vertex buffer elements data on GPU buffer +RLAPI void rlUnloadVertexArray(unsigned int vaoId); // Unload vertex array (vao) +RLAPI void rlUnloadVertexBuffer(unsigned int vboId); // Unload vertex buffer object +RLAPI void rlSetVertexAttribute(unsigned int index, int compSize, int type, bool normalized, int stride, int offset); // Set vertex attribute data configuration +RLAPI void rlSetVertexAttributeDivisor(unsigned int index, int divisor); // Set vertex attribute data divisor +RLAPI void rlSetVertexAttributeDefault(int locIndex, const void *value, int attribType, int count); // Set vertex attribute default value, when attribute to provided +RLAPI void rlDrawVertexArray(int offset, int count); // Draw vertex array (currently active vao) +RLAPI void rlDrawVertexArrayElements(int offset, int count, const void *buffer); // Draw vertex array elements +RLAPI void rlDrawVertexArrayInstanced(int offset, int count, int instances); // Draw vertex array (currently active vao) with instancing +RLAPI void rlDrawVertexArrayElementsInstanced(int offset, int count, const void *buffer, int instances); // Draw vertex array elements with instancing + +// Textures management +RLAPI unsigned int rlLoadTexture(const void *data, int width, int height, int format, int mipmapCount); // Load texture data +RLAPI unsigned int rlLoadTextureDepth(int width, int height, bool useRenderBuffer); // Load depth texture/renderbuffer (to be attached to fbo) +RLAPI unsigned int rlLoadTextureCubemap(const void *data, int size, int format, int mipmapCount); // Load texture cubemap data +RLAPI void rlUpdateTexture(unsigned int id, int offsetX, int offsetY, int width, int height, int format, const void *data); // Update texture with new data on GPU +RLAPI void rlGetGlTextureFormats(int format, unsigned int *glInternalFormat, unsigned int *glFormat, unsigned int *glType); // Get OpenGL internal formats +RLAPI const char *rlGetPixelFormatName(unsigned int format); // Get name string for pixel format +RLAPI void rlUnloadTexture(unsigned int id); // Unload texture from GPU memory +RLAPI void rlGenTextureMipmaps(unsigned int id, int width, int height, int format, int *mipmaps); // Generate mipmap data for selected texture +RLAPI void *rlReadTexturePixels(unsigned int id, int width, int height, int format); // Read texture pixel data +RLAPI unsigned char *rlReadScreenPixels(int width, int height); // Read screen pixel data (color buffer) + +// Framebuffer management (fbo) +RLAPI unsigned int rlLoadFramebuffer(void); // Load an empty framebuffer +RLAPI void rlFramebufferAttach(unsigned int fboId, unsigned int texId, int attachType, int texType, int mipLevel); // Attach texture/renderbuffer to a framebuffer +RLAPI bool rlFramebufferComplete(unsigned int id); // Verify framebuffer is complete +RLAPI void rlUnloadFramebuffer(unsigned int id); // Delete framebuffer from GPU + +// Shaders management +RLAPI unsigned int rlLoadShaderCode(const char *vsCode, const char *fsCode); // Load shader from code strings +RLAPI unsigned int rlCompileShader(const char *shaderCode, int type); // Compile custom shader and return shader id (type: RL_VERTEX_SHADER, RL_FRAGMENT_SHADER, RL_COMPUTE_SHADER) +RLAPI unsigned int rlLoadShaderProgram(unsigned int vShaderId, unsigned int fShaderId); // Load custom shader program +RLAPI void rlUnloadShaderProgram(unsigned int id); // Unload shader program +RLAPI int rlGetLocationUniform(unsigned int shaderId, const char *uniformName); // Get shader location uniform +RLAPI int rlGetLocationAttrib(unsigned int shaderId, const char *attribName); // Get shader location attribute +RLAPI void rlSetUniform(int locIndex, const void *value, int uniformType, int count); // Set shader value uniform +RLAPI void rlSetUniformMatrix(int locIndex, Matrix mat); // Set shader value matrix +RLAPI void rlSetUniformMatrices(int locIndex, const Matrix *mat, int count); // Set shader value matrices +RLAPI void rlSetUniformSampler(int locIndex, unsigned int textureId); // Set shader value sampler +RLAPI void rlSetShader(unsigned int id, int *locs); // Set shader currently active (id and locations) + +// Compute shader management +RLAPI unsigned int rlLoadComputeShaderProgram(unsigned int shaderId); // Load compute shader program +RLAPI void rlComputeShaderDispatch(unsigned int groupX, unsigned int groupY, unsigned int groupZ); // Dispatch compute shader (equivalent to *draw* for graphics pipeline) + +// Shader buffer storage object management (ssbo) +RLAPI unsigned int rlLoadShaderBuffer(unsigned int size, const void *data, int usageHint); // Load shader storage buffer object (SSBO) +RLAPI void rlUnloadShaderBuffer(unsigned int ssboId); // Unload shader storage buffer object (SSBO) +RLAPI void rlUpdateShaderBuffer(unsigned int id, const void *data, unsigned int dataSize, unsigned int offset); // Update SSBO buffer data +RLAPI void rlBindShaderBuffer(unsigned int id, unsigned int index); // Bind SSBO buffer +RLAPI void rlReadShaderBuffer(unsigned int id, void *dest, unsigned int count, unsigned int offset); // Read SSBO buffer data (GPU->CPU) +RLAPI void rlCopyShaderBuffer(unsigned int destId, unsigned int srcId, unsigned int destOffset, unsigned int srcOffset, unsigned int count); // Copy SSBO data between buffers +RLAPI unsigned int rlGetShaderBufferSize(unsigned int id); // Get SSBO buffer size + +// Buffer management +RLAPI void rlBindImageTexture(unsigned int id, unsigned int index, int format, bool readonly); // Bind image texture + +// Matrix state management +RLAPI Matrix rlGetMatrixModelview(void); // Get internal modelview matrix +RLAPI Matrix rlGetMatrixProjection(void); // Get internal projection matrix +RLAPI Matrix rlGetMatrixTransform(void); // Get internal accumulated transform matrix +RLAPI Matrix rlGetMatrixProjectionStereo(int eye); // Get internal projection matrix for stereo render (selected eye) +RLAPI Matrix rlGetMatrixViewOffsetStereo(int eye); // Get internal view offset matrix for stereo render (selected eye) +RLAPI void rlSetMatrixProjection(Matrix proj); // Set a custom projection matrix (replaces internal projection matrix) +RLAPI void rlSetMatrixModelview(Matrix view); // Set a custom modelview matrix (replaces internal modelview matrix) +RLAPI void rlSetMatrixProjectionStereo(Matrix right, Matrix left); // Set eyes projection matrices for stereo rendering +RLAPI void rlSetMatrixViewOffsetStereo(Matrix right, Matrix left); // Set eyes view offsets matrices for stereo rendering + +// Quick and dirty cube/quad buffers load->draw->unload +RLAPI void rlLoadDrawCube(void); // Load and draw a cube +RLAPI void rlLoadDrawQuad(void); // Load and draw a quad + +#if defined(__cplusplus) +} +#endif + +#endif // RLGL_H + +/*********************************************************************************** +* +* RLGL IMPLEMENTATION +* +************************************************************************************/ + +#if defined(RLGL_IMPLEMENTATION) + +// Expose OpenGL functions from glad in raylib +#if defined(BUILD_LIBTYPE_SHARED) + #define GLAD_API_CALL_EXPORT + #define GLAD_API_CALL_EXPORT_BUILD +#endif + +#if defined(GRAPHICS_API_OPENGL_11) + #if defined(__APPLE__) + #include <OpenGL/gl.h> // OpenGL 1.1 library for OSX + #include <OpenGL/glext.h> // OpenGL extensions library + #else + // APIENTRY for OpenGL function pointer declarations is required + #if !defined(APIENTRY) + #if defined(_WIN32) + #define APIENTRY __stdcall + #else + #define APIENTRY + #endif + #endif + // WINGDIAPI definition. Some Windows OpenGL headers need it + #if !defined(WINGDIAPI) && defined(_WIN32) + #define WINGDIAPI __declspec(dllimport) + #endif + + #include <GL/gl.h> // OpenGL 1.1 library + #endif +#endif + +#if defined(GRAPHICS_API_OPENGL_33) + #define GLAD_MALLOC RL_MALLOC + #define GLAD_FREE RL_FREE + + #define GLAD_GL_IMPLEMENTATION + #include "external/glad.h" // GLAD extensions loading library, includes OpenGL headers +#endif + +#if defined(GRAPHICS_API_OPENGL_ES3) + #include <GLES3/gl3.h> // OpenGL ES 3.0 library + #define GL_GLEXT_PROTOTYPES + #include <GLES2/gl2ext.h> // OpenGL ES 2.0 extensions library +#elif defined(GRAPHICS_API_OPENGL_ES2) + // NOTE: OpenGL ES 2.0 can be enabled on Desktop platforms, + // in that case, functions are loaded from a custom glad for OpenGL ES 2.0 + #if defined(PLATFORM_DESKTOP_GLFW) || defined(PLATFORM_DESKTOP_SDL) + #define GLAD_GLES2_IMPLEMENTATION + #include "external/glad_gles2.h" + #else + #define GL_GLEXT_PROTOTYPES + //#include <EGL/egl.h> // EGL library -> not required, platform layer + #include <GLES2/gl2.h> // OpenGL ES 2.0 library + #include <GLES2/gl2ext.h> // OpenGL ES 2.0 extensions library + #endif + + // It seems OpenGL ES 2.0 instancing entry points are not defined on Raspberry Pi + // provided headers (despite being defined in official Khronos GLES2 headers) + #if defined(PLATFORM_DRM) + typedef void (GL_APIENTRYP PFNGLDRAWARRAYSINSTANCEDEXTPROC) (GLenum mode, GLint start, GLsizei count, GLsizei primcount); + typedef void (GL_APIENTRYP PFNGLDRAWELEMENTSINSTANCEDEXTPROC) (GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei primcount); + typedef void (GL_APIENTRYP PFNGLVERTEXATTRIBDIVISOREXTPROC) (GLuint index, GLuint divisor); + #endif +#endif + +#include <stdlib.h> // Required for: malloc(), free() +#include <string.h> // Required for: strcmp(), strlen() [Used in rlglInit(), on extensions loading] +#include <math.h> // Required for: sqrtf(), sinf(), cosf(), floor(), log() + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +#ifndef PI + #define PI 3.14159265358979323846f +#endif +#ifndef DEG2RAD + #define DEG2RAD (PI/180.0f) +#endif +#ifndef RAD2DEG + #define RAD2DEG (180.0f/PI) +#endif + +#ifndef GL_SHADING_LANGUAGE_VERSION + #define GL_SHADING_LANGUAGE_VERSION 0x8B8C +#endif + +#ifndef GL_COMPRESSED_RGB_S3TC_DXT1_EXT + #define GL_COMPRESSED_RGB_S3TC_DXT1_EXT 0x83F0 +#endif +#ifndef GL_COMPRESSED_RGBA_S3TC_DXT1_EXT + #define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1 +#endif +#ifndef GL_COMPRESSED_RGBA_S3TC_DXT3_EXT + #define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83F2 +#endif +#ifndef GL_COMPRESSED_RGBA_S3TC_DXT5_EXT + #define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3 +#endif +#ifndef GL_ETC1_RGB8_OES + #define GL_ETC1_RGB8_OES 0x8D64 +#endif +#ifndef GL_COMPRESSED_RGB8_ETC2 + #define GL_COMPRESSED_RGB8_ETC2 0x9274 +#endif +#ifndef GL_COMPRESSED_RGBA8_ETC2_EAC + #define GL_COMPRESSED_RGBA8_ETC2_EAC 0x9278 +#endif +#ifndef GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG + #define GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG 0x8C00 +#endif +#ifndef GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG + #define GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG 0x8C02 +#endif +#ifndef GL_COMPRESSED_RGBA_ASTC_4x4_KHR + #define GL_COMPRESSED_RGBA_ASTC_4x4_KHR 0x93b0 +#endif +#ifndef GL_COMPRESSED_RGBA_ASTC_8x8_KHR + #define GL_COMPRESSED_RGBA_ASTC_8x8_KHR 0x93b7 +#endif + +#ifndef GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT + #define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF +#endif +#ifndef GL_TEXTURE_MAX_ANISOTROPY_EXT + #define GL_TEXTURE_MAX_ANISOTROPY_EXT 0x84FE +#endif + +#ifndef GL_PROGRAM_POINT_SIZE + #define GL_PROGRAM_POINT_SIZE 0x8642 +#endif + +#ifndef GL_LINE_WIDTH + #define GL_LINE_WIDTH 0x0B21 +#endif + +#if defined(GRAPHICS_API_OPENGL_11) + #define GL_UNSIGNED_SHORT_5_6_5 0x8363 + #define GL_UNSIGNED_SHORT_5_5_5_1 0x8034 + #define GL_UNSIGNED_SHORT_4_4_4_4 0x8033 +#endif + +#if defined(GRAPHICS_API_OPENGL_21) + #define GL_LUMINANCE 0x1909 + #define GL_LUMINANCE_ALPHA 0x190A +#endif + +#if defined(GRAPHICS_API_OPENGL_ES2) + #define glClearDepth glClearDepthf + #if !defined(GRAPHICS_API_OPENGL_ES3) + #define GL_READ_FRAMEBUFFER GL_FRAMEBUFFER + #define GL_DRAW_FRAMEBUFFER GL_FRAMEBUFFER + #endif +#endif + +// Default shader vertex attribute names to set location points +#ifndef RL_DEFAULT_SHADER_ATTRIB_NAME_POSITION + #define RL_DEFAULT_SHADER_ATTRIB_NAME_POSITION "vertexPosition" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_NAME_POSITION +#endif +#ifndef RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD + #define RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD "vertexTexCoord" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD +#endif +#ifndef RL_DEFAULT_SHADER_ATTRIB_NAME_NORMAL + #define RL_DEFAULT_SHADER_ATTRIB_NAME_NORMAL "vertexNormal" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_NAME_NORMAL +#endif +#ifndef RL_DEFAULT_SHADER_ATTRIB_NAME_COLOR + #define RL_DEFAULT_SHADER_ATTRIB_NAME_COLOR "vertexColor" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_NAME_COLOR +#endif +#ifndef RL_DEFAULT_SHADER_ATTRIB_NAME_TANGENT + #define RL_DEFAULT_SHADER_ATTRIB_NAME_TANGENT "vertexTangent" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_NAME_TANGENT +#endif +#ifndef RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD2 + #define RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD2 "vertexTexCoord2" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD2 +#endif +#ifndef RL_DEFAULT_SHADER_ATTRIB_NAME_BONEIDS + #define RL_DEFAULT_SHADER_ATTRIB_NAME_BONEIDS "vertexBoneIds" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_NAME_BONEIDS +#endif +#ifndef RL_DEFAULT_SHADER_ATTRIB_NAME_BONEWEIGHTS + #define RL_DEFAULT_SHADER_ATTRIB_NAME_BONEWEIGHTS "vertexBoneWeights" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_NAME_BONEWEIGHTS +#endif + +#ifndef RL_DEFAULT_SHADER_UNIFORM_NAME_MVP + #define RL_DEFAULT_SHADER_UNIFORM_NAME_MVP "mvp" // model-view-projection matrix +#endif +#ifndef RL_DEFAULT_SHADER_UNIFORM_NAME_VIEW + #define RL_DEFAULT_SHADER_UNIFORM_NAME_VIEW "matView" // view matrix +#endif +#ifndef RL_DEFAULT_SHADER_UNIFORM_NAME_PROJECTION + #define RL_DEFAULT_SHADER_UNIFORM_NAME_PROJECTION "matProjection" // projection matrix +#endif +#ifndef RL_DEFAULT_SHADER_UNIFORM_NAME_MODEL + #define RL_DEFAULT_SHADER_UNIFORM_NAME_MODEL "matModel" // model matrix +#endif +#ifndef RL_DEFAULT_SHADER_UNIFORM_NAME_NORMAL + #define RL_DEFAULT_SHADER_UNIFORM_NAME_NORMAL "matNormal" // normal matrix (transpose(inverse(matModelView)) +#endif +#ifndef RL_DEFAULT_SHADER_UNIFORM_NAME_COLOR + #define RL_DEFAULT_SHADER_UNIFORM_NAME_COLOR "colDiffuse" // color diffuse (base tint color, multiplied by texture color) +#endif +#ifndef RL_DEFAULT_SHADER_UNIFORM_NAME_BONE_MATRICES + #define RL_DEFAULT_SHADER_UNIFORM_NAME_BONE_MATRICES "boneMatrices" // bone matrices +#endif +#ifndef RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE0 + #define RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE0 "texture0" // texture0 (texture slot active 0) +#endif +#ifndef RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE1 + #define RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE1 "texture1" // texture1 (texture slot active 1) +#endif +#ifndef RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE2 + #define RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE2 "texture2" // texture2 (texture slot active 2) +#endif + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) +typedef struct rlglData { + rlRenderBatch *currentBatch; // Current render batch + rlRenderBatch defaultBatch; // Default internal render batch + + struct { + int vertexCounter; // Current active render batch vertex counter (generic, used for all batches) + float texcoordx, texcoordy; // Current active texture coordinate (added on glVertex*()) + float normalx, normaly, normalz; // Current active normal (added on glVertex*()) + unsigned char colorr, colorg, colorb, colora; // Current active color (added on glVertex*()) + + int currentMatrixMode; // Current matrix mode + Matrix *currentMatrix; // Current matrix pointer + Matrix modelview; // Default modelview matrix + Matrix projection; // Default projection matrix + Matrix transform; // Transform matrix to be used with rlTranslate, rlRotate, rlScale + bool transformRequired; // Require transform matrix application to current draw-call vertex (if required) + Matrix stack[RL_MAX_MATRIX_STACK_SIZE];// Matrix stack for push/pop + int stackCounter; // Matrix stack counter + + unsigned int defaultTextureId; // Default texture used on shapes/poly drawing (required by shader) + unsigned int activeTextureId[RL_DEFAULT_BATCH_MAX_TEXTURE_UNITS]; // Active texture ids to be enabled on batch drawing (0 active by default) + unsigned int defaultVShaderId; // Default vertex shader id (used by default shader program) + unsigned int defaultFShaderId; // Default fragment shader id (used by default shader program) + unsigned int defaultShaderId; // Default shader program id, supports vertex color and diffuse texture + int *defaultShaderLocs; // Default shader locations pointer to be used on rendering + unsigned int currentShaderId; // Current shader id to be used on rendering (by default, defaultShaderId) + int *currentShaderLocs; // Current shader locations pointer to be used on rendering (by default, defaultShaderLocs) + + bool stereoRender; // Stereo rendering flag + Matrix projectionStereo[2]; // VR stereo rendering eyes projection matrices + Matrix viewOffsetStereo[2]; // VR stereo rendering eyes view offset matrices + + // Blending variables + int currentBlendMode; // Blending mode active + int glBlendSrcFactor; // Blending source factor + int glBlendDstFactor; // Blending destination factor + int glBlendEquation; // Blending equation + int glBlendSrcFactorRGB; // Blending source RGB factor + int glBlendDestFactorRGB; // Blending destination RGB factor + int glBlendSrcFactorAlpha; // Blending source alpha factor + int glBlendDestFactorAlpha; // Blending destination alpha factor + int glBlendEquationRGB; // Blending equation for RGB + int glBlendEquationAlpha; // Blending equation for alpha + bool glCustomBlendModeModified; // Custom blending factor and equation modification status + + int framebufferWidth; // Current framebuffer width + int framebufferHeight; // Current framebuffer height + + } State; // Renderer state + struct { + bool vao; // VAO support (OpenGL ES2 could not support VAO extension) (GL_ARB_vertex_array_object) + bool instancing; // Instancing supported (GL_ANGLE_instanced_arrays, GL_EXT_draw_instanced + GL_EXT_instanced_arrays) + bool texNPOT; // NPOT textures full support (GL_ARB_texture_non_power_of_two, GL_OES_texture_npot) + bool texDepth; // Depth textures supported (GL_ARB_depth_texture, GL_OES_depth_texture) + bool texDepthWebGL; // Depth textures supported WebGL specific (GL_WEBGL_depth_texture) + bool texFloat32; // float textures support (32 bit per channel) (GL_OES_texture_float) + bool texFloat16; // half float textures support (16 bit per channel) (GL_OES_texture_half_float) + bool texCompDXT; // DDS texture compression support (GL_EXT_texture_compression_s3tc, GL_WEBGL_compressed_texture_s3tc, GL_WEBKIT_WEBGL_compressed_texture_s3tc) + bool texCompETC1; // ETC1 texture compression support (GL_OES_compressed_ETC1_RGB8_texture, GL_WEBGL_compressed_texture_etc1) + bool texCompETC2; // ETC2/EAC texture compression support (GL_ARB_ES3_compatibility) + bool texCompPVRT; // PVR texture compression support (GL_IMG_texture_compression_pvrtc) + bool texCompASTC; // ASTC texture compression support (GL_KHR_texture_compression_astc_hdr, GL_KHR_texture_compression_astc_ldr) + bool texMirrorClamp; // Clamp mirror wrap mode supported (GL_EXT_texture_mirror_clamp) + bool texAnisoFilter; // Anisotropic texture filtering support (GL_EXT_texture_filter_anisotropic) + bool computeShader; // Compute shaders support (GL_ARB_compute_shader) + bool ssbo; // Shader storage buffer object support (GL_ARB_shader_storage_buffer_object) + + float maxAnisotropyLevel; // Maximum anisotropy level supported (minimum is 2.0f) + int maxDepthBits; // Maximum bits for depth component + + } ExtSupported; // Extensions supported flags +} rlglData; + +typedef void *(*rlglLoadProc)(const char *name); // OpenGL extension functions loader signature (same as GLADloadproc) + +#endif // GRAPHICS_API_OPENGL_33 || GRAPHICS_API_OPENGL_ES2 + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +static double rlCullDistanceNear = RL_CULL_DISTANCE_NEAR; +static double rlCullDistanceFar = RL_CULL_DISTANCE_FAR; + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) +static rlglData RLGL = { 0 }; +#endif // GRAPHICS_API_OPENGL_33 || GRAPHICS_API_OPENGL_ES2 + +#if defined(GRAPHICS_API_OPENGL_ES2) && !defined(GRAPHICS_API_OPENGL_ES3) +// NOTE: VAO functionality is exposed through extensions (OES) +static PFNGLGENVERTEXARRAYSOESPROC glGenVertexArrays = NULL; +static PFNGLBINDVERTEXARRAYOESPROC glBindVertexArray = NULL; +static PFNGLDELETEVERTEXARRAYSOESPROC glDeleteVertexArrays = NULL; + +// NOTE: Instancing functionality could also be available through extension +static PFNGLDRAWARRAYSINSTANCEDEXTPROC glDrawArraysInstanced = NULL; +static PFNGLDRAWELEMENTSINSTANCEDEXTPROC glDrawElementsInstanced = NULL; +static PFNGLVERTEXATTRIBDIVISOREXTPROC glVertexAttribDivisor = NULL; +#endif + +//---------------------------------------------------------------------------------- +// Module specific Functions Declaration +//---------------------------------------------------------------------------------- +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) +static void rlLoadShaderDefault(void); // Load default shader +static void rlUnloadShaderDefault(void); // Unload default shader +#if defined(RLGL_SHOW_GL_DETAILS_INFO) +static const char *rlGetCompressedFormatName(int format); // Get compressed format official GL identifier name +#endif // RLGL_SHOW_GL_DETAILS_INFO +#endif // GRAPHICS_API_OPENGL_33 || GRAPHICS_API_OPENGL_ES2 + +static int rlGetPixelDataSize(int width, int height, int format); // Get pixel data size in bytes (image or texture) + +// Auxiliar matrix math functions +typedef struct rl_float16 { + float v[16]; +} rl_float16; +static rl_float16 rlMatrixToFloatV(Matrix mat); // Get float array of matrix data +#define rlMatrixToFloat(mat) (rlMatrixToFloatV(mat).v) // Get float vector for Matrix +static Matrix rlMatrixIdentity(void); // Get identity matrix +static Matrix rlMatrixMultiply(Matrix left, Matrix right); // Multiply two matrices +static Matrix rlMatrixTranspose(Matrix mat); // Transposes provided matrix +static Matrix rlMatrixInvert(Matrix mat); // Invert provided matrix + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Matrix operations +//---------------------------------------------------------------------------------- + +#if defined(GRAPHICS_API_OPENGL_11) +// Fallback to OpenGL 1.1 function calls +//--------------------------------------- +void rlMatrixMode(int mode) +{ + switch (mode) + { + case RL_PROJECTION: glMatrixMode(GL_PROJECTION); break; + case RL_MODELVIEW: glMatrixMode(GL_MODELVIEW); break; + case RL_TEXTURE: glMatrixMode(GL_TEXTURE); break; + default: break; + } +} + +void rlFrustum(double left, double right, double bottom, double top, double znear, double zfar) +{ + glFrustum(left, right, bottom, top, znear, zfar); +} + +void rlOrtho(double left, double right, double bottom, double top, double znear, double zfar) +{ + glOrtho(left, right, bottom, top, znear, zfar); +} + +void rlPushMatrix(void) { glPushMatrix(); } +void rlPopMatrix(void) { glPopMatrix(); } +void rlLoadIdentity(void) { glLoadIdentity(); } +void rlTranslatef(float x, float y, float z) { glTranslatef(x, y, z); } +void rlRotatef(float angle, float x, float y, float z) { glRotatef(angle, x, y, z); } +void rlScalef(float x, float y, float z) { glScalef(x, y, z); } +void rlMultMatrixf(const float *matf) { glMultMatrixf(matf); } +#endif +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) +// Choose the current matrix to be transformed +void rlMatrixMode(int mode) +{ + if (mode == RL_PROJECTION) RLGL.State.currentMatrix = &RLGL.State.projection; + else if (mode == RL_MODELVIEW) RLGL.State.currentMatrix = &RLGL.State.modelview; + //else if (mode == RL_TEXTURE) // Not supported + + RLGL.State.currentMatrixMode = mode; +} + +// Push the current matrix into RLGL.State.stack +void rlPushMatrix(void) +{ + if (RLGL.State.stackCounter >= RL_MAX_MATRIX_STACK_SIZE) TRACELOG(RL_LOG_ERROR, "RLGL: Matrix stack overflow (RL_MAX_MATRIX_STACK_SIZE)"); + + if (RLGL.State.currentMatrixMode == RL_MODELVIEW) + { + RLGL.State.transformRequired = true; + RLGL.State.currentMatrix = &RLGL.State.transform; + } + + RLGL.State.stack[RLGL.State.stackCounter] = *RLGL.State.currentMatrix; + RLGL.State.stackCounter++; +} + +// Pop lattest inserted matrix from RLGL.State.stack +void rlPopMatrix(void) +{ + if (RLGL.State.stackCounter > 0) + { + Matrix mat = RLGL.State.stack[RLGL.State.stackCounter - 1]; + *RLGL.State.currentMatrix = mat; + RLGL.State.stackCounter--; + } + + if ((RLGL.State.stackCounter == 0) && (RLGL.State.currentMatrixMode == RL_MODELVIEW)) + { + RLGL.State.currentMatrix = &RLGL.State.modelview; + RLGL.State.transformRequired = false; + } +} + +// Reset current matrix to identity matrix +void rlLoadIdentity(void) +{ + *RLGL.State.currentMatrix = rlMatrixIdentity(); +} + +// Multiply the current matrix by a translation matrix +void rlTranslatef(float x, float y, float z) +{ + Matrix matTranslation = { + 1.0f, 0.0f, 0.0f, x, + 0.0f, 1.0f, 0.0f, y, + 0.0f, 0.0f, 1.0f, z, + 0.0f, 0.0f, 0.0f, 1.0f + }; + + // NOTE: We transpose matrix with multiplication order + *RLGL.State.currentMatrix = rlMatrixMultiply(matTranslation, *RLGL.State.currentMatrix); +} + +// Multiply the current matrix by a rotation matrix +// NOTE: The provided angle must be in degrees +void rlRotatef(float angle, float x, float y, float z) +{ + Matrix matRotation = rlMatrixIdentity(); + + // Axis vector (x, y, z) normalization + float lengthSquared = x*x + y*y + z*z; + if ((lengthSquared != 1.0f) && (lengthSquared != 0.0f)) + { + float inverseLength = 1.0f/sqrtf(lengthSquared); + x *= inverseLength; + y *= inverseLength; + z *= inverseLength; + } + + // Rotation matrix generation + float sinres = sinf(DEG2RAD*angle); + float cosres = cosf(DEG2RAD*angle); + float t = 1.0f - cosres; + + matRotation.m0 = x*x*t + cosres; + matRotation.m1 = y*x*t + z*sinres; + matRotation.m2 = z*x*t - y*sinres; + matRotation.m3 = 0.0f; + + matRotation.m4 = x*y*t - z*sinres; + matRotation.m5 = y*y*t + cosres; + matRotation.m6 = z*y*t + x*sinres; + matRotation.m7 = 0.0f; + + matRotation.m8 = x*z*t + y*sinres; + matRotation.m9 = y*z*t - x*sinres; + matRotation.m10 = z*z*t + cosres; + matRotation.m11 = 0.0f; + + matRotation.m12 = 0.0f; + matRotation.m13 = 0.0f; + matRotation.m14 = 0.0f; + matRotation.m15 = 1.0f; + + // NOTE: We transpose matrix with multiplication order + *RLGL.State.currentMatrix = rlMatrixMultiply(matRotation, *RLGL.State.currentMatrix); +} + +// Multiply the current matrix by a scaling matrix +void rlScalef(float x, float y, float z) +{ + Matrix matScale = { + x, 0.0f, 0.0f, 0.0f, + 0.0f, y, 0.0f, 0.0f, + 0.0f, 0.0f, z, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f + }; + + // NOTE: We transpose matrix with multiplication order + *RLGL.State.currentMatrix = rlMatrixMultiply(matScale, *RLGL.State.currentMatrix); +} + +// Multiply the current matrix by another matrix +void rlMultMatrixf(const float *matf) +{ + // Matrix creation from array + Matrix mat = { matf[0], matf[4], matf[8], matf[12], + matf[1], matf[5], matf[9], matf[13], + matf[2], matf[6], matf[10], matf[14], + matf[3], matf[7], matf[11], matf[15] }; + + *RLGL.State.currentMatrix = rlMatrixMultiply(mat, *RLGL.State.currentMatrix); +} + +// Multiply the current matrix by a perspective matrix generated by parameters +void rlFrustum(double left, double right, double bottom, double top, double znear, double zfar) +{ + Matrix matFrustum = { 0 }; + + float rl = (float)(right - left); + float tb = (float)(top - bottom); + float fn = (float)(zfar - znear); + + matFrustum.m0 = ((float) znear*2.0f)/rl; + matFrustum.m1 = 0.0f; + matFrustum.m2 = 0.0f; + matFrustum.m3 = 0.0f; + + matFrustum.m4 = 0.0f; + matFrustum.m5 = ((float) znear*2.0f)/tb; + matFrustum.m6 = 0.0f; + matFrustum.m7 = 0.0f; + + matFrustum.m8 = ((float)right + (float)left)/rl; + matFrustum.m9 = ((float)top + (float)bottom)/tb; + matFrustum.m10 = -((float)zfar + (float)znear)/fn; + matFrustum.m11 = -1.0f; + + matFrustum.m12 = 0.0f; + matFrustum.m13 = 0.0f; + matFrustum.m14 = -((float)zfar*(float)znear*2.0f)/fn; + matFrustum.m15 = 0.0f; + + *RLGL.State.currentMatrix = rlMatrixMultiply(*RLGL.State.currentMatrix, matFrustum); +} + +// Multiply the current matrix by an orthographic matrix generated by parameters +void rlOrtho(double left, double right, double bottom, double top, double znear, double zfar) +{ + // NOTE: If left-right and top-botton values are equal it could create a division by zero, + // response to it is platform/compiler dependant + Matrix matOrtho = { 0 }; + + float rl = (float)(right - left); + float tb = (float)(top - bottom); + float fn = (float)(zfar - znear); + + matOrtho.m0 = 2.0f/rl; + matOrtho.m1 = 0.0f; + matOrtho.m2 = 0.0f; + matOrtho.m3 = 0.0f; + matOrtho.m4 = 0.0f; + matOrtho.m5 = 2.0f/tb; + matOrtho.m6 = 0.0f; + matOrtho.m7 = 0.0f; + matOrtho.m8 = 0.0f; + matOrtho.m9 = 0.0f; + matOrtho.m10 = -2.0f/fn; + matOrtho.m11 = 0.0f; + matOrtho.m12 = -((float)left + (float)right)/rl; + matOrtho.m13 = -((float)top + (float)bottom)/tb; + matOrtho.m14 = -((float)zfar + (float)znear)/fn; + matOrtho.m15 = 1.0f; + + *RLGL.State.currentMatrix = rlMatrixMultiply(*RLGL.State.currentMatrix, matOrtho); +} +#endif + +// Set the viewport area (transformation from normalized device coordinates to window coordinates) +// NOTE: We store current viewport dimensions +void rlViewport(int x, int y, int width, int height) +{ + glViewport(x, y, width, height); +} + +// Set clip planes distances +void rlSetClipPlanes(double nearPlane, double farPlane) +{ + rlCullDistanceNear = nearPlane; + rlCullDistanceFar = farPlane; +} + +// Get cull plane distance near +double rlGetCullDistanceNear(void) +{ + return rlCullDistanceNear; +} + +// Get cull plane distance far +double rlGetCullDistanceFar(void) +{ + return rlCullDistanceFar; +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Vertex level operations +//---------------------------------------------------------------------------------- +#if defined(GRAPHICS_API_OPENGL_11) +// Fallback to OpenGL 1.1 function calls +//--------------------------------------- +void rlBegin(int mode) +{ + switch (mode) + { + case RL_LINES: glBegin(GL_LINES); break; + case RL_TRIANGLES: glBegin(GL_TRIANGLES); break; + case RL_QUADS: glBegin(GL_QUADS); break; + default: break; + } +} + +void rlEnd(void) { glEnd(); } +void rlVertex2i(int x, int y) { glVertex2i(x, y); } +void rlVertex2f(float x, float y) { glVertex2f(x, y); } +void rlVertex3f(float x, float y, float z) { glVertex3f(x, y, z); } +void rlTexCoord2f(float x, float y) { glTexCoord2f(x, y); } +void rlNormal3f(float x, float y, float z) { glNormal3f(x, y, z); } +void rlColor4ub(unsigned char r, unsigned char g, unsigned char b, unsigned char a) { glColor4ub(r, g, b, a); } +void rlColor3f(float x, float y, float z) { glColor3f(x, y, z); } +void rlColor4f(float x, float y, float z, float w) { glColor4f(x, y, z, w); } +#endif +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) +// Initialize drawing mode (how to organize vertex) +void rlBegin(int mode) +{ + // Draw mode can be RL_LINES, RL_TRIANGLES and RL_QUADS + // NOTE: In all three cases, vertex are accumulated over default internal vertex buffer + if (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode != mode) + { + if (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount > 0) + { + // Make sure current RLGL.currentBatch->draws[i].vertexCount is aligned a multiple of 4, + // that way, following QUADS drawing will keep aligned with index processing + // It implies adding some extra alignment vertex at the end of the draw, + // those vertex are not processed but they are considered as an additional offset + // for the next set of vertex to be drawn + if (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode == RL_LINES) RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexAlignment = ((RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount < 4)? RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount : RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount%4); + else if (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode == RL_TRIANGLES) RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexAlignment = ((RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount < 4)? 1 : (4 - (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount%4))); + else RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexAlignment = 0; + + if (!rlCheckRenderBatchLimit(RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexAlignment)) + { + RLGL.State.vertexCounter += RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexAlignment; + RLGL.currentBatch->drawCounter++; + } + } + + if (RLGL.currentBatch->drawCounter >= RL_DEFAULT_BATCH_DRAWCALLS) rlDrawRenderBatch(RLGL.currentBatch); + + RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode = mode; + RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount = 0; + RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].textureId = RLGL.State.defaultTextureId; + } +} + +// Finish vertex providing +void rlEnd(void) +{ + // NOTE: Depth increment is dependant on rlOrtho(): z-near and z-far values, + // as well as depth buffer bit-depth (16bit or 24bit or 32bit) + // Correct increment formula would be: depthInc = (zfar - znear)/pow(2, bits) + RLGL.currentBatch->currentDepth += (1.0f/20000.0f); +} + +// Define one vertex (position) +// NOTE: Vertex position data is the basic information required for drawing +void rlVertex3f(float x, float y, float z) +{ + float tx = x; + float ty = y; + float tz = z; + + // Transform provided vector if required + if (RLGL.State.transformRequired) + { + tx = RLGL.State.transform.m0*x + RLGL.State.transform.m4*y + RLGL.State.transform.m8*z + RLGL.State.transform.m12; + ty = RLGL.State.transform.m1*x + RLGL.State.transform.m5*y + RLGL.State.transform.m9*z + RLGL.State.transform.m13; + tz = RLGL.State.transform.m2*x + RLGL.State.transform.m6*y + RLGL.State.transform.m10*z + RLGL.State.transform.m14; + } + + // WARNING: We can't break primitives when launching a new batch + // RL_LINES comes in pairs, RL_TRIANGLES come in groups of 3 vertices and RL_QUADS come in groups of 4 vertices + // We must check current draw.mode when a new vertex is required and finish the batch only if the draw.mode draw.vertexCount is %2, %3 or %4 + if (RLGL.State.vertexCounter > (RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].elementCount*4 - 4)) + { + if ((RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode == RL_LINES) && + (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount%2 == 0)) + { + // Reached the maximum number of vertices for RL_LINES drawing + // Launch a draw call but keep current state for next vertices comming + // NOTE: We add +1 vertex to the check for security + rlCheckRenderBatchLimit(2 + 1); + } + else if ((RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode == RL_TRIANGLES) && + (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount%3 == 0)) + { + rlCheckRenderBatchLimit(3 + 1); + } + else if ((RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode == RL_QUADS) && + (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount%4 == 0)) + { + rlCheckRenderBatchLimit(4 + 1); + } + } + + // Add vertices + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].vertices[3*RLGL.State.vertexCounter] = tx; + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].vertices[3*RLGL.State.vertexCounter + 1] = ty; + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].vertices[3*RLGL.State.vertexCounter + 2] = tz; + + // Add current texcoord + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].texcoords[2*RLGL.State.vertexCounter] = RLGL.State.texcoordx; + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].texcoords[2*RLGL.State.vertexCounter + 1] = RLGL.State.texcoordy; + + // Add current normal + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].normals[3*RLGL.State.vertexCounter] = RLGL.State.normalx; + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].normals[3*RLGL.State.vertexCounter + 1] = RLGL.State.normaly; + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].normals[3*RLGL.State.vertexCounter + 2] = RLGL.State.normalz; + + // Add current color + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].colors[4*RLGL.State.vertexCounter] = RLGL.State.colorr; + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].colors[4*RLGL.State.vertexCounter + 1] = RLGL.State.colorg; + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].colors[4*RLGL.State.vertexCounter + 2] = RLGL.State.colorb; + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].colors[4*RLGL.State.vertexCounter + 3] = RLGL.State.colora; + + RLGL.State.vertexCounter++; + RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount++; +} + +// Define one vertex (position) +void rlVertex2f(float x, float y) +{ + rlVertex3f(x, y, RLGL.currentBatch->currentDepth); +} + +// Define one vertex (position) +void rlVertex2i(int x, int y) +{ + rlVertex3f((float)x, (float)y, RLGL.currentBatch->currentDepth); +} + +// Define one vertex (texture coordinate) +// NOTE: Texture coordinates are limited to QUADS only +void rlTexCoord2f(float x, float y) +{ + RLGL.State.texcoordx = x; + RLGL.State.texcoordy = y; +} + +// Define one vertex (normal) +// NOTE: Normals limited to TRIANGLES only? +void rlNormal3f(float x, float y, float z) +{ + float normalx = x; + float normaly = y; + float normalz = z; + if (RLGL.State.transformRequired) + { + normalx = RLGL.State.transform.m0*x + RLGL.State.transform.m4*y + RLGL.State.transform.m8*z; + normaly = RLGL.State.transform.m1*x + RLGL.State.transform.m5*y + RLGL.State.transform.m9*z; + normalz = RLGL.State.transform.m2*x + RLGL.State.transform.m6*y + RLGL.State.transform.m10*z; + } + float length = sqrtf(normalx*normalx + normaly*normaly + normalz*normalz); + if (length != 0.0f) + { + float ilength = 1.0f/length; + normalx *= ilength; + normaly *= ilength; + normalz *= ilength; + } + RLGL.State.normalx = normalx; + RLGL.State.normaly = normaly; + RLGL.State.normalz = normalz; +} + +// Define one vertex (color) +void rlColor4ub(unsigned char x, unsigned char y, unsigned char z, unsigned char w) +{ + RLGL.State.colorr = x; + RLGL.State.colorg = y; + RLGL.State.colorb = z; + RLGL.State.colora = w; +} + +// Define one vertex (color) +void rlColor4f(float r, float g, float b, float a) +{ + rlColor4ub((unsigned char)(r*255), (unsigned char)(g*255), (unsigned char)(b*255), (unsigned char)(a*255)); +} + +// Define one vertex (color) +void rlColor3f(float x, float y, float z) +{ + rlColor4ub((unsigned char)(x*255), (unsigned char)(y*255), (unsigned char)(z*255), 255); +} + +#endif + +//-------------------------------------------------------------------------------------- +// Module Functions Definition - OpenGL style functions (common to 1.1, 3.3+, ES2) +//-------------------------------------------------------------------------------------- + +// Set current texture to use +void rlSetTexture(unsigned int id) +{ + if (id == 0) + { +#if defined(GRAPHICS_API_OPENGL_11) + rlDisableTexture(); +#else + // NOTE: If quads batch limit is reached, we force a draw call and next batch starts + if (RLGL.State.vertexCounter >= + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].elementCount*4) + { + rlDrawRenderBatch(RLGL.currentBatch); + } +#endif + } + else + { +#if defined(GRAPHICS_API_OPENGL_11) + rlEnableTexture(id); +#else + if (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].textureId != id) + { + if (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount > 0) + { + // Make sure current RLGL.currentBatch->draws[i].vertexCount is aligned a multiple of 4, + // that way, following QUADS drawing will keep aligned with index processing + // It implies adding some extra alignment vertex at the end of the draw, + // those vertex are not processed but they are considered as an additional offset + // for the next set of vertex to be drawn + if (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode == RL_LINES) RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexAlignment = ((RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount < 4)? RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount : RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount%4); + else if (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode == RL_TRIANGLES) RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexAlignment = ((RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount < 4)? 1 : (4 - (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount%4))); + else RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexAlignment = 0; + + if (!rlCheckRenderBatchLimit(RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexAlignment)) + { + RLGL.State.vertexCounter += RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexAlignment; + + RLGL.currentBatch->drawCounter++; + } + } + + if (RLGL.currentBatch->drawCounter >= RL_DEFAULT_BATCH_DRAWCALLS) rlDrawRenderBatch(RLGL.currentBatch); + + RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].textureId = id; + RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount = 0; + } +#endif + } +} + +// Select and active a texture slot +void rlActiveTextureSlot(int slot) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glActiveTexture(GL_TEXTURE0 + slot); +#endif +} + +// Enable texture +void rlEnableTexture(unsigned int id) +{ +#if defined(GRAPHICS_API_OPENGL_11) + glEnable(GL_TEXTURE_2D); +#endif + glBindTexture(GL_TEXTURE_2D, id); +} + +// Disable texture +void rlDisableTexture(void) +{ +#if defined(GRAPHICS_API_OPENGL_11) + glDisable(GL_TEXTURE_2D); +#endif + glBindTexture(GL_TEXTURE_2D, 0); +} + +// Enable texture cubemap +void rlEnableTextureCubemap(unsigned int id) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glBindTexture(GL_TEXTURE_CUBE_MAP, id); +#endif +} + +// Disable texture cubemap +void rlDisableTextureCubemap(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glBindTexture(GL_TEXTURE_CUBE_MAP, 0); +#endif +} + +// Set texture parameters (wrap mode/filter mode) +void rlTextureParameters(unsigned int id, int param, int value) +{ + glBindTexture(GL_TEXTURE_2D, id); + +#if !defined(GRAPHICS_API_OPENGL_11) + // Reset anisotropy filter, in case it was set + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1.0f); +#endif + + switch (param) + { + case RL_TEXTURE_WRAP_S: + case RL_TEXTURE_WRAP_T: + { + if (value == RL_TEXTURE_WRAP_MIRROR_CLAMP) + { +#if !defined(GRAPHICS_API_OPENGL_11) + if (RLGL.ExtSupported.texMirrorClamp) glTexParameteri(GL_TEXTURE_2D, param, value); + else TRACELOG(RL_LOG_WARNING, "GL: Clamp mirror wrap mode not supported (GL_MIRROR_CLAMP_EXT)"); +#endif + } + else glTexParameteri(GL_TEXTURE_2D, param, value); + + } break; + case RL_TEXTURE_MAG_FILTER: + case RL_TEXTURE_MIN_FILTER: glTexParameteri(GL_TEXTURE_2D, param, value); break; + case RL_TEXTURE_FILTER_ANISOTROPIC: + { +#if !defined(GRAPHICS_API_OPENGL_11) + if (value <= RLGL.ExtSupported.maxAnisotropyLevel) glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, (float)value); + else if (RLGL.ExtSupported.maxAnisotropyLevel > 0.0f) + { + TRACELOG(RL_LOG_WARNING, "GL: Maximum anisotropic filter level supported is %iX", id, (int)RLGL.ExtSupported.maxAnisotropyLevel); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, (float)value); + } + else TRACELOG(RL_LOG_WARNING, "GL: Anisotropic filtering not supported"); +#endif + } break; +#if defined(GRAPHICS_API_OPENGL_33) + case RL_TEXTURE_MIPMAP_BIAS_RATIO: glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, value/100.0f); +#endif + default: break; + } + + glBindTexture(GL_TEXTURE_2D, 0); +} + +// Set cubemap parameters (wrap mode/filter mode) +void rlCubemapParameters(unsigned int id, int param, int value) +{ +#if !defined(GRAPHICS_API_OPENGL_11) + glBindTexture(GL_TEXTURE_CUBE_MAP, id); + + // Reset anisotropy filter, in case it was set + glTexParameterf(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1.0f); + + switch (param) + { + case RL_TEXTURE_WRAP_S: + case RL_TEXTURE_WRAP_T: + { + if (value == RL_TEXTURE_WRAP_MIRROR_CLAMP) + { + if (RLGL.ExtSupported.texMirrorClamp) glTexParameteri(GL_TEXTURE_CUBE_MAP, param, value); + else TRACELOG(RL_LOG_WARNING, "GL: Clamp mirror wrap mode not supported (GL_MIRROR_CLAMP_EXT)"); + } + else glTexParameteri(GL_TEXTURE_CUBE_MAP, param, value); + + } break; + case RL_TEXTURE_MAG_FILTER: + case RL_TEXTURE_MIN_FILTER: glTexParameteri(GL_TEXTURE_CUBE_MAP, param, value); break; + case RL_TEXTURE_FILTER_ANISOTROPIC: + { + if (value <= RLGL.ExtSupported.maxAnisotropyLevel) glTexParameterf(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAX_ANISOTROPY_EXT, (float)value); + else if (RLGL.ExtSupported.maxAnisotropyLevel > 0.0f) + { + TRACELOG(RL_LOG_WARNING, "GL: Maximum anisotropic filter level supported is %iX", id, (int)RLGL.ExtSupported.maxAnisotropyLevel); + glTexParameterf(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAX_ANISOTROPY_EXT, (float)value); + } + else TRACELOG(RL_LOG_WARNING, "GL: Anisotropic filtering not supported"); + } break; +#if defined(GRAPHICS_API_OPENGL_33) + case RL_TEXTURE_MIPMAP_BIAS_RATIO: glTexParameterf(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_LOD_BIAS, value/100.0f); +#endif + default: break; + } + + glBindTexture(GL_TEXTURE_CUBE_MAP, 0); +#endif +} + +// Enable shader program +void rlEnableShader(unsigned int id) +{ +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) + glUseProgram(id); +#endif +} + +// Disable shader program +void rlDisableShader(void) +{ +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) + glUseProgram(0); +#endif +} + +// Enable rendering to texture (fbo) +void rlEnableFramebuffer(unsigned int id) +{ +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) && defined(RLGL_RENDER_TEXTURES_HINT) + glBindFramebuffer(GL_FRAMEBUFFER, id); +#endif +} + +// return the active render texture (fbo) +unsigned int rlGetActiveFramebuffer(void) +{ + GLint fboId = 0; +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES3)) && defined(RLGL_RENDER_TEXTURES_HINT) + glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &fboId); +#endif + return fboId; +} + +// Disable rendering to texture +void rlDisableFramebuffer(void) +{ +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) && defined(RLGL_RENDER_TEXTURES_HINT) + glBindFramebuffer(GL_FRAMEBUFFER, 0); +#endif +} + +// Blit active framebuffer to main framebuffer +void rlBlitFramebuffer(int srcX, int srcY, int srcWidth, int srcHeight, int dstX, int dstY, int dstWidth, int dstHeight, int bufferMask) +{ +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES3)) && defined(RLGL_RENDER_TEXTURES_HINT) + glBlitFramebuffer(srcX, srcY, srcWidth, srcHeight, dstX, dstY, dstWidth, dstHeight, bufferMask, GL_NEAREST); +#endif +} + +// Bind framebuffer object (fbo) +void rlBindFramebuffer(unsigned int target, unsigned int framebuffer) +{ +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) && defined(RLGL_RENDER_TEXTURES_HINT) + glBindFramebuffer(target, framebuffer); +#endif +} + +// Activate multiple draw color buffers +// NOTE: One color buffer is always active by default +void rlActiveDrawBuffers(int count) +{ +#if ((defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES3)) && defined(RLGL_RENDER_TEXTURES_HINT)) + // NOTE: Maximum number of draw buffers supported is implementation dependant, + // it can be queried with glGet*() but it must be at least 8 + //GLint maxDrawBuffers = 0; + //glGetIntegerv(GL_MAX_DRAW_BUFFERS, &maxDrawBuffers); + + if (count > 0) + { + if (count > 8) TRACELOG(LOG_WARNING, "GL: Max color buffers limited to 8"); + else + { + unsigned int buffers[8] = { +#if defined(GRAPHICS_API_OPENGL_ES3) + GL_COLOR_ATTACHMENT0_EXT, + GL_COLOR_ATTACHMENT1_EXT, + GL_COLOR_ATTACHMENT2_EXT, + GL_COLOR_ATTACHMENT3_EXT, + GL_COLOR_ATTACHMENT4_EXT, + GL_COLOR_ATTACHMENT5_EXT, + GL_COLOR_ATTACHMENT6_EXT, + GL_COLOR_ATTACHMENT7_EXT, +#else + GL_COLOR_ATTACHMENT0, + GL_COLOR_ATTACHMENT1, + GL_COLOR_ATTACHMENT2, + GL_COLOR_ATTACHMENT3, + GL_COLOR_ATTACHMENT4, + GL_COLOR_ATTACHMENT5, + GL_COLOR_ATTACHMENT6, + GL_COLOR_ATTACHMENT7, +#endif + }; + +#if defined(GRAPHICS_API_OPENGL_ES3) + glDrawBuffersEXT(count, buffers); +#else + glDrawBuffers(count, buffers); +#endif + } + } + else TRACELOG(LOG_WARNING, "GL: One color buffer active by default"); +#endif +} + +//---------------------------------------------------------------------------------- +// General render state configuration +//---------------------------------------------------------------------------------- + +// Enable color blending +void rlEnableColorBlend(void) { glEnable(GL_BLEND); } + +// Disable color blending +void rlDisableColorBlend(void) { glDisable(GL_BLEND); } + +// Enable depth test +void rlEnableDepthTest(void) { glEnable(GL_DEPTH_TEST); } + +// Disable depth test +void rlDisableDepthTest(void) { glDisable(GL_DEPTH_TEST); } + +// Enable depth write +void rlEnableDepthMask(void) { glDepthMask(GL_TRUE); } + +// Disable depth write +void rlDisableDepthMask(void) { glDepthMask(GL_FALSE); } + +// Enable backface culling +void rlEnableBackfaceCulling(void) { glEnable(GL_CULL_FACE); } + +// Disable backface culling +void rlDisableBackfaceCulling(void) { glDisable(GL_CULL_FACE); } + +// Set color mask active for screen read/draw +void rlColorMask(bool r, bool g, bool b, bool a) { glColorMask(r, g, b, a); } + +// Set face culling mode +void rlSetCullFace(int mode) +{ + switch (mode) + { + case RL_CULL_FACE_BACK: glCullFace(GL_BACK); break; + case RL_CULL_FACE_FRONT: glCullFace(GL_FRONT); break; + default: break; + } +} + +// Enable scissor test +void rlEnableScissorTest(void) { glEnable(GL_SCISSOR_TEST); } + +// Disable scissor test +void rlDisableScissorTest(void) { glDisable(GL_SCISSOR_TEST); } + +// Scissor test +void rlScissor(int x, int y, int width, int height) { glScissor(x, y, width, height); } + +// Enable wire mode +void rlEnableWireMode(void) +{ +#if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33) + // NOTE: glPolygonMode() not available on OpenGL ES + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); +#endif +} + +// Enable point mode +void rlEnablePointMode(void) +{ +#if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33) + // NOTE: glPolygonMode() not available on OpenGL ES + glPolygonMode(GL_FRONT_AND_BACK, GL_POINT); + glEnable(GL_PROGRAM_POINT_SIZE); +#endif +} + +// Disable wire mode +void rlDisableWireMode(void) +{ +#if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33) + // NOTE: glPolygonMode() not available on OpenGL ES + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); +#endif +} + +// Set the line drawing width +void rlSetLineWidth(float width) { glLineWidth(width); } + +// Get the line drawing width +float rlGetLineWidth(void) +{ + float width = 0; + glGetFloatv(GL_LINE_WIDTH, &width); + return width; +} + +// Enable line aliasing +void rlEnableSmoothLines(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_11) + glEnable(GL_LINE_SMOOTH); +#endif +} + +// Disable line aliasing +void rlDisableSmoothLines(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_11) + glDisable(GL_LINE_SMOOTH); +#endif +} + +// Enable stereo rendering +void rlEnableStereoRender(void) +{ +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) + RLGL.State.stereoRender = true; +#endif +} + +// Disable stereo rendering +void rlDisableStereoRender(void) +{ +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) + RLGL.State.stereoRender = false; +#endif +} + +// Check if stereo render is enabled +bool rlIsStereoRenderEnabled(void) +{ +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) + return RLGL.State.stereoRender; +#else + return false; +#endif +} + +// Clear color buffer with color +void rlClearColor(unsigned char r, unsigned char g, unsigned char b, unsigned char a) +{ + // Color values clamp to 0.0f(0) and 1.0f(255) + float cr = (float)r/255; + float cg = (float)g/255; + float cb = (float)b/255; + float ca = (float)a/255; + + glClearColor(cr, cg, cb, ca); +} + +// Clear used screen buffers (color and depth) +void rlClearScreenBuffers(void) +{ + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear used buffers: Color and Depth (Depth is used for 3D) + //glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); // Stencil buffer not used... +} + +// Check and log OpenGL error codes +void rlCheckErrors(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + int check = 1; + while (check) + { + const GLenum err = glGetError(); + switch (err) + { + case GL_NO_ERROR: check = 0; break; + case 0x0500: TRACELOG(RL_LOG_WARNING, "GL: Error detected: GL_INVALID_ENUM"); break; + case 0x0501: TRACELOG(RL_LOG_WARNING, "GL: Error detected: GL_INVALID_VALUE"); break; + case 0x0502: TRACELOG(RL_LOG_WARNING, "GL: Error detected: GL_INVALID_OPERATION"); break; + case 0x0503: TRACELOG(RL_LOG_WARNING, "GL: Error detected: GL_STACK_OVERFLOW"); break; + case 0x0504: TRACELOG(RL_LOG_WARNING, "GL: Error detected: GL_STACK_UNDERFLOW"); break; + case 0x0505: TRACELOG(RL_LOG_WARNING, "GL: Error detected: GL_OUT_OF_MEMORY"); break; + case 0x0506: TRACELOG(RL_LOG_WARNING, "GL: Error detected: GL_INVALID_FRAMEBUFFER_OPERATION"); break; + default: TRACELOG(RL_LOG_WARNING, "GL: Error detected: Unknown error code: %x", err); break; + } + } +#endif +} + +// Set blend mode +void rlSetBlendMode(int mode) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + if ((RLGL.State.currentBlendMode != mode) || ((mode == RL_BLEND_CUSTOM || mode == RL_BLEND_CUSTOM_SEPARATE) && RLGL.State.glCustomBlendModeModified)) + { + rlDrawRenderBatch(RLGL.currentBatch); + + switch (mode) + { + case RL_BLEND_ALPHA: glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glBlendEquation(GL_FUNC_ADD); break; + case RL_BLEND_ADDITIVE: glBlendFunc(GL_SRC_ALPHA, GL_ONE); glBlendEquation(GL_FUNC_ADD); break; + case RL_BLEND_MULTIPLIED: glBlendFunc(GL_DST_COLOR, GL_ONE_MINUS_SRC_ALPHA); glBlendEquation(GL_FUNC_ADD); break; + case RL_BLEND_ADD_COLORS: glBlendFunc(GL_ONE, GL_ONE); glBlendEquation(GL_FUNC_ADD); break; + case RL_BLEND_SUBTRACT_COLORS: glBlendFunc(GL_ONE, GL_ONE); glBlendEquation(GL_FUNC_SUBTRACT); break; + case RL_BLEND_ALPHA_PREMULTIPLY: glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); glBlendEquation(GL_FUNC_ADD); break; + case RL_BLEND_CUSTOM: + { + // NOTE: Using GL blend src/dst factors and GL equation configured with rlSetBlendFactors() + glBlendFunc(RLGL.State.glBlendSrcFactor, RLGL.State.glBlendDstFactor); glBlendEquation(RLGL.State.glBlendEquation); + + } break; + case RL_BLEND_CUSTOM_SEPARATE: + { + // NOTE: Using GL blend src/dst factors and GL equation configured with rlSetBlendFactorsSeparate() + glBlendFuncSeparate(RLGL.State.glBlendSrcFactorRGB, RLGL.State.glBlendDestFactorRGB, RLGL.State.glBlendSrcFactorAlpha, RLGL.State.glBlendDestFactorAlpha); + glBlendEquationSeparate(RLGL.State.glBlendEquationRGB, RLGL.State.glBlendEquationAlpha); + + } break; + default: break; + } + + RLGL.State.currentBlendMode = mode; + RLGL.State.glCustomBlendModeModified = false; + } +#endif +} + +// Set blending mode factor and equation +void rlSetBlendFactors(int glSrcFactor, int glDstFactor, int glEquation) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + if ((RLGL.State.glBlendSrcFactor != glSrcFactor) || + (RLGL.State.glBlendDstFactor != glDstFactor) || + (RLGL.State.glBlendEquation != glEquation)) + { + RLGL.State.glBlendSrcFactor = glSrcFactor; + RLGL.State.glBlendDstFactor = glDstFactor; + RLGL.State.glBlendEquation = glEquation; + + RLGL.State.glCustomBlendModeModified = true; + } +#endif +} + +// Set blending mode factor and equation separately for RGB and alpha +void rlSetBlendFactorsSeparate(int glSrcRGB, int glDstRGB, int glSrcAlpha, int glDstAlpha, int glEqRGB, int glEqAlpha) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + if ((RLGL.State.glBlendSrcFactorRGB != glSrcRGB) || + (RLGL.State.glBlendDestFactorRGB != glDstRGB) || + (RLGL.State.glBlendSrcFactorAlpha != glSrcAlpha) || + (RLGL.State.glBlendDestFactorAlpha != glDstAlpha) || + (RLGL.State.glBlendEquationRGB != glEqRGB) || + (RLGL.State.glBlendEquationAlpha != glEqAlpha)) + { + RLGL.State.glBlendSrcFactorRGB = glSrcRGB; + RLGL.State.glBlendDestFactorRGB = glDstRGB; + RLGL.State.glBlendSrcFactorAlpha = glSrcAlpha; + RLGL.State.glBlendDestFactorAlpha = glDstAlpha; + RLGL.State.glBlendEquationRGB = glEqRGB; + RLGL.State.glBlendEquationAlpha = glEqAlpha; + + RLGL.State.glCustomBlendModeModified = true; + } +#endif +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition - OpenGL Debug +//---------------------------------------------------------------------------------- +#if defined(RLGL_ENABLE_OPENGL_DEBUG_CONTEXT) && defined(GRAPHICS_API_OPENGL_43) +static void GLAPIENTRY rlDebugMessageCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *message, const void *userParam) +{ + // Ignore non-significant error/warning codes (NVidia drivers) + // NOTE: Here there are the details with a sample output: + // - #131169 - Framebuffer detailed info: The driver allocated storage for renderbuffer 2. (severity: low) + // - #131185 - Buffer detailed info: Buffer object 1 (bound to GL_ELEMENT_ARRAY_BUFFER_ARB, usage hint is GL_ENUM_88e4) + // will use VIDEO memory as the source for buffer object operations. (severity: low) + // - #131218 - Program/shader state performance warning: Vertex shader in program 7 is being recompiled based on GL state. (severity: medium) + // - #131204 - Texture state usage warning: The texture object (0) bound to texture image unit 0 does not have + // a defined base level and cannot be used for texture mapping. (severity: low) + if ((id == 131169) || (id == 131185) || (id == 131218) || (id == 131204)) return; + + const char *msgSource = NULL; + switch (source) + { + case GL_DEBUG_SOURCE_API: msgSource = "API"; break; + case GL_DEBUG_SOURCE_WINDOW_SYSTEM: msgSource = "WINDOW_SYSTEM"; break; + case GL_DEBUG_SOURCE_SHADER_COMPILER: msgSource = "SHADER_COMPILER"; break; + case GL_DEBUG_SOURCE_THIRD_PARTY: msgSource = "THIRD_PARTY"; break; + case GL_DEBUG_SOURCE_APPLICATION: msgSource = "APPLICATION"; break; + case GL_DEBUG_SOURCE_OTHER: msgSource = "OTHER"; break; + default: break; + } + + const char *msgType = NULL; + switch (type) + { + case GL_DEBUG_TYPE_ERROR: msgType = "ERROR"; break; + case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: msgType = "DEPRECATED_BEHAVIOR"; break; + case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR: msgType = "UNDEFINED_BEHAVIOR"; break; + case GL_DEBUG_TYPE_PORTABILITY: msgType = "PORTABILITY"; break; + case GL_DEBUG_TYPE_PERFORMANCE: msgType = "PERFORMANCE"; break; + case GL_DEBUG_TYPE_MARKER: msgType = "MARKER"; break; + case GL_DEBUG_TYPE_PUSH_GROUP: msgType = "PUSH_GROUP"; break; + case GL_DEBUG_TYPE_POP_GROUP: msgType = "POP_GROUP"; break; + case GL_DEBUG_TYPE_OTHER: msgType = "OTHER"; break; + default: break; + } + + const char *msgSeverity = "DEFAULT"; + switch (severity) + { + case GL_DEBUG_SEVERITY_LOW: msgSeverity = "LOW"; break; + case GL_DEBUG_SEVERITY_MEDIUM: msgSeverity = "MEDIUM"; break; + case GL_DEBUG_SEVERITY_HIGH: msgSeverity = "HIGH"; break; + case GL_DEBUG_SEVERITY_NOTIFICATION: msgSeverity = "NOTIFICATION"; break; + default: break; + } + + TRACELOG(LOG_WARNING, "GL: OpenGL debug message: %s", message); + TRACELOG(LOG_WARNING, " > Type: %s", msgType); + TRACELOG(LOG_WARNING, " > Source = %s", msgSource); + TRACELOG(LOG_WARNING, " > Severity = %s", msgSeverity); +} +#endif + +//---------------------------------------------------------------------------------- +// Module Functions Definition - rlgl functionality +//---------------------------------------------------------------------------------- + +// Initialize rlgl: OpenGL extensions, default buffers/shaders/textures, OpenGL states +void rlglInit(int width, int height) +{ + // Enable OpenGL debug context if required +#if defined(RLGL_ENABLE_OPENGL_DEBUG_CONTEXT) && defined(GRAPHICS_API_OPENGL_43) + if ((glDebugMessageCallback != NULL) && (glDebugMessageControl != NULL)) + { + glDebugMessageCallback(rlDebugMessageCallback, 0); + // glDebugMessageControl(GL_DEBUG_SOURCE_API, GL_DEBUG_TYPE_ERROR, GL_DEBUG_SEVERITY_HIGH, 0, 0, GL_TRUE); + + // Debug context options: + // - GL_DEBUG_OUTPUT - Faster version but not useful for breakpoints + // - GL_DEBUG_OUTPUT_SYNCHRONUS - Callback is in sync with errors, so a breakpoint can be placed on the callback in order to get a stacktrace for the GL error + glEnable(GL_DEBUG_OUTPUT); + glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); + } +#endif + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // Init default white texture + unsigned char pixels[4] = { 255, 255, 255, 255 }; // 1 pixel RGBA (4 bytes) + RLGL.State.defaultTextureId = rlLoadTexture(pixels, 1, 1, RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, 1); + + if (RLGL.State.defaultTextureId != 0) TRACELOG(RL_LOG_INFO, "TEXTURE: [ID %i] Default texture loaded successfully", RLGL.State.defaultTextureId); + else TRACELOG(RL_LOG_WARNING, "TEXTURE: Failed to load default texture"); + + // Init default Shader (customized for GL 3.3 and ES2) + // Loaded: RLGL.State.defaultShaderId + RLGL.State.defaultShaderLocs + rlLoadShaderDefault(); + RLGL.State.currentShaderId = RLGL.State.defaultShaderId; + RLGL.State.currentShaderLocs = RLGL.State.defaultShaderLocs; + + // Init default vertex arrays buffers + // Simulate that the default shader has the location RL_SHADER_LOC_VERTEX_NORMAL to bind the normal buffer for the default render batch + RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_NORMAL] = RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL; + RLGL.defaultBatch = rlLoadRenderBatch(RL_DEFAULT_BATCH_BUFFERS, RL_DEFAULT_BATCH_BUFFER_ELEMENTS); + RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_NORMAL] = -1; + RLGL.currentBatch = &RLGL.defaultBatch; + + // Init stack matrices (emulating OpenGL 1.1) + for (int i = 0; i < RL_MAX_MATRIX_STACK_SIZE; i++) RLGL.State.stack[i] = rlMatrixIdentity(); + + // Init internal matrices + RLGL.State.transform = rlMatrixIdentity(); + RLGL.State.projection = rlMatrixIdentity(); + RLGL.State.modelview = rlMatrixIdentity(); + RLGL.State.currentMatrix = &RLGL.State.modelview; +#endif // GRAPHICS_API_OPENGL_33 || GRAPHICS_API_OPENGL_ES2 + + // Initialize OpenGL default states + //---------------------------------------------------------- + // Init state: Depth test + glDepthFunc(GL_LEQUAL); // Type of depth testing to apply + glDisable(GL_DEPTH_TEST); // Disable depth testing for 2D (only used for 3D) + + // Init state: Blending mode + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // Color blending function (how colors are mixed) + glEnable(GL_BLEND); // Enable color blending (required to work with transparencies) + + // Init state: Culling + // NOTE: All shapes/models triangles are drawn CCW + glCullFace(GL_BACK); // Cull the back face (default) + glFrontFace(GL_CCW); // Front face are defined counter clockwise (default) + glEnable(GL_CULL_FACE); // Enable backface culling + + // Init state: Cubemap seamless +#if defined(GRAPHICS_API_OPENGL_33) + glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS); // Seamless cubemaps (not supported on OpenGL ES 2.0) +#endif + +#if defined(GRAPHICS_API_OPENGL_11) + // Init state: Color hints (deprecated in OpenGL 3.0+) + glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); // Improve quality of color and texture coordinate interpolation + glShadeModel(GL_SMOOTH); // Smooth shading between vertex (vertex colors interpolation) +#endif + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // Store screen size into global variables + RLGL.State.framebufferWidth = width; + RLGL.State.framebufferHeight = height; + + TRACELOG(RL_LOG_INFO, "RLGL: Default OpenGL state initialized successfully"); + //---------------------------------------------------------- +#endif + + // Init state: Color/Depth buffers clear + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); // Set clear color (black) + glClearDepth(1.0f); // Set clear depth value (default) + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear color and depth buffers (depth buffer required for 3D) +} + +// Vertex Buffer Object deinitialization (memory free) +void rlglClose(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + rlUnloadRenderBatch(RLGL.defaultBatch); + + rlUnloadShaderDefault(); // Unload default shader + + glDeleteTextures(1, &RLGL.State.defaultTextureId); // Unload default texture + TRACELOG(RL_LOG_INFO, "TEXTURE: [ID %i] Default texture unloaded successfully", RLGL.State.defaultTextureId); +#endif +} + +// Load OpenGL extensions +// NOTE: External loader function must be provided +void rlLoadExtensions(void *loader) +{ +#if defined(GRAPHICS_API_OPENGL_33) // Also defined for GRAPHICS_API_OPENGL_21 + // NOTE: glad is generated and contains only required OpenGL 3.3 Core extensions (and lower versions) + if (gladLoadGL((GLADloadfunc)loader) == 0) TRACELOG(RL_LOG_WARNING, "GLAD: Cannot load OpenGL extensions"); + else TRACELOG(RL_LOG_INFO, "GLAD: OpenGL extensions loaded successfully"); + + // Get number of supported extensions + GLint numExt = 0; + glGetIntegerv(GL_NUM_EXTENSIONS, &numExt); + TRACELOG(RL_LOG_INFO, "GL: Supported extensions count: %i", numExt); + +#if defined(RLGL_SHOW_GL_DETAILS_INFO) + // Get supported extensions list + // WARNING: glGetStringi() not available on OpenGL 2.1 + TRACELOG(RL_LOG_INFO, "GL: OpenGL extensions:"); + for (int i = 0; i < numExt; i++) TRACELOG(RL_LOG_INFO, " %s", glGetStringi(GL_EXTENSIONS, i)); +#endif + +#if defined(GRAPHICS_API_OPENGL_21) + // Register supported extensions flags + // Optional OpenGL 2.1 extensions + RLGL.ExtSupported.vao = GLAD_GL_ARB_vertex_array_object; + RLGL.ExtSupported.instancing = (GLAD_GL_EXT_draw_instanced && GLAD_GL_ARB_instanced_arrays); + RLGL.ExtSupported.texNPOT = GLAD_GL_ARB_texture_non_power_of_two; + RLGL.ExtSupported.texFloat32 = GLAD_GL_ARB_texture_float; + RLGL.ExtSupported.texFloat16 = GLAD_GL_ARB_texture_float; + RLGL.ExtSupported.texDepth = GLAD_GL_ARB_depth_texture; + RLGL.ExtSupported.maxDepthBits = 32; + RLGL.ExtSupported.texAnisoFilter = GLAD_GL_EXT_texture_filter_anisotropic; + RLGL.ExtSupported.texMirrorClamp = GLAD_GL_EXT_texture_mirror_clamp; +#else + // Register supported extensions flags + // OpenGL 3.3 extensions supported by default (core) + RLGL.ExtSupported.vao = true; + RLGL.ExtSupported.instancing = true; + RLGL.ExtSupported.texNPOT = true; + RLGL.ExtSupported.texFloat32 = true; + RLGL.ExtSupported.texFloat16 = true; + RLGL.ExtSupported.texDepth = true; + RLGL.ExtSupported.maxDepthBits = 32; + RLGL.ExtSupported.texAnisoFilter = true; + RLGL.ExtSupported.texMirrorClamp = true; +#endif + + // Optional OpenGL 3.3 extensions + RLGL.ExtSupported.texCompASTC = GLAD_GL_KHR_texture_compression_astc_hdr && GLAD_GL_KHR_texture_compression_astc_ldr; + RLGL.ExtSupported.texCompDXT = GLAD_GL_EXT_texture_compression_s3tc; // Texture compression: DXT + RLGL.ExtSupported.texCompETC2 = GLAD_GL_ARB_ES3_compatibility; // Texture compression: ETC2/EAC + #if defined(GRAPHICS_API_OPENGL_43) + RLGL.ExtSupported.computeShader = GLAD_GL_ARB_compute_shader; + RLGL.ExtSupported.ssbo = GLAD_GL_ARB_shader_storage_buffer_object; + #endif + +#endif // GRAPHICS_API_OPENGL_33 + +#if defined(GRAPHICS_API_OPENGL_ES3) + // Register supported extensions flags + // OpenGL ES 3.0 extensions supported by default (or it should be) + RLGL.ExtSupported.vao = true; + RLGL.ExtSupported.instancing = true; + RLGL.ExtSupported.texNPOT = true; + RLGL.ExtSupported.texFloat32 = true; + RLGL.ExtSupported.texFloat16 = true; + RLGL.ExtSupported.texDepth = true; + RLGL.ExtSupported.texDepthWebGL = true; + RLGL.ExtSupported.maxDepthBits = 24; + RLGL.ExtSupported.texAnisoFilter = true; + RLGL.ExtSupported.texMirrorClamp = true; + // TODO: Check for additional OpenGL ES 3.0 supported extensions: + //RLGL.ExtSupported.texCompDXT = true; + //RLGL.ExtSupported.texCompETC1 = true; + //RLGL.ExtSupported.texCompETC2 = true; + //RLGL.ExtSupported.texCompPVRT = true; + //RLGL.ExtSupported.texCompASTC = true; + //RLGL.ExtSupported.maxAnisotropyLevel = true; + //RLGL.ExtSupported.computeShader = true; + //RLGL.ExtSupported.ssbo = true; + +#elif defined(GRAPHICS_API_OPENGL_ES2) + + #if defined(PLATFORM_DESKTOP_GLFW) || defined(PLATFORM_DESKTOP_SDL) + // TODO: Support GLAD loader for OpenGL ES 3.0 + if (gladLoadGLES2((GLADloadfunc)loader) == 0) TRACELOG(RL_LOG_WARNING, "GLAD: Cannot load OpenGL ES2.0 functions"); + else TRACELOG(RL_LOG_INFO, "GLAD: OpenGL ES 2.0 loaded successfully"); + #endif + + // Get supported extensions list + GLint numExt = 0; + const char **extList = RL_MALLOC(512*sizeof(const char *)); // Allocate 512 strings pointers (2 KB) + const char *extensions = (const char *)glGetString(GL_EXTENSIONS); // One big const string + + // NOTE: We have to duplicate string because glGetString() returns a const string + int size = strlen(extensions) + 1; // Get extensions string size in bytes + char *extensionsDup = (char *)RL_CALLOC(size, sizeof(char)); + strcpy(extensionsDup, extensions); + extList[numExt] = extensionsDup; + + for (int i = 0; i < size; i++) + { + if (extensionsDup[i] == ' ') + { + extensionsDup[i] = '\0'; + numExt++; + extList[numExt] = &extensionsDup[i + 1]; + } + } + + TRACELOG(RL_LOG_INFO, "GL: Supported extensions count: %i", numExt); + +#if defined(RLGL_SHOW_GL_DETAILS_INFO) + TRACELOG(RL_LOG_INFO, "GL: OpenGL extensions:"); + for (int i = 0; i < numExt; i++) TRACELOG(RL_LOG_INFO, " %s", extList[i]); +#endif + + // Check required extensions + for (int i = 0; i < numExt; i++) + { + // Check VAO support + // NOTE: Only check on OpenGL ES, OpenGL 3.3 has VAO support as core feature + if (strcmp(extList[i], (const char *)"GL_OES_vertex_array_object") == 0) + { + // The extension is supported by our hardware and driver, try to get related functions pointers + // NOTE: emscripten does not support VAOs natively, it uses emulation and it reduces overall performance... + glGenVertexArrays = (PFNGLGENVERTEXARRAYSOESPROC)((rlglLoadProc)loader)("glGenVertexArraysOES"); + glBindVertexArray = (PFNGLBINDVERTEXARRAYOESPROC)((rlglLoadProc)loader)("glBindVertexArrayOES"); + glDeleteVertexArrays = (PFNGLDELETEVERTEXARRAYSOESPROC)((rlglLoadProc)loader)("glDeleteVertexArraysOES"); + //glIsVertexArray = (PFNGLISVERTEXARRAYOESPROC)loader("glIsVertexArrayOES"); // NOTE: Fails in WebGL, omitted + + if ((glGenVertexArrays != NULL) && (glBindVertexArray != NULL) && (glDeleteVertexArrays != NULL)) RLGL.ExtSupported.vao = true; + } + + // Check instanced rendering support + if (strstr(extList[i], (const char*)"instanced_arrays") != NULL) // Broad check for instanced_arrays + { + // Specific check + if (strcmp(extList[i], (const char *)"GL_ANGLE_instanced_arrays") == 0) // ANGLE + { + glDrawArraysInstanced = (PFNGLDRAWARRAYSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawArraysInstancedANGLE"); + glDrawElementsInstanced = (PFNGLDRAWELEMENTSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawElementsInstancedANGLE"); + glVertexAttribDivisor = (PFNGLVERTEXATTRIBDIVISOREXTPROC)((rlglLoadProc)loader)("glVertexAttribDivisorANGLE"); + } + else if (strcmp(extList[i], (const char *)"GL_EXT_instanced_arrays") == 0) // EXT + { + glDrawArraysInstanced = (PFNGLDRAWARRAYSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawArraysInstancedEXT"); + glDrawElementsInstanced = (PFNGLDRAWELEMENTSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawElementsInstancedEXT"); + glVertexAttribDivisor = (PFNGLVERTEXATTRIBDIVISOREXTPROC)((rlglLoadProc)loader)("glVertexAttribDivisorEXT"); + } + else if (strcmp(extList[i], (const char *)"GL_NV_instanced_arrays") == 0) // NVIDIA GLES + { + glDrawArraysInstanced = (PFNGLDRAWARRAYSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawArraysInstancedNV"); + glDrawElementsInstanced = (PFNGLDRAWELEMENTSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawElementsInstancedNV"); + glVertexAttribDivisor = (PFNGLVERTEXATTRIBDIVISOREXTPROC)((rlglLoadProc)loader)("glVertexAttribDivisorNV"); + } + + // The feature will only be marked as supported if the elements from GL_XXX_instanced_arrays are present + if ((glDrawArraysInstanced != NULL) && (glDrawElementsInstanced != NULL) && (glVertexAttribDivisor != NULL)) RLGL.ExtSupported.instancing = true; + } + else if (strstr(extList[i], (const char *)"draw_instanced") != NULL) + { + // GL_ANGLE_draw_instanced doesn't exist + if (strcmp(extList[i], (const char *)"GL_EXT_draw_instanced") == 0) + { + glDrawArraysInstanced = (PFNGLDRAWARRAYSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawArraysInstancedEXT"); + glDrawElementsInstanced = (PFNGLDRAWELEMENTSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawElementsInstancedEXT"); + } + else if (strcmp(extList[i], (const char*)"GL_NV_draw_instanced") == 0) + { + glDrawArraysInstanced = (PFNGLDRAWARRAYSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawArraysInstancedNV"); + glDrawElementsInstanced = (PFNGLDRAWELEMENTSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawElementsInstancedNV"); + } + + // But the functions will at least be loaded if only GL_XX_EXT_draw_instanced exist + if ((glDrawArraysInstanced != NULL) && (glDrawElementsInstanced != NULL) && (glVertexAttribDivisor != NULL)) RLGL.ExtSupported.instancing = true; + } + + // Check NPOT textures support + // NOTE: Only check on OpenGL ES, OpenGL 3.3 has NPOT textures full support as core feature + if (strcmp(extList[i], (const char *)"GL_OES_texture_npot") == 0) RLGL.ExtSupported.texNPOT = true; + + // Check texture float support + if (strcmp(extList[i], (const char *)"GL_OES_texture_float") == 0) RLGL.ExtSupported.texFloat32 = true; + if (strcmp(extList[i], (const char *)"GL_OES_texture_half_float") == 0) RLGL.ExtSupported.texFloat16 = true; + + // Check depth texture support + if (strcmp(extList[i], (const char *)"GL_OES_depth_texture") == 0) RLGL.ExtSupported.texDepth = true; + if (strcmp(extList[i], (const char *)"GL_WEBGL_depth_texture") == 0) RLGL.ExtSupported.texDepthWebGL = true; // WebGL requires unsized internal format + if (RLGL.ExtSupported.texDepthWebGL) RLGL.ExtSupported.texDepth = true; + + if (strcmp(extList[i], (const char *)"GL_OES_depth24") == 0) RLGL.ExtSupported.maxDepthBits = 24; // Not available on WebGL + if (strcmp(extList[i], (const char *)"GL_OES_depth32") == 0) RLGL.ExtSupported.maxDepthBits = 32; // Not available on WebGL + + // Check texture compression support: DXT + if ((strcmp(extList[i], (const char *)"GL_EXT_texture_compression_s3tc") == 0) || + (strcmp(extList[i], (const char *)"GL_WEBGL_compressed_texture_s3tc") == 0) || + (strcmp(extList[i], (const char *)"GL_WEBKIT_WEBGL_compressed_texture_s3tc") == 0)) RLGL.ExtSupported.texCompDXT = true; + + // Check texture compression support: ETC1 + if ((strcmp(extList[i], (const char *)"GL_OES_compressed_ETC1_RGB8_texture") == 0) || + (strcmp(extList[i], (const char *)"GL_WEBGL_compressed_texture_etc1") == 0)) RLGL.ExtSupported.texCompETC1 = true; + + // Check texture compression support: ETC2/EAC + if (strcmp(extList[i], (const char *)"GL_ARB_ES3_compatibility") == 0) RLGL.ExtSupported.texCompETC2 = true; + + // Check texture compression support: PVR + if (strcmp(extList[i], (const char *)"GL_IMG_texture_compression_pvrtc") == 0) RLGL.ExtSupported.texCompPVRT = true; + + // Check texture compression support: ASTC + if (strcmp(extList[i], (const char *)"GL_KHR_texture_compression_astc_hdr") == 0) RLGL.ExtSupported.texCompASTC = true; + + // Check anisotropic texture filter support + if (strcmp(extList[i], (const char *)"GL_EXT_texture_filter_anisotropic") == 0) RLGL.ExtSupported.texAnisoFilter = true; + + // Check clamp mirror wrap mode support + if (strcmp(extList[i], (const char *)"GL_EXT_texture_mirror_clamp") == 0) RLGL.ExtSupported.texMirrorClamp = true; + } + + // Free extensions pointers + RL_FREE(extList); + RL_FREE(extensionsDup); // Duplicated string must be deallocated +#endif // GRAPHICS_API_OPENGL_ES2 + + // Check OpenGL information and capabilities + //------------------------------------------------------------------------------ + // Show current OpenGL and GLSL version + TRACELOG(RL_LOG_INFO, "GL: OpenGL device information:"); + TRACELOG(RL_LOG_INFO, " > Vendor: %s", glGetString(GL_VENDOR)); + TRACELOG(RL_LOG_INFO, " > Renderer: %s", glGetString(GL_RENDERER)); + TRACELOG(RL_LOG_INFO, " > Version: %s", glGetString(GL_VERSION)); + TRACELOG(RL_LOG_INFO, " > GLSL: %s", glGetString(GL_SHADING_LANGUAGE_VERSION)); + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // NOTE: Anisotropy levels capability is an extension + #ifndef GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT + #define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF + #endif + glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &RLGL.ExtSupported.maxAnisotropyLevel); + +#if defined(RLGL_SHOW_GL_DETAILS_INFO) + // Show some OpenGL GPU capabilities + TRACELOG(RL_LOG_INFO, "GL: OpenGL capabilities:"); + GLint capability = 0; + glGetIntegerv(GL_MAX_TEXTURE_SIZE, &capability); + TRACELOG(RL_LOG_INFO, " GL_MAX_TEXTURE_SIZE: %i", capability); + glGetIntegerv(GL_MAX_CUBE_MAP_TEXTURE_SIZE, &capability); + TRACELOG(RL_LOG_INFO, " GL_MAX_CUBE_MAP_TEXTURE_SIZE: %i", capability); + glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &capability); + TRACELOG(RL_LOG_INFO, " GL_MAX_TEXTURE_IMAGE_UNITS: %i", capability); + glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &capability); + TRACELOG(RL_LOG_INFO, " GL_MAX_VERTEX_ATTRIBS: %i", capability); + #if !defined(GRAPHICS_API_OPENGL_ES2) + glGetIntegerv(GL_MAX_UNIFORM_BLOCK_SIZE, &capability); + TRACELOG(RL_LOG_INFO, " GL_MAX_UNIFORM_BLOCK_SIZE: %i", capability); + glGetIntegerv(GL_MAX_DRAW_BUFFERS, &capability); + TRACELOG(RL_LOG_INFO, " GL_MAX_DRAW_BUFFERS: %i", capability); + if (RLGL.ExtSupported.texAnisoFilter) TRACELOG(RL_LOG_INFO, " GL_MAX_TEXTURE_MAX_ANISOTROPY: %.0f", RLGL.ExtSupported.maxAnisotropyLevel); + #endif + glGetIntegerv(GL_NUM_COMPRESSED_TEXTURE_FORMATS, &capability); + TRACELOG(RL_LOG_INFO, " GL_NUM_COMPRESSED_TEXTURE_FORMATS: %i", capability); + GLint *compFormats = (GLint *)RL_CALLOC(capability, sizeof(GLint)); + glGetIntegerv(GL_COMPRESSED_TEXTURE_FORMATS, compFormats); + for (int i = 0; i < capability; i++) TRACELOG(RL_LOG_INFO, " %s", rlGetCompressedFormatName(compFormats[i])); + RL_FREE(compFormats); + +#if defined(GRAPHICS_API_OPENGL_43) + glGetIntegerv(GL_MAX_VERTEX_ATTRIB_BINDINGS, &capability); + TRACELOG(RL_LOG_INFO, " GL_MAX_VERTEX_ATTRIB_BINDINGS: %i", capability); + glGetIntegerv(GL_MAX_UNIFORM_LOCATIONS, &capability); + TRACELOG(RL_LOG_INFO, " GL_MAX_UNIFORM_LOCATIONS: %i", capability); +#endif // GRAPHICS_API_OPENGL_43 +#else // RLGL_SHOW_GL_DETAILS_INFO + + // Show some basic info about GL supported features + if (RLGL.ExtSupported.vao) TRACELOG(RL_LOG_INFO, "GL: VAO extension detected, VAO functions loaded successfully"); + else TRACELOG(RL_LOG_WARNING, "GL: VAO extension not found, VAO not supported"); + if (RLGL.ExtSupported.texNPOT) TRACELOG(RL_LOG_INFO, "GL: NPOT textures extension detected, full NPOT textures supported"); + else TRACELOG(RL_LOG_WARNING, "GL: NPOT textures extension not found, limited NPOT support (no-mipmaps, no-repeat)"); + if (RLGL.ExtSupported.texCompDXT) TRACELOG(RL_LOG_INFO, "GL: DXT compressed textures supported"); + if (RLGL.ExtSupported.texCompETC1) TRACELOG(RL_LOG_INFO, "GL: ETC1 compressed textures supported"); + if (RLGL.ExtSupported.texCompETC2) TRACELOG(RL_LOG_INFO, "GL: ETC2/EAC compressed textures supported"); + if (RLGL.ExtSupported.texCompPVRT) TRACELOG(RL_LOG_INFO, "GL: PVRT compressed textures supported"); + if (RLGL.ExtSupported.texCompASTC) TRACELOG(RL_LOG_INFO, "GL: ASTC compressed textures supported"); + if (RLGL.ExtSupported.computeShader) TRACELOG(RL_LOG_INFO, "GL: Compute shaders supported"); + if (RLGL.ExtSupported.ssbo) TRACELOG(RL_LOG_INFO, "GL: Shader storage buffer objects supported"); +#endif // RLGL_SHOW_GL_DETAILS_INFO + +#endif // GRAPHICS_API_OPENGL_33 || GRAPHICS_API_OPENGL_ES2 +} + +// Get current OpenGL version +int rlGetVersion(void) +{ + int glVersion = 0; +#if defined(GRAPHICS_API_OPENGL_11) + glVersion = RL_OPENGL_11; +#endif +#if defined(GRAPHICS_API_OPENGL_21) + glVersion = RL_OPENGL_21; +#elif defined(GRAPHICS_API_OPENGL_43) + glVersion = RL_OPENGL_43; +#elif defined(GRAPHICS_API_OPENGL_33) + glVersion = RL_OPENGL_33; +#endif +#if defined(GRAPHICS_API_OPENGL_ES3) + glVersion = RL_OPENGL_ES_30; +#elif defined(GRAPHICS_API_OPENGL_ES2) + glVersion = RL_OPENGL_ES_20; +#endif + + return glVersion; +} + +// Set current framebuffer width +void rlSetFramebufferWidth(int width) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + RLGL.State.framebufferWidth = width; +#endif +} + +// Set current framebuffer height +void rlSetFramebufferHeight(int height) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + RLGL.State.framebufferHeight = height; +#endif +} + +// Get default framebuffer width +int rlGetFramebufferWidth(void) +{ + int width = 0; +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + width = RLGL.State.framebufferWidth; +#endif + return width; +} + +// Get default framebuffer height +int rlGetFramebufferHeight(void) +{ + int height = 0; +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + height = RLGL.State.framebufferHeight; +#endif + return height; +} + +// Get default internal texture (white texture) +// NOTE: Default texture is a 1x1 pixel UNCOMPRESSED_R8G8B8A8 +unsigned int rlGetTextureIdDefault(void) +{ + unsigned int id = 0; +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + id = RLGL.State.defaultTextureId; +#endif + return id; +} + +// Get default shader id +unsigned int rlGetShaderIdDefault(void) +{ + unsigned int id = 0; +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + id = RLGL.State.defaultShaderId; +#endif + return id; +} + +// Get default shader locs +int *rlGetShaderLocsDefault(void) +{ + int *locs = NULL; +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + locs = RLGL.State.defaultShaderLocs; +#endif + return locs; +} + +// Render batch management +//------------------------------------------------------------------------------------------------ +// Load render batch +rlRenderBatch rlLoadRenderBatch(int numBuffers, int bufferElements) +{ + rlRenderBatch batch = { 0 }; + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // Initialize CPU (RAM) vertex buffers (position, texcoord, color data and indexes) + //-------------------------------------------------------------------------------------------- + batch.vertexBuffer = (rlVertexBuffer *)RL_MALLOC(numBuffers*sizeof(rlVertexBuffer)); + + for (int i = 0; i < numBuffers; i++) + { + batch.vertexBuffer[i].elementCount = bufferElements; + + batch.vertexBuffer[i].vertices = (float *)RL_MALLOC(bufferElements*3*4*sizeof(float)); // 3 float by vertex, 4 vertex by quad + batch.vertexBuffer[i].texcoords = (float *)RL_MALLOC(bufferElements*2*4*sizeof(float)); // 2 float by texcoord, 4 texcoord by quad + batch.vertexBuffer[i].normals = (float *)RL_MALLOC(bufferElements*3*4*sizeof(float)); // 3 float by vertex, 4 vertex by quad + batch.vertexBuffer[i].colors = (unsigned char *)RL_MALLOC(bufferElements*4*4*sizeof(unsigned char)); // 4 float by color, 4 colors by quad +#if defined(GRAPHICS_API_OPENGL_33) + batch.vertexBuffer[i].indices = (unsigned int *)RL_MALLOC(bufferElements*6*sizeof(unsigned int)); // 6 int by quad (indices) +#endif +#if defined(GRAPHICS_API_OPENGL_ES2) + batch.vertexBuffer[i].indices = (unsigned short *)RL_MALLOC(bufferElements*6*sizeof(unsigned short)); // 6 int by quad (indices) +#endif + + for (int j = 0; j < (3*4*bufferElements); j++) batch.vertexBuffer[i].vertices[j] = 0.0f; + for (int j = 0; j < (2*4*bufferElements); j++) batch.vertexBuffer[i].texcoords[j] = 0.0f; + for (int j = 0; j < (3*4*bufferElements); j++) batch.vertexBuffer[i].normals[j] = 0.0f; + for (int j = 0; j < (4*4*bufferElements); j++) batch.vertexBuffer[i].colors[j] = 0; + + int k = 0; + + // Indices can be initialized right now + for (int j = 0; j < (6*bufferElements); j += 6) + { + batch.vertexBuffer[i].indices[j] = 4*k; + batch.vertexBuffer[i].indices[j + 1] = 4*k + 1; + batch.vertexBuffer[i].indices[j + 2] = 4*k + 2; + batch.vertexBuffer[i].indices[j + 3] = 4*k; + batch.vertexBuffer[i].indices[j + 4] = 4*k + 2; + batch.vertexBuffer[i].indices[j + 5] = 4*k + 3; + + k++; + } + + RLGL.State.vertexCounter = 0; + } + + TRACELOG(RL_LOG_INFO, "RLGL: Render batch vertex buffers loaded successfully in RAM (CPU)"); + //-------------------------------------------------------------------------------------------- + + // Upload to GPU (VRAM) vertex data and initialize VAOs/VBOs + //-------------------------------------------------------------------------------------------- + for (int i = 0; i < numBuffers; i++) + { + if (RLGL.ExtSupported.vao) + { + // Initialize Quads VAO + glGenVertexArrays(1, &batch.vertexBuffer[i].vaoId); + glBindVertexArray(batch.vertexBuffer[i].vaoId); + } + + // Quads - Vertex buffers binding and attributes enable + // Vertex position buffer (shader-location = 0) + glGenBuffers(1, &batch.vertexBuffer[i].vboId[0]); + glBindBuffer(GL_ARRAY_BUFFER, batch.vertexBuffer[i].vboId[0]); + glBufferData(GL_ARRAY_BUFFER, bufferElements*3*4*sizeof(float), batch.vertexBuffer[i].vertices, GL_DYNAMIC_DRAW); + glEnableVertexAttribArray(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_POSITION]); + glVertexAttribPointer(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_POSITION], 3, GL_FLOAT, 0, 0, 0); + + // Vertex texcoord buffer (shader-location = 1) + glGenBuffers(1, &batch.vertexBuffer[i].vboId[1]); + glBindBuffer(GL_ARRAY_BUFFER, batch.vertexBuffer[i].vboId[1]); + glBufferData(GL_ARRAY_BUFFER, bufferElements*2*4*sizeof(float), batch.vertexBuffer[i].texcoords, GL_DYNAMIC_DRAW); + glEnableVertexAttribArray(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_TEXCOORD01]); + glVertexAttribPointer(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_TEXCOORD01], 2, GL_FLOAT, 0, 0, 0); + + // Vertex normal buffer (shader-location = 2) + glGenBuffers(1, &batch.vertexBuffer[i].vboId[2]); + glBindBuffer(GL_ARRAY_BUFFER, batch.vertexBuffer[i].vboId[2]); + glBufferData(GL_ARRAY_BUFFER, bufferElements*3*4*sizeof(float), batch.vertexBuffer[i].normals, GL_DYNAMIC_DRAW); + glEnableVertexAttribArray(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_NORMAL]); + glVertexAttribPointer(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_NORMAL], 3, GL_FLOAT, 0, 0, 0); + + // Vertex color buffer (shader-location = 3) + glGenBuffers(1, &batch.vertexBuffer[i].vboId[3]); + glBindBuffer(GL_ARRAY_BUFFER, batch.vertexBuffer[i].vboId[3]); + glBufferData(GL_ARRAY_BUFFER, bufferElements*4*4*sizeof(unsigned char), batch.vertexBuffer[i].colors, GL_DYNAMIC_DRAW); + glEnableVertexAttribArray(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_COLOR]); + glVertexAttribPointer(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_COLOR], 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); + + // Fill index buffer + glGenBuffers(1, &batch.vertexBuffer[i].vboId[4]); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, batch.vertexBuffer[i].vboId[4]); +#if defined(GRAPHICS_API_OPENGL_33) + glBufferData(GL_ELEMENT_ARRAY_BUFFER, bufferElements*6*sizeof(int), batch.vertexBuffer[i].indices, GL_STATIC_DRAW); +#endif +#if defined(GRAPHICS_API_OPENGL_ES2) + glBufferData(GL_ELEMENT_ARRAY_BUFFER, bufferElements*6*sizeof(short), batch.vertexBuffer[i].indices, GL_STATIC_DRAW); +#endif + } + + TRACELOG(RL_LOG_INFO, "RLGL: Render batch vertex buffers loaded successfully in VRAM (GPU)"); + + // Unbind the current VAO + if (RLGL.ExtSupported.vao) glBindVertexArray(0); + //-------------------------------------------------------------------------------------------- + + // Init draw calls tracking system + //-------------------------------------------------------------------------------------------- + batch.draws = (rlDrawCall *)RL_MALLOC(RL_DEFAULT_BATCH_DRAWCALLS*sizeof(rlDrawCall)); + + for (int i = 0; i < RL_DEFAULT_BATCH_DRAWCALLS; i++) + { + batch.draws[i].mode = RL_QUADS; + batch.draws[i].vertexCount = 0; + batch.draws[i].vertexAlignment = 0; + //batch.draws[i].vaoId = 0; + //batch.draws[i].shaderId = 0; + batch.draws[i].textureId = RLGL.State.defaultTextureId; + //batch.draws[i].RLGL.State.projection = rlMatrixIdentity(); + //batch.draws[i].RLGL.State.modelview = rlMatrixIdentity(); + } + + batch.bufferCount = numBuffers; // Record buffer count + batch.drawCounter = 1; // Reset draws counter + batch.currentDepth = -1.0f; // Reset depth value + //-------------------------------------------------------------------------------------------- +#endif + + return batch; +} + +// Unload default internal buffers vertex data from CPU and GPU +void rlUnloadRenderBatch(rlRenderBatch batch) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // Unbind everything + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + + // Unload all vertex buffers data + for (int i = 0; i < batch.bufferCount; i++) + { + // Unbind VAO attribs data + if (RLGL.ExtSupported.vao) + { + glBindVertexArray(batch.vertexBuffer[i].vaoId); + glDisableVertexAttribArray(RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION); + glDisableVertexAttribArray(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD); + glDisableVertexAttribArray(RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL); + glDisableVertexAttribArray(RL_DEFAULT_SHADER_ATTRIB_LOCATION_COLOR); + glBindVertexArray(0); + } + + // Delete VBOs from GPU (VRAM) + glDeleteBuffers(1, &batch.vertexBuffer[i].vboId[0]); + glDeleteBuffers(1, &batch.vertexBuffer[i].vboId[1]); + glDeleteBuffers(1, &batch.vertexBuffer[i].vboId[2]); + glDeleteBuffers(1, &batch.vertexBuffer[i].vboId[3]); + glDeleteBuffers(1, &batch.vertexBuffer[i].vboId[4]); + + // Delete VAOs from GPU (VRAM) + if (RLGL.ExtSupported.vao) glDeleteVertexArrays(1, &batch.vertexBuffer[i].vaoId); + + // Free vertex arrays memory from CPU (RAM) + RL_FREE(batch.vertexBuffer[i].vertices); + RL_FREE(batch.vertexBuffer[i].texcoords); + RL_FREE(batch.vertexBuffer[i].normals); + RL_FREE(batch.vertexBuffer[i].colors); + RL_FREE(batch.vertexBuffer[i].indices); + } + + // Unload arrays + RL_FREE(batch.vertexBuffer); + RL_FREE(batch.draws); +#endif +} + +// Draw render batch +// NOTE: We require a pointer to reset batch and increase current buffer (multi-buffer) +void rlDrawRenderBatch(rlRenderBatch *batch) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // Update batch vertex buffers + //------------------------------------------------------------------------------------------------------------ + // NOTE: If there is not vertex data, buffers doesn't need to be updated (vertexCount > 0) + // TODO: If no data changed on the CPU arrays --> No need to re-update GPU arrays (use a change detector flag?) + if (RLGL.State.vertexCounter > 0) + { + // Activate elements VAO + if (RLGL.ExtSupported.vao) glBindVertexArray(batch->vertexBuffer[batch->currentBuffer].vaoId); + + // Vertex positions buffer + glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[0]); + glBufferSubData(GL_ARRAY_BUFFER, 0, RLGL.State.vertexCounter*3*sizeof(float), batch->vertexBuffer[batch->currentBuffer].vertices); + //glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*4*batch->vertexBuffer[batch->currentBuffer].elementCount, batch->vertexBuffer[batch->currentBuffer].vertices, GL_DYNAMIC_DRAW); // Update all buffer + + // Texture coordinates buffer + glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[1]); + glBufferSubData(GL_ARRAY_BUFFER, 0, RLGL.State.vertexCounter*2*sizeof(float), batch->vertexBuffer[batch->currentBuffer].texcoords); + //glBufferData(GL_ARRAY_BUFFER, sizeof(float)*2*4*batch->vertexBuffer[batch->currentBuffer].elementCount, batch->vertexBuffer[batch->currentBuffer].texcoords, GL_DYNAMIC_DRAW); // Update all buffer + + // Normals buffer + glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[2]); + glBufferSubData(GL_ARRAY_BUFFER, 0, RLGL.State.vertexCounter*3*sizeof(float), batch->vertexBuffer[batch->currentBuffer].normals); + //glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*4*batch->vertexBuffer[batch->currentBuffer].elementCount, batch->vertexBuffer[batch->currentBuffer].normals, GL_DYNAMIC_DRAW); // Update all buffer + + // Colors buffer + glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[3]); + glBufferSubData(GL_ARRAY_BUFFER, 0, RLGL.State.vertexCounter*4*sizeof(unsigned char), batch->vertexBuffer[batch->currentBuffer].colors); + //glBufferData(GL_ARRAY_BUFFER, sizeof(float)*4*4*batch->vertexBuffer[batch->currentBuffer].elementCount, batch->vertexBuffer[batch->currentBuffer].colors, GL_DYNAMIC_DRAW); // Update all buffer + + // NOTE: glMapBuffer() causes sync issue + // If GPU is working with this buffer, glMapBuffer() will wait(stall) until GPU to finish its job + // To avoid waiting (idle), you can call first glBufferData() with NULL pointer before glMapBuffer() + // If you do that, the previous data in PBO will be discarded and glMapBuffer() returns a new + // allocated pointer immediately even if GPU is still working with the previous data + + // Another option: map the buffer object into client's memory + // Probably this code could be moved somewhere else... + // batch->vertexBuffer[batch->currentBuffer].vertices = (float *)glMapBuffer(GL_ARRAY_BUFFER, GL_READ_WRITE); + // if (batch->vertexBuffer[batch->currentBuffer].vertices) + // { + // Update vertex data + // } + // glUnmapBuffer(GL_ARRAY_BUFFER); + + // Unbind the current VAO + if (RLGL.ExtSupported.vao) glBindVertexArray(0); + } + //------------------------------------------------------------------------------------------------------------ + + // Draw batch vertex buffers (considering VR stereo if required) + //------------------------------------------------------------------------------------------------------------ + Matrix matProjection = RLGL.State.projection; + Matrix matModelView = RLGL.State.modelview; + + int eyeCount = 1; + if (RLGL.State.stereoRender) eyeCount = 2; + + for (int eye = 0; eye < eyeCount; eye++) + { + if (eyeCount == 2) + { + // Setup current eye viewport (half screen width) + rlViewport(eye*RLGL.State.framebufferWidth/2, 0, RLGL.State.framebufferWidth/2, RLGL.State.framebufferHeight); + + // Set current eye view offset to modelview matrix + rlSetMatrixModelview(rlMatrixMultiply(matModelView, RLGL.State.viewOffsetStereo[eye])); + // Set current eye projection matrix + rlSetMatrixProjection(RLGL.State.projectionStereo[eye]); + } + + // Draw buffers + if (RLGL.State.vertexCounter > 0) + { + // Set current shader and upload current MVP matrix + glUseProgram(RLGL.State.currentShaderId); + + // Create modelview-projection matrix and upload to shader + Matrix matMVP = rlMatrixMultiply(RLGL.State.modelview, RLGL.State.projection); + glUniformMatrix4fv(RLGL.State.currentShaderLocs[RL_SHADER_LOC_MATRIX_MVP], 1, false, rlMatrixToFloat(matMVP)); + + if (RLGL.State.currentShaderLocs[RL_SHADER_LOC_MATRIX_PROJECTION] != -1) + { + glUniformMatrix4fv(RLGL.State.currentShaderLocs[RL_SHADER_LOC_MATRIX_PROJECTION], 1, false, rlMatrixToFloat(RLGL.State.projection)); + } + + // WARNING: For the following setup of the view, model, and normal matrices, it is expected that + // transformations and rendering occur between rlPushMatrix() and rlPopMatrix() + + if (RLGL.State.currentShaderLocs[RL_SHADER_LOC_MATRIX_VIEW] != -1) + { + glUniformMatrix4fv(RLGL.State.currentShaderLocs[RL_SHADER_LOC_MATRIX_VIEW], 1, false, rlMatrixToFloat(RLGL.State.modelview)); + } + + if (RLGL.State.currentShaderLocs[RL_SHADER_LOC_MATRIX_MODEL] != -1) + { + glUniformMatrix4fv(RLGL.State.currentShaderLocs[RL_SHADER_LOC_MATRIX_MODEL], 1, false, rlMatrixToFloat(RLGL.State.transform)); + } + + if (RLGL.State.currentShaderLocs[RL_SHADER_LOC_MATRIX_NORMAL] != -1) + { + glUniformMatrix4fv(RLGL.State.currentShaderLocs[RL_SHADER_LOC_MATRIX_NORMAL], 1, false, rlMatrixToFloat(rlMatrixTranspose(rlMatrixInvert(RLGL.State.transform)))); + } + + if (RLGL.ExtSupported.vao) glBindVertexArray(batch->vertexBuffer[batch->currentBuffer].vaoId); + else + { + // Bind vertex attrib: position (shader-location = 0) + glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[0]); + glVertexAttribPointer(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_POSITION], 3, GL_FLOAT, 0, 0, 0); + glEnableVertexAttribArray(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_POSITION]); + + // Bind vertex attrib: texcoord (shader-location = 1) + glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[1]); + glVertexAttribPointer(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_TEXCOORD01], 2, GL_FLOAT, 0, 0, 0); + glEnableVertexAttribArray(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_TEXCOORD01]); + + // Bind vertex attrib: normal (shader-location = 2) + glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[2]); + glVertexAttribPointer(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_NORMAL], 3, GL_FLOAT, 0, 0, 0); + glEnableVertexAttribArray(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_NORMAL]); + + // Bind vertex attrib: color (shader-location = 3) + glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[3]); + glVertexAttribPointer(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_COLOR], 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); + glEnableVertexAttribArray(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_COLOR]); + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[4]); + } + + // Setup some default shader values + glUniform4f(RLGL.State.currentShaderLocs[RL_SHADER_LOC_COLOR_DIFFUSE], 1.0f, 1.0f, 1.0f, 1.0f); + glUniform1i(RLGL.State.currentShaderLocs[RL_SHADER_LOC_MAP_DIFFUSE], 0); // Active default sampler2D: texture0 + + // Activate additional sampler textures + // Those additional textures will be common for all draw calls of the batch + for (int i = 0; i < RL_DEFAULT_BATCH_MAX_TEXTURE_UNITS; i++) + { + if (RLGL.State.activeTextureId[i] > 0) + { + glActiveTexture(GL_TEXTURE0 + 1 + i); + glBindTexture(GL_TEXTURE_2D, RLGL.State.activeTextureId[i]); + } + } + + // Activate default sampler2D texture0 (one texture is always active for default batch shader) + // NOTE: Batch system accumulates calls by texture0 changes, additional textures are enabled for all the draw calls + glActiveTexture(GL_TEXTURE0); + + for (int i = 0, vertexOffset = 0; i < batch->drawCounter; i++) + { + // Bind current draw call texture, activated as GL_TEXTURE0 and Bound to sampler2D texture0 by default + glBindTexture(GL_TEXTURE_2D, batch->draws[i].textureId); + + if ((batch->draws[i].mode == RL_LINES) || (batch->draws[i].mode == RL_TRIANGLES)) glDrawArrays(batch->draws[i].mode, vertexOffset, batch->draws[i].vertexCount); + else + { + #if defined(GRAPHICS_API_OPENGL_33) + // We need to define the number of indices to be processed: elementCount*6 + // NOTE: The final parameter tells the GPU the offset in bytes from the + // start of the index buffer to the location of the first index to process + glDrawElements(GL_TRIANGLES, batch->draws[i].vertexCount/4*6, GL_UNSIGNED_INT, (GLvoid *)(vertexOffset/4*6*sizeof(GLuint))); + #endif + #if defined(GRAPHICS_API_OPENGL_ES2) + glDrawElements(GL_TRIANGLES, batch->draws[i].vertexCount/4*6, GL_UNSIGNED_SHORT, (GLvoid *)(vertexOffset/4*6*sizeof(GLushort))); + #endif + } + + vertexOffset += (batch->draws[i].vertexCount + batch->draws[i].vertexAlignment); + } + + if (!RLGL.ExtSupported.vao) + { + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + } + + glBindTexture(GL_TEXTURE_2D, 0); // Unbind textures + } + + if (RLGL.ExtSupported.vao) glBindVertexArray(0); // Unbind VAO + + glUseProgram(0); // Unbind shader program + } + + // Restore viewport to default measures + if (eyeCount == 2) rlViewport(0, 0, RLGL.State.framebufferWidth, RLGL.State.framebufferHeight); + //------------------------------------------------------------------------------------------------------------ + + // Reset batch buffers + //------------------------------------------------------------------------------------------------------------ + // Reset vertex counter for next frame + RLGL.State.vertexCounter = 0; + + // Reset depth for next draw + batch->currentDepth = -1.0f; + + // Restore projection/modelview matrices + RLGL.State.projection = matProjection; + RLGL.State.modelview = matModelView; + + // Reset RLGL.currentBatch->draws array + for (int i = 0; i < RL_DEFAULT_BATCH_DRAWCALLS; i++) + { + batch->draws[i].mode = RL_QUADS; + batch->draws[i].vertexCount = 0; + batch->draws[i].textureId = RLGL.State.defaultTextureId; + } + + // Reset active texture units for next batch + for (int i = 0; i < RL_DEFAULT_BATCH_MAX_TEXTURE_UNITS; i++) RLGL.State.activeTextureId[i] = 0; + + // Reset draws counter to one draw for the batch + batch->drawCounter = 1; + //------------------------------------------------------------------------------------------------------------ + + // Change to next buffer in the list (in case of multi-buffering) + batch->currentBuffer++; + if (batch->currentBuffer >= batch->bufferCount) batch->currentBuffer = 0; +#endif +} + +// Set the active render batch for rlgl +void rlSetRenderBatchActive(rlRenderBatch *batch) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + rlDrawRenderBatch(RLGL.currentBatch); + + if (batch != NULL) RLGL.currentBatch = batch; + else RLGL.currentBatch = &RLGL.defaultBatch; +#endif +} + +// Update and draw internal render batch +void rlDrawRenderBatchActive(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + rlDrawRenderBatch(RLGL.currentBatch); // NOTE: Stereo rendering is checked inside +#endif +} + +// Check internal buffer overflow for a given number of vertex +// and force a rlRenderBatch draw call if required +bool rlCheckRenderBatchLimit(int vCount) +{ + bool overflow = false; + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + if ((RLGL.State.vertexCounter + vCount) >= + (RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].elementCount*4)) + { + overflow = true; + + // Store current primitive drawing mode and texture id + int currentMode = RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode; + int currentTexture = RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].textureId; + + rlDrawRenderBatch(RLGL.currentBatch); // NOTE: Stereo rendering is checked inside + + // Restore state of last batch so we can continue adding vertices + RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode = currentMode; + RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].textureId = currentTexture; + } +#endif + + return overflow; +} + +// Textures data management +//----------------------------------------------------------------------------------------- +// Convert image data to OpenGL texture (returns OpenGL valid Id) +unsigned int rlLoadTexture(const void *data, int width, int height, int format, int mipmapCount) +{ + unsigned int id = 0; + + glBindTexture(GL_TEXTURE_2D, 0); // Free any old binding + + // Check texture format support by OpenGL 1.1 (compressed textures not supported) +#if defined(GRAPHICS_API_OPENGL_11) + if (format >= RL_PIXELFORMAT_COMPRESSED_DXT1_RGB) + { + TRACELOG(RL_LOG_WARNING, "GL: OpenGL 1.1 does not support GPU compressed texture formats"); + return id; + } +#else + if ((!RLGL.ExtSupported.texCompDXT) && ((format == RL_PIXELFORMAT_COMPRESSED_DXT1_RGB) || (format == RL_PIXELFORMAT_COMPRESSED_DXT1_RGBA) || + (format == RL_PIXELFORMAT_COMPRESSED_DXT3_RGBA) || (format == RL_PIXELFORMAT_COMPRESSED_DXT5_RGBA))) + { + TRACELOG(RL_LOG_WARNING, "GL: DXT compressed texture format not supported"); + return id; + } +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + if ((!RLGL.ExtSupported.texCompETC1) && (format == RL_PIXELFORMAT_COMPRESSED_ETC1_RGB)) + { + TRACELOG(RL_LOG_WARNING, "GL: ETC1 compressed texture format not supported"); + return id; + } + + if ((!RLGL.ExtSupported.texCompETC2) && ((format == RL_PIXELFORMAT_COMPRESSED_ETC2_RGB) || (format == RL_PIXELFORMAT_COMPRESSED_ETC2_EAC_RGBA))) + { + TRACELOG(RL_LOG_WARNING, "GL: ETC2 compressed texture format not supported"); + return id; + } + + if ((!RLGL.ExtSupported.texCompPVRT) && ((format == RL_PIXELFORMAT_COMPRESSED_PVRT_RGB) || (format == RL_PIXELFORMAT_COMPRESSED_PVRT_RGBA))) + { + TRACELOG(RL_LOG_WARNING, "GL: PVRT compressed texture format not supported"); + return id; + } + + if ((!RLGL.ExtSupported.texCompASTC) && ((format == RL_PIXELFORMAT_COMPRESSED_ASTC_4x4_RGBA) || (format == RL_PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA))) + { + TRACELOG(RL_LOG_WARNING, "GL: ASTC compressed texture format not supported"); + return id; + } +#endif +#endif // GRAPHICS_API_OPENGL_11 + + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + + glGenTextures(1, &id); // Generate texture id + + glBindTexture(GL_TEXTURE_2D, id); + + int mipWidth = width; + int mipHeight = height; + int mipOffset = 0; // Mipmap data offset, only used for tracelog + + // NOTE: Added pointer math separately from function to avoid UBSAN complaining + unsigned char *dataPtr = NULL; + if (data != NULL) dataPtr = (unsigned char *)data; + + // Load the different mipmap levels + for (int i = 0; i < mipmapCount; i++) + { + unsigned int mipSize = rlGetPixelDataSize(mipWidth, mipHeight, format); + + unsigned int glInternalFormat, glFormat, glType; + rlGetGlTextureFormats(format, &glInternalFormat, &glFormat, &glType); + + TRACELOGD("TEXTURE: Load mipmap level %i (%i x %i), size: %i, offset: %i", i, mipWidth, mipHeight, mipSize, mipOffset); + + if (glInternalFormat != 0) + { + if (format < RL_PIXELFORMAT_COMPRESSED_DXT1_RGB) glTexImage2D(GL_TEXTURE_2D, i, glInternalFormat, mipWidth, mipHeight, 0, glFormat, glType, dataPtr); +#if !defined(GRAPHICS_API_OPENGL_11) + else glCompressedTexImage2D(GL_TEXTURE_2D, i, glInternalFormat, mipWidth, mipHeight, 0, mipSize, dataPtr); +#endif + +#if defined(GRAPHICS_API_OPENGL_33) + if (format == RL_PIXELFORMAT_UNCOMPRESSED_GRAYSCALE) + { + GLint swizzleMask[] = { GL_RED, GL_RED, GL_RED, GL_ONE }; + glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, swizzleMask); + } + else if (format == RL_PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA) + { +#if defined(GRAPHICS_API_OPENGL_21) + GLint swizzleMask[] = { GL_RED, GL_RED, GL_RED, GL_ALPHA }; +#elif defined(GRAPHICS_API_OPENGL_33) + GLint swizzleMask[] = { GL_RED, GL_RED, GL_RED, GL_GREEN }; +#endif + glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, swizzleMask); + } +#endif + } + + mipWidth /= 2; + mipHeight /= 2; + mipOffset += mipSize; // Increment offset position to next mipmap + if (data != NULL) dataPtr += mipSize; // Increment data pointer to next mipmap + + // Security check for NPOT textures + if (mipWidth < 1) mipWidth = 1; + if (mipHeight < 1) mipHeight = 1; + } + + // Texture parameters configuration + // NOTE: glTexParameteri does NOT affect texture uploading, just the way it's used +#if defined(GRAPHICS_API_OPENGL_ES2) + // NOTE: OpenGL ES 2.0 with no GL_OES_texture_npot support (i.e. WebGL) has limited NPOT support, so CLAMP_TO_EDGE must be used + if (RLGL.ExtSupported.texNPOT) + { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); // Set texture to repeat on x-axis + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); // Set texture to repeat on y-axis + } + else + { + // NOTE: If using negative texture coordinates (LoadOBJ()), it does not work! + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); // Set texture to clamp on x-axis + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); // Set texture to clamp on y-axis + } +#else + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); // Set texture to repeat on x-axis + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); // Set texture to repeat on y-axis +#endif + + // Magnification and minification filters + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); // Alternative: GL_LINEAR + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); // Alternative: GL_LINEAR + +#if defined(GRAPHICS_API_OPENGL_33) + if (mipmapCount > 1) + { + // Activate Trilinear filtering if mipmaps are available + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + } +#endif + + // At this point we have the texture loaded in GPU and texture parameters configured + + // NOTE: If mipmaps were not in data, they are not generated automatically + + // Unbind current texture + glBindTexture(GL_TEXTURE_2D, 0); + + if (id > 0) TRACELOG(RL_LOG_INFO, "TEXTURE: [ID %i] Texture loaded successfully (%ix%i | %s | %i mipmaps)", id, width, height, rlGetPixelFormatName(format), mipmapCount); + else TRACELOG(RL_LOG_WARNING, "TEXTURE: Failed to load texture"); + + return id; +} + +// Load depth texture/renderbuffer (to be attached to fbo) +// WARNING: OpenGL ES 2.0 requires GL_OES_depth_texture and WebGL requires WEBGL_depth_texture extensions +unsigned int rlLoadTextureDepth(int width, int height, bool useRenderBuffer) +{ + unsigned int id = 0; + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // In case depth textures not supported, we force renderbuffer usage + if (!RLGL.ExtSupported.texDepth) useRenderBuffer = true; + + // NOTE: We let the implementation to choose the best bit-depth + // Possible formats: GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT24, GL_DEPTH_COMPONENT32 and GL_DEPTH_COMPONENT32F + unsigned int glInternalFormat = GL_DEPTH_COMPONENT; + +#if (defined(GRAPHICS_API_OPENGL_ES2) || defined(GRAPHICS_API_OPENGL_ES3)) + // WARNING: WebGL platform requires unsized internal format definition (GL_DEPTH_COMPONENT) + // while other platforms using OpenGL ES 2.0 require/support sized internal formats depending on the GPU capabilities + if (!RLGL.ExtSupported.texDepthWebGL || useRenderBuffer) + { + if (RLGL.ExtSupported.maxDepthBits == 32) glInternalFormat = GL_DEPTH_COMPONENT32_OES; + else if (RLGL.ExtSupported.maxDepthBits == 24) glInternalFormat = GL_DEPTH_COMPONENT24_OES; + else glInternalFormat = GL_DEPTH_COMPONENT16; + } +#endif + + if (!useRenderBuffer && RLGL.ExtSupported.texDepth) + { + glGenTextures(1, &id); + glBindTexture(GL_TEXTURE_2D, id); + glTexImage2D(GL_TEXTURE_2D, 0, glInternalFormat, width, height, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, NULL); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + glBindTexture(GL_TEXTURE_2D, 0); + + TRACELOG(RL_LOG_INFO, "TEXTURE: Depth texture loaded successfully"); + } + else + { + // Create the renderbuffer that will serve as the depth attachment for the framebuffer + // NOTE: A renderbuffer is simpler than a texture and could offer better performance on embedded devices + glGenRenderbuffers(1, &id); + glBindRenderbuffer(GL_RENDERBUFFER, id); + glRenderbufferStorage(GL_RENDERBUFFER, glInternalFormat, width, height); + + glBindRenderbuffer(GL_RENDERBUFFER, 0); + + TRACELOG(RL_LOG_INFO, "TEXTURE: [ID %i] Depth renderbuffer loaded successfully (%i bits)", id, (RLGL.ExtSupported.maxDepthBits >= 24)? RLGL.ExtSupported.maxDepthBits : 16); + } +#endif + + return id; +} + +// Load texture cubemap +// NOTE: Cubemap data is expected to be 6 images in a single data array (one after the other), +// expected the following convention: +X, -X, +Y, -Y, +Z, -Z +unsigned int rlLoadTextureCubemap(const void *data, int size, int format, int mipmapCount) +{ + unsigned int id = 0; + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + int mipSize = size; + + // NOTE: Added pointer math separately from function to avoid UBSAN complaining + unsigned char *dataPtr = NULL; + if (data != NULL) dataPtr = (unsigned char *)data; + + unsigned int dataSize = rlGetPixelDataSize(size, size, format); + + glGenTextures(1, &id); + glBindTexture(GL_TEXTURE_CUBE_MAP, id); + + unsigned int glInternalFormat, glFormat, glType; + rlGetGlTextureFormats(format, &glInternalFormat, &glFormat, &glType); + + if (glInternalFormat != 0) + { + // Load cubemap faces/mipmaps + for (int i = 0; i < 6*mipmapCount; i++) + { + int mipmapLevel = i/6; + int face = i%6; + + if (data == NULL) + { + if (format < RL_PIXELFORMAT_COMPRESSED_DXT1_RGB) + { + if ((format == RL_PIXELFORMAT_UNCOMPRESSED_R32) || + (format == RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32) || + (format == RL_PIXELFORMAT_UNCOMPRESSED_R16) || + (format == RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16)) TRACELOG(RL_LOG_WARNING, "TEXTURES: Cubemap requested format not supported"); + else glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, mipmapLevel, glInternalFormat, mipSize, mipSize, 0, glFormat, glType, NULL); + } + else TRACELOG(RL_LOG_WARNING, "TEXTURES: Empty cubemap creation does not support compressed format"); + } + else + { + if (format < RL_PIXELFORMAT_COMPRESSED_DXT1_RGB) glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, mipmapLevel, glInternalFormat, mipSize, mipSize, 0, glFormat, glType, (unsigned char *)dataPtr + face*dataSize); + else glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, mipmapLevel, glInternalFormat, mipSize, mipSize, 0, dataSize, (unsigned char *)dataPtr + face*dataSize); + } + +#if defined(GRAPHICS_API_OPENGL_33) + if (format == RL_PIXELFORMAT_UNCOMPRESSED_GRAYSCALE) + { + GLint swizzleMask[] = { GL_RED, GL_RED, GL_RED, GL_ONE }; + glTexParameteriv(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_SWIZZLE_RGBA, swizzleMask); + } + else if (format == RL_PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA) + { +#if defined(GRAPHICS_API_OPENGL_21) + GLint swizzleMask[] = { GL_RED, GL_RED, GL_RED, GL_ALPHA }; +#elif defined(GRAPHICS_API_OPENGL_33) + GLint swizzleMask[] = { GL_RED, GL_RED, GL_RED, GL_GREEN }; +#endif + glTexParameteriv(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_SWIZZLE_RGBA, swizzleMask); + } +#endif + if (face == 5) + { + mipSize /= 2; + if (data != NULL) dataPtr += dataSize*6; // Increment data pointer to next mipmap + + // Security check for NPOT textures + if (mipSize < 1) mipSize = 1; + + dataSize = rlGetPixelDataSize(mipSize, mipSize, format); + } + } + } + + // Set cubemap texture sampling parameters + if (mipmapCount > 1) glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + else glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); +#if defined(GRAPHICS_API_OPENGL_33) + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); // Flag not supported on OpenGL ES 2.0 +#endif + + glBindTexture(GL_TEXTURE_CUBE_MAP, 0); +#endif + + if (id > 0) TRACELOG(RL_LOG_INFO, "TEXTURE: [ID %i] Cubemap texture loaded successfully (%ix%i)", id, size, size); + else TRACELOG(RL_LOG_WARNING, "TEXTURE: Failed to load cubemap texture"); + + return id; +} + +// Update already loaded texture in GPU with new data +// NOTE: We don't know safely if internal texture format is the expected one... +void rlUpdateTexture(unsigned int id, int offsetX, int offsetY, int width, int height, int format, const void *data) +{ + glBindTexture(GL_TEXTURE_2D, id); + + unsigned int glInternalFormat, glFormat, glType; + rlGetGlTextureFormats(format, &glInternalFormat, &glFormat, &glType); + + if ((glInternalFormat != 0) && (format < RL_PIXELFORMAT_COMPRESSED_DXT1_RGB)) + { + glTexSubImage2D(GL_TEXTURE_2D, 0, offsetX, offsetY, width, height, glFormat, glType, data); + } + else TRACELOG(RL_LOG_WARNING, "TEXTURE: [ID %i] Failed to update for current texture format (%i)", id, format); +} + +// Get OpenGL internal formats and data type from raylib PixelFormat +void rlGetGlTextureFormats(int format, unsigned int *glInternalFormat, unsigned int *glFormat, unsigned int *glType) +{ + *glInternalFormat = 0; + *glFormat = 0; + *glType = 0; + + switch (format) + { + #if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_21) || defined(GRAPHICS_API_OPENGL_ES2) + // NOTE: on OpenGL ES 2.0 (WebGL), internalFormat must match format and options allowed are: GL_LUMINANCE, GL_RGB, GL_RGBA + case RL_PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: *glInternalFormat = GL_LUMINANCE; *glFormat = GL_LUMINANCE; *glType = GL_UNSIGNED_BYTE; break; + case RL_PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: *glInternalFormat = GL_LUMINANCE_ALPHA; *glFormat = GL_LUMINANCE_ALPHA; *glType = GL_UNSIGNED_BYTE; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R5G6B5: *glInternalFormat = GL_RGB; *glFormat = GL_RGB; *glType = GL_UNSIGNED_SHORT_5_6_5; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8: *glInternalFormat = GL_RGB; *glFormat = GL_RGB; *glType = GL_UNSIGNED_BYTE; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: *glInternalFormat = GL_RGBA; *glFormat = GL_RGBA; *glType = GL_UNSIGNED_SHORT_5_5_5_1; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: *glInternalFormat = GL_RGBA; *glFormat = GL_RGBA; *glType = GL_UNSIGNED_SHORT_4_4_4_4; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: *glInternalFormat = GL_RGBA; *glFormat = GL_RGBA; *glType = GL_UNSIGNED_BYTE; break; + #if !defined(GRAPHICS_API_OPENGL_11) + #if defined(GRAPHICS_API_OPENGL_ES3) + case RL_PIXELFORMAT_UNCOMPRESSED_R32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_R32F_EXT; *glFormat = GL_RED_EXT; *glType = GL_FLOAT; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_RGB32F_EXT; *glFormat = GL_RGB; *glType = GL_FLOAT; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_RGBA32F_EXT; *glFormat = GL_RGBA; *glType = GL_FLOAT; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R16: if (RLGL.ExtSupported.texFloat16) *glInternalFormat = GL_R16F_EXT; *glFormat = GL_RED_EXT; *glType = GL_HALF_FLOAT; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16: if (RLGL.ExtSupported.texFloat16) *glInternalFormat = GL_RGB16F_EXT; *glFormat = GL_RGB; *glType = GL_HALF_FLOAT; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: if (RLGL.ExtSupported.texFloat16) *glInternalFormat = GL_RGBA16F_EXT; *glFormat = GL_RGBA; *glType = GL_HALF_FLOAT; break; + #else + case RL_PIXELFORMAT_UNCOMPRESSED_R32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_LUMINANCE; *glFormat = GL_LUMINANCE; *glType = GL_FLOAT; break; // NOTE: Requires extension OES_texture_float + case RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_RGB; *glFormat = GL_RGB; *glType = GL_FLOAT; break; // NOTE: Requires extension OES_texture_float + case RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_RGBA; *glFormat = GL_RGBA; *glType = GL_FLOAT; break; // NOTE: Requires extension OES_texture_float + #if defined(GRAPHICS_API_OPENGL_21) + case RL_PIXELFORMAT_UNCOMPRESSED_R16: if (RLGL.ExtSupported.texFloat16) *glInternalFormat = GL_LUMINANCE; *glFormat = GL_LUMINANCE; *glType = GL_HALF_FLOAT_ARB; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16: if (RLGL.ExtSupported.texFloat16) *glInternalFormat = GL_RGB; *glFormat = GL_RGB; *glType = GL_HALF_FLOAT_ARB; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: if (RLGL.ExtSupported.texFloat16) *glInternalFormat = GL_RGBA; *glFormat = GL_RGBA; *glType = GL_HALF_FLOAT_ARB; break; + #else // defined(GRAPHICS_API_OPENGL_ES2) + case RL_PIXELFORMAT_UNCOMPRESSED_R16: if (RLGL.ExtSupported.texFloat16) *glInternalFormat = GL_LUMINANCE; *glFormat = GL_LUMINANCE; *glType = GL_HALF_FLOAT_OES; break; // NOTE: Requires extension OES_texture_half_float + case RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16: if (RLGL.ExtSupported.texFloat16) *glInternalFormat = GL_RGB; *glFormat = GL_RGB; *glType = GL_HALF_FLOAT_OES; break; // NOTE: Requires extension OES_texture_half_float + case RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: if (RLGL.ExtSupported.texFloat16) *glInternalFormat = GL_RGBA; *glFormat = GL_RGBA; *glType = GL_HALF_FLOAT_OES; break; // NOTE: Requires extension OES_texture_half_float + #endif + #endif + #endif + #elif defined(GRAPHICS_API_OPENGL_33) + case RL_PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: *glInternalFormat = GL_R8; *glFormat = GL_RED; *glType = GL_UNSIGNED_BYTE; break; + case RL_PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: *glInternalFormat = GL_RG8; *glFormat = GL_RG; *glType = GL_UNSIGNED_BYTE; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R5G6B5: *glInternalFormat = GL_RGB565; *glFormat = GL_RGB; *glType = GL_UNSIGNED_SHORT_5_6_5; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8: *glInternalFormat = GL_RGB8; *glFormat = GL_RGB; *glType = GL_UNSIGNED_BYTE; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: *glInternalFormat = GL_RGB5_A1; *glFormat = GL_RGBA; *glType = GL_UNSIGNED_SHORT_5_5_5_1; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: *glInternalFormat = GL_RGBA4; *glFormat = GL_RGBA; *glType = GL_UNSIGNED_SHORT_4_4_4_4; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: *glInternalFormat = GL_RGBA8; *glFormat = GL_RGBA; *glType = GL_UNSIGNED_BYTE; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_R32F; *glFormat = GL_RED; *glType = GL_FLOAT; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_RGB32F; *glFormat = GL_RGB; *glType = GL_FLOAT; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_RGBA32F; *glFormat = GL_RGBA; *glType = GL_FLOAT; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R16: if (RLGL.ExtSupported.texFloat16) *glInternalFormat = GL_R16F; *glFormat = GL_RED; *glType = GL_HALF_FLOAT; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16: if (RLGL.ExtSupported.texFloat16) *glInternalFormat = GL_RGB16F; *glFormat = GL_RGB; *glType = GL_HALF_FLOAT; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: if (RLGL.ExtSupported.texFloat16) *glInternalFormat = GL_RGBA16F; *glFormat = GL_RGBA; *glType = GL_HALF_FLOAT; break; + #endif + #if !defined(GRAPHICS_API_OPENGL_11) + case RL_PIXELFORMAT_COMPRESSED_DXT1_RGB: if (RLGL.ExtSupported.texCompDXT) *glInternalFormat = GL_COMPRESSED_RGB_S3TC_DXT1_EXT; break; + case RL_PIXELFORMAT_COMPRESSED_DXT1_RGBA: if (RLGL.ExtSupported.texCompDXT) *glInternalFormat = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; break; + case RL_PIXELFORMAT_COMPRESSED_DXT3_RGBA: if (RLGL.ExtSupported.texCompDXT) *glInternalFormat = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; break; + case RL_PIXELFORMAT_COMPRESSED_DXT5_RGBA: if (RLGL.ExtSupported.texCompDXT) *glInternalFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; break; + case RL_PIXELFORMAT_COMPRESSED_ETC1_RGB: if (RLGL.ExtSupported.texCompETC1) *glInternalFormat = GL_ETC1_RGB8_OES; break; // NOTE: Requires OpenGL ES 2.0 or OpenGL 4.3 + case RL_PIXELFORMAT_COMPRESSED_ETC2_RGB: if (RLGL.ExtSupported.texCompETC2) *glInternalFormat = GL_COMPRESSED_RGB8_ETC2; break; // NOTE: Requires OpenGL ES 3.0 or OpenGL 4.3 + case RL_PIXELFORMAT_COMPRESSED_ETC2_EAC_RGBA: if (RLGL.ExtSupported.texCompETC2) *glInternalFormat = GL_COMPRESSED_RGBA8_ETC2_EAC; break; // NOTE: Requires OpenGL ES 3.0 or OpenGL 4.3 + case RL_PIXELFORMAT_COMPRESSED_PVRT_RGB: if (RLGL.ExtSupported.texCompPVRT) *glInternalFormat = GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG; break; // NOTE: Requires PowerVR GPU + case RL_PIXELFORMAT_COMPRESSED_PVRT_RGBA: if (RLGL.ExtSupported.texCompPVRT) *glInternalFormat = GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG; break; // NOTE: Requires PowerVR GPU + case RL_PIXELFORMAT_COMPRESSED_ASTC_4x4_RGBA: if (RLGL.ExtSupported.texCompASTC) *glInternalFormat = GL_COMPRESSED_RGBA_ASTC_4x4_KHR; break; // NOTE: Requires OpenGL ES 3.1 or OpenGL 4.3 + case RL_PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA: if (RLGL.ExtSupported.texCompASTC) *glInternalFormat = GL_COMPRESSED_RGBA_ASTC_8x8_KHR; break; // NOTE: Requires OpenGL ES 3.1 or OpenGL 4.3 + #endif + default: TRACELOG(RL_LOG_WARNING, "TEXTURE: Current format not supported (%i)", format); break; + } +} + +// Unload texture from GPU memory +void rlUnloadTexture(unsigned int id) +{ + glDeleteTextures(1, &id); +} + +// Generate mipmap data for selected texture +// NOTE: Only supports GPU mipmap generation +void rlGenTextureMipmaps(unsigned int id, int width, int height, int format, int *mipmaps) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glBindTexture(GL_TEXTURE_2D, id); + + // Check if texture is power-of-two (POT) + bool texIsPOT = false; + + if (((width > 0) && ((width & (width - 1)) == 0)) && + ((height > 0) && ((height & (height - 1)) == 0))) texIsPOT = true; + + if ((texIsPOT) || (RLGL.ExtSupported.texNPOT)) + { + //glHint(GL_GENERATE_MIPMAP_HINT, GL_DONT_CARE); // Hint for mipmaps generation algorithm: GL_FASTEST, GL_NICEST, GL_DONT_CARE + glGenerateMipmap(GL_TEXTURE_2D); // Generate mipmaps automatically + + #define MIN(a,b) (((a)<(b))? (a):(b)) + #define MAX(a,b) (((a)>(b))? (a):(b)) + + *mipmaps = 1 + (int)floor(log(MAX(width, height))/log(2)); + TRACELOG(RL_LOG_INFO, "TEXTURE: [ID %i] Mipmaps generated automatically, total: %i", id, *mipmaps); + } + else TRACELOG(RL_LOG_WARNING, "TEXTURE: [ID %i] Failed to generate mipmaps", id); + + glBindTexture(GL_TEXTURE_2D, 0); +#else + TRACELOG(RL_LOG_WARNING, "TEXTURE: [ID %i] GPU mipmap generation not supported", id); +#endif +} + +// Read texture pixel data +void *rlReadTexturePixels(unsigned int id, int width, int height, int format) +{ + void *pixels = NULL; + +#if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33) + glBindTexture(GL_TEXTURE_2D, id); + + // NOTE: Using texture id, we can retrieve some texture info (but not on OpenGL ES 2.0) + // Possible texture info: GL_TEXTURE_RED_SIZE, GL_TEXTURE_GREEN_SIZE, GL_TEXTURE_BLUE_SIZE, GL_TEXTURE_ALPHA_SIZE + //int width, height, format; + //glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &width); + //glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &height); + //glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_INTERNAL_FORMAT, &format); + + // NOTE: Each row written to or read from by OpenGL pixel operations like glGetTexImage are aligned to a 4 byte boundary by default, which may add some padding + // Use glPixelStorei to modify padding with the GL_[UN]PACK_ALIGNMENT setting + // GL_PACK_ALIGNMENT affects operations that read from OpenGL memory (glReadPixels, glGetTexImage, etc.) + // GL_UNPACK_ALIGNMENT affects operations that write to OpenGL memory (glTexImage, etc.) + glPixelStorei(GL_PACK_ALIGNMENT, 1); + + unsigned int glInternalFormat, glFormat, glType; + rlGetGlTextureFormats(format, &glInternalFormat, &glFormat, &glType); + unsigned int size = rlGetPixelDataSize(width, height, format); + + if ((glInternalFormat != 0) && (format < RL_PIXELFORMAT_COMPRESSED_DXT1_RGB)) + { + pixels = RL_MALLOC(size); + glGetTexImage(GL_TEXTURE_2D, 0, glFormat, glType, pixels); + } + else TRACELOG(RL_LOG_WARNING, "TEXTURE: [ID %i] Data retrieval not suported for pixel format (%i)", id, format); + + glBindTexture(GL_TEXTURE_2D, 0); +#endif + +#if defined(GRAPHICS_API_OPENGL_ES2) + // glGetTexImage() is not available on OpenGL ES 2.0 + // Texture width and height are required on OpenGL ES 2.0, there is no way to get it from texture id + // Two possible Options: + // 1 - Bind texture to color fbo attachment and glReadPixels() + // 2 - Create an fbo, activate it, render quad with texture, glReadPixels() + // We are using Option 1, just need to care for texture format on retrieval + // NOTE: This behaviour could be conditioned by graphic driver... + unsigned int fboId = rlLoadFramebuffer(); + + glBindFramebuffer(GL_FRAMEBUFFER, fboId); + glBindTexture(GL_TEXTURE_2D, 0); + + // Attach our texture to FBO + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, id, 0); + + // We read data as RGBA because FBO texture is configured as RGBA, despite binding another texture format + pixels = (unsigned char *)RL_MALLOC(rlGetPixelDataSize(width, height, RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8)); + glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels); + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + // Clean up temporal fbo + rlUnloadFramebuffer(fboId); +#endif + + return pixels; +} + +// Read screen pixel data (color buffer) +unsigned char *rlReadScreenPixels(int width, int height) +{ + unsigned char *screenData = (unsigned char *)RL_CALLOC(width*height*4, sizeof(unsigned char)); + + // NOTE 1: glReadPixels returns image flipped vertically -> (0,0) is the bottom left corner of the framebuffer + // NOTE 2: We are getting alpha channel! Be careful, it can be transparent if not cleared properly! + glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, screenData); + + // Flip image vertically! + unsigned char *imgData = (unsigned char *)RL_MALLOC(width*height*4*sizeof(unsigned char)); + + for (int y = height - 1; y >= 0; y--) + { + for (int x = 0; x < (width*4); x++) + { + imgData[((height - 1) - y)*width*4 + x] = screenData[(y*width*4) + x]; // Flip line + + // Set alpha component value to 255 (no trasparent image retrieval) + // NOTE: Alpha value has already been applied to RGB in framebuffer, we don't need it! + if (((x + 1)%4) == 0) imgData[((height - 1) - y)*width*4 + x] = 255; + } + } + + RL_FREE(screenData); + + return imgData; // NOTE: image data should be freed +} + +// Framebuffer management (fbo) +//----------------------------------------------------------------------------------------- +// Load a framebuffer to be used for rendering +// NOTE: No textures attached +unsigned int rlLoadFramebuffer(void) +{ + unsigned int fboId = 0; + +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) && defined(RLGL_RENDER_TEXTURES_HINT) + glGenFramebuffers(1, &fboId); // Create the framebuffer object + glBindFramebuffer(GL_FRAMEBUFFER, 0); // Unbind any framebuffer +#endif + + return fboId; +} + +// Attach color buffer texture to an fbo (unloads previous attachment) +// NOTE: Attach type: 0-Color, 1-Depth renderbuffer, 2-Depth texture +void rlFramebufferAttach(unsigned int fboId, unsigned int texId, int attachType, int texType, int mipLevel) +{ +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) && defined(RLGL_RENDER_TEXTURES_HINT) + glBindFramebuffer(GL_FRAMEBUFFER, fboId); + + switch (attachType) + { + case RL_ATTACHMENT_COLOR_CHANNEL0: + case RL_ATTACHMENT_COLOR_CHANNEL1: + case RL_ATTACHMENT_COLOR_CHANNEL2: + case RL_ATTACHMENT_COLOR_CHANNEL3: + case RL_ATTACHMENT_COLOR_CHANNEL4: + case RL_ATTACHMENT_COLOR_CHANNEL5: + case RL_ATTACHMENT_COLOR_CHANNEL6: + case RL_ATTACHMENT_COLOR_CHANNEL7: + { + if (texType == RL_ATTACHMENT_TEXTURE2D) glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + attachType, GL_TEXTURE_2D, texId, mipLevel); + else if (texType == RL_ATTACHMENT_RENDERBUFFER) glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + attachType, GL_RENDERBUFFER, texId); + else if (texType >= RL_ATTACHMENT_CUBEMAP_POSITIVE_X) glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + attachType, GL_TEXTURE_CUBE_MAP_POSITIVE_X + texType, texId, mipLevel); + + } break; + case RL_ATTACHMENT_DEPTH: + { + if (texType == RL_ATTACHMENT_TEXTURE2D) glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, texId, mipLevel); + else if (texType == RL_ATTACHMENT_RENDERBUFFER) glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, texId); + + } break; + case RL_ATTACHMENT_STENCIL: + { + if (texType == RL_ATTACHMENT_TEXTURE2D) glFramebufferTexture2D(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, texId, mipLevel); + else if (texType == RL_ATTACHMENT_RENDERBUFFER) glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, texId); + + } break; + default: break; + } + + glBindFramebuffer(GL_FRAMEBUFFER, 0); +#endif +} + +// Verify render texture is complete +bool rlFramebufferComplete(unsigned int id) +{ + bool result = false; + +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) && defined(RLGL_RENDER_TEXTURES_HINT) + glBindFramebuffer(GL_FRAMEBUFFER, id); + + GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + + if (status != GL_FRAMEBUFFER_COMPLETE) + { + switch (status) + { + case GL_FRAMEBUFFER_UNSUPPORTED: TRACELOG(RL_LOG_WARNING, "FBO: [ID %i] Framebuffer is unsupported", id); break; + case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: TRACELOG(RL_LOG_WARNING, "FBO: [ID %i] Framebuffer has incomplete attachment", id); break; +#if defined(GRAPHICS_API_OPENGL_ES2) + case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS: TRACELOG(RL_LOG_WARNING, "FBO: [ID %i] Framebuffer has incomplete dimensions", id); break; +#endif + case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: TRACELOG(RL_LOG_WARNING, "FBO: [ID %i] Framebuffer has a missing attachment", id); break; + default: break; + } + } + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + result = (status == GL_FRAMEBUFFER_COMPLETE); +#endif + + return result; +} + +// Unload framebuffer from GPU memory +// NOTE: All attached textures/cubemaps/renderbuffers are also deleted +void rlUnloadFramebuffer(unsigned int id) +{ +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) && defined(RLGL_RENDER_TEXTURES_HINT) + // Query depth attachment to automatically delete texture/renderbuffer + int depthType = 0, depthId = 0; + glBindFramebuffer(GL_FRAMEBUFFER, id); // Bind framebuffer to query depth texture type + glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, &depthType); + + // TODO: Review warning retrieving object name in WebGL + // WARNING: WebGL: INVALID_ENUM: getFramebufferAttachmentParameter: invalid parameter name + // https://registry.khronos.org/webgl/specs/latest/1.0/ + glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &depthId); + + unsigned int depthIdU = (unsigned int)depthId; + if (depthType == GL_RENDERBUFFER) glDeleteRenderbuffers(1, &depthIdU); + else if (depthType == GL_TEXTURE) glDeleteTextures(1, &depthIdU); + + // NOTE: If a texture object is deleted while its image is attached to the *currently bound* framebuffer, + // the texture image is automatically detached from the currently bound framebuffer + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glDeleteFramebuffers(1, &id); + + TRACELOG(RL_LOG_INFO, "FBO: [ID %i] Unloaded framebuffer from VRAM (GPU)", id); +#endif +} + +// Vertex data management +//----------------------------------------------------------------------------------------- +// Load a new attributes buffer +unsigned int rlLoadVertexBuffer(const void *buffer, int size, bool dynamic) +{ + unsigned int id = 0; + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glGenBuffers(1, &id); + glBindBuffer(GL_ARRAY_BUFFER, id); + glBufferData(GL_ARRAY_BUFFER, size, buffer, dynamic? GL_DYNAMIC_DRAW : GL_STATIC_DRAW); +#endif + + return id; +} + +// Load a new attributes element buffer +unsigned int rlLoadVertexBufferElement(const void *buffer, int size, bool dynamic) +{ + unsigned int id = 0; + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glGenBuffers(1, &id); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, id); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, size, buffer, dynamic? GL_DYNAMIC_DRAW : GL_STATIC_DRAW); +#endif + + return id; +} + +// Enable vertex buffer (VBO) +void rlEnableVertexBuffer(unsigned int id) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glBindBuffer(GL_ARRAY_BUFFER, id); +#endif +} + +// Disable vertex buffer (VBO) +void rlDisableVertexBuffer(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glBindBuffer(GL_ARRAY_BUFFER, 0); +#endif +} + +// Enable vertex buffer element (VBO element) +void rlEnableVertexBufferElement(unsigned int id) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, id); +#endif +} + +// Disable vertex buffer element (VBO element) +void rlDisableVertexBufferElement(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); +#endif +} + +// Update vertex buffer with new data +// NOTE: dataSize and offset must be provided in bytes +void rlUpdateVertexBuffer(unsigned int id, const void *data, int dataSize, int offset) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glBindBuffer(GL_ARRAY_BUFFER, id); + glBufferSubData(GL_ARRAY_BUFFER, offset, dataSize, data); +#endif +} + +// Update vertex buffer elements with new data +// NOTE: dataSize and offset must be provided in bytes +void rlUpdateVertexBufferElements(unsigned int id, const void *data, int dataSize, int offset) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, id); + glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, offset, dataSize, data); +#endif +} + +// Enable vertex array object (VAO) +bool rlEnableVertexArray(unsigned int vaoId) +{ + bool result = false; +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + if (RLGL.ExtSupported.vao) + { + glBindVertexArray(vaoId); + result = true; + } +#endif + return result; +} + +// Disable vertex array object (VAO) +void rlDisableVertexArray(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + if (RLGL.ExtSupported.vao) glBindVertexArray(0); +#endif +} + +// Enable vertex attribute index +void rlEnableVertexAttribute(unsigned int index) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glEnableVertexAttribArray(index); +#endif +} + +// Disable vertex attribute index +void rlDisableVertexAttribute(unsigned int index) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glDisableVertexAttribArray(index); +#endif +} + +// Draw vertex array +void rlDrawVertexArray(int offset, int count) +{ + glDrawArrays(GL_TRIANGLES, offset, count); +} + +// Draw vertex array elements +void rlDrawVertexArrayElements(int offset, int count, const void *buffer) +{ + // NOTE: Added pointer math separately from function to avoid UBSAN complaining + unsigned short *bufferPtr = (unsigned short *)buffer; + if (offset > 0) bufferPtr += offset; + + glDrawElements(GL_TRIANGLES, count, GL_UNSIGNED_SHORT, (const unsigned short *)bufferPtr); +} + +// Draw vertex array instanced +void rlDrawVertexArrayInstanced(int offset, int count, int instances) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glDrawArraysInstanced(GL_TRIANGLES, 0, count, instances); +#endif +} + +// Draw vertex array elements instanced +void rlDrawVertexArrayElementsInstanced(int offset, int count, const void *buffer, int instances) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // NOTE: Added pointer math separately from function to avoid UBSAN complaining + unsigned short *bufferPtr = (unsigned short *)buffer; + if (offset > 0) bufferPtr += offset; + + glDrawElementsInstanced(GL_TRIANGLES, count, GL_UNSIGNED_SHORT, (const unsigned short *)bufferPtr, instances); +#endif +} + +#if defined(GRAPHICS_API_OPENGL_11) +// Enable vertex state pointer +void rlEnableStatePointer(int vertexAttribType, void *buffer) +{ + if (buffer != NULL) glEnableClientState(vertexAttribType); + switch (vertexAttribType) + { + case GL_VERTEX_ARRAY: glVertexPointer(3, GL_FLOAT, 0, buffer); break; + case GL_TEXTURE_COORD_ARRAY: glTexCoordPointer(2, GL_FLOAT, 0, buffer); break; + case GL_NORMAL_ARRAY: if (buffer != NULL) glNormalPointer(GL_FLOAT, 0, buffer); break; + case GL_COLOR_ARRAY: if (buffer != NULL) glColorPointer(4, GL_UNSIGNED_BYTE, 0, buffer); break; + //case GL_INDEX_ARRAY: if (buffer != NULL) glIndexPointer(GL_SHORT, 0, buffer); break; // Indexed colors + default: break; + } +} + +// Disable vertex state pointer +void rlDisableStatePointer(int vertexAttribType) +{ + glDisableClientState(vertexAttribType); +} +#endif + +// Load vertex array object (VAO) +unsigned int rlLoadVertexArray(void) +{ + unsigned int vaoId = 0; +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + if (RLGL.ExtSupported.vao) + { + glGenVertexArrays(1, &vaoId); + } +#endif + return vaoId; +} + +// Set vertex attribute +void rlSetVertexAttribute(unsigned int index, int compSize, int type, bool normalized, int stride, int offset) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // NOTE: Data type could be: GL_BYTE, GL_UNSIGNED_BYTE, GL_SHORT, GL_UNSIGNED_SHORT, GL_INT, GL_UNSIGNED_INT + // Additional types (depends on OpenGL version or extensions): + // - GL_HALF_FLOAT, GL_FLOAT, GL_DOUBLE, GL_FIXED, + // - GL_INT_2_10_10_10_REV, GL_UNSIGNED_INT_2_10_10_10_REV, GL_UNSIGNED_INT_10F_11F_11F_REV + + size_t offsetNative = offset; + glVertexAttribPointer(index, compSize, type, normalized, stride, (void *)offsetNative); +#endif +} + +// Set vertex attribute divisor +void rlSetVertexAttributeDivisor(unsigned int index, int divisor) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glVertexAttribDivisor(index, divisor); +#endif +} + +// Unload vertex array object (VAO) +void rlUnloadVertexArray(unsigned int vaoId) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + if (RLGL.ExtSupported.vao) + { + glBindVertexArray(0); + glDeleteVertexArrays(1, &vaoId); + TRACELOG(RL_LOG_INFO, "VAO: [ID %i] Unloaded vertex array data from VRAM (GPU)", vaoId); + } +#endif +} + +// Unload vertex buffer (VBO) +void rlUnloadVertexBuffer(unsigned int vboId) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glDeleteBuffers(1, &vboId); + //TRACELOG(RL_LOG_INFO, "VBO: Unloaded vertex data from VRAM (GPU)"); +#endif +} + +// Shaders management +//----------------------------------------------------------------------------------------------- +// Load shader from code strings +// NOTE: If shader string is NULL, using default vertex/fragment shaders +unsigned int rlLoadShaderCode(const char *vsCode, const char *fsCode) +{ + unsigned int id = 0; + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + unsigned int vertexShaderId = 0; + unsigned int fragmentShaderId = 0; + + // Compile vertex shader (if provided) + // NOTE: If not vertex shader is provided, use default one + if (vsCode != NULL) vertexShaderId = rlCompileShader(vsCode, GL_VERTEX_SHADER); + else vertexShaderId = RLGL.State.defaultVShaderId; + + // Compile fragment shader (if provided) + // NOTE: If not vertex shader is provided, use default one + if (fsCode != NULL) fragmentShaderId = rlCompileShader(fsCode, GL_FRAGMENT_SHADER); + else fragmentShaderId = RLGL.State.defaultFShaderId; + + // In case vertex and fragment shader are the default ones, no need to recompile, we can just assign the default shader program id + if ((vertexShaderId == RLGL.State.defaultVShaderId) && (fragmentShaderId == RLGL.State.defaultFShaderId)) id = RLGL.State.defaultShaderId; + else if ((vertexShaderId > 0) && (fragmentShaderId > 0)) + { + // One of or both shader are new, we need to compile a new shader program + id = rlLoadShaderProgram(vertexShaderId, fragmentShaderId); + + // We can detach and delete vertex/fragment shaders (if not default ones) + // NOTE: We detach shader before deletion to make sure memory is freed + if (vertexShaderId != RLGL.State.defaultVShaderId) + { + // WARNING: Shader program linkage could fail and returned id is 0 + if (id > 0) glDetachShader(id, vertexShaderId); + glDeleteShader(vertexShaderId); + } + if (fragmentShaderId != RLGL.State.defaultFShaderId) + { + // WARNING: Shader program linkage could fail and returned id is 0 + if (id > 0) glDetachShader(id, fragmentShaderId); + glDeleteShader(fragmentShaderId); + } + + // In case shader program loading failed, we assign default shader + if (id == 0) + { + // In case shader loading fails, we return the default shader + TRACELOG(RL_LOG_WARNING, "SHADER: Failed to load custom shader code, using default shader"); + id = RLGL.State.defaultShaderId; + } + /* + else + { + // Get available shader uniforms + // NOTE: This information is useful for debug... + int uniformCount = -1; + glGetProgramiv(id, GL_ACTIVE_UNIFORMS, &uniformCount); + + for (int i = 0; i < uniformCount; i++) + { + int namelen = -1; + int num = -1; + char name[256] = { 0 }; // Assume no variable names longer than 256 + GLenum type = GL_ZERO; + + // Get the name of the uniforms + glGetActiveUniform(id, i, sizeof(name) - 1, &namelen, &num, &type, name); + + name[namelen] = 0; + TRACELOGD("SHADER: [ID %i] Active uniform (%s) set at location: %i", id, name, glGetUniformLocation(id, name)); + } + } + */ + } +#endif + + return id; +} + +// Compile custom shader and return shader id +unsigned int rlCompileShader(const char *shaderCode, int type) +{ + unsigned int shader = 0; + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + shader = glCreateShader(type); + glShaderSource(shader, 1, &shaderCode, NULL); + + GLint success = 0; + glCompileShader(shader); + glGetShaderiv(shader, GL_COMPILE_STATUS, &success); + + if (success == GL_FALSE) + { + switch (type) + { + case GL_VERTEX_SHADER: TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Failed to compile vertex shader code", shader); break; + case GL_FRAGMENT_SHADER: TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Failed to compile fragment shader code", shader); break; + //case GL_GEOMETRY_SHADER: + #if defined(GRAPHICS_API_OPENGL_43) + case GL_COMPUTE_SHADER: TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Failed to compile compute shader code", shader); break; + #elif defined(GRAPHICS_API_OPENGL_33) + case GL_COMPUTE_SHADER: TRACELOG(RL_LOG_WARNING, "SHADER: Compute shaders not enabled. Define GRAPHICS_API_OPENGL_43", shader); break; + #endif + default: break; + } + + int maxLength = 0; + glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &maxLength); + + if (maxLength > 0) + { + int length = 0; + char *log = (char *)RL_CALLOC(maxLength, sizeof(char)); + glGetShaderInfoLog(shader, maxLength, &length, log); + TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Compile error: %s", shader, log); + RL_FREE(log); + } + + shader = 0; + } + else + { + switch (type) + { + case GL_VERTEX_SHADER: TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Vertex shader compiled successfully", shader); break; + case GL_FRAGMENT_SHADER: TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Fragment shader compiled successfully", shader); break; + //case GL_GEOMETRY_SHADER: + #if defined(GRAPHICS_API_OPENGL_43) + case GL_COMPUTE_SHADER: TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Compute shader compiled successfully", shader); break; + #elif defined(GRAPHICS_API_OPENGL_33) + case GL_COMPUTE_SHADER: TRACELOG(RL_LOG_WARNING, "SHADER: Compute shaders not enabled. Define GRAPHICS_API_OPENGL_43", shader); break; + #endif + default: break; + } + } +#endif + + return shader; +} + +// Load custom shader strings and return program id +unsigned int rlLoadShaderProgram(unsigned int vShaderId, unsigned int fShaderId) +{ + unsigned int program = 0; + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + GLint success = 0; + program = glCreateProgram(); + + glAttachShader(program, vShaderId); + glAttachShader(program, fShaderId); + + // NOTE: Default attribute shader locations must be Bound before linking + glBindAttribLocation(program, RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION, RL_DEFAULT_SHADER_ATTRIB_NAME_POSITION); + glBindAttribLocation(program, RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD, RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD); + glBindAttribLocation(program, RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL, RL_DEFAULT_SHADER_ATTRIB_NAME_NORMAL); + glBindAttribLocation(program, RL_DEFAULT_SHADER_ATTRIB_LOCATION_COLOR, RL_DEFAULT_SHADER_ATTRIB_NAME_COLOR); + glBindAttribLocation(program, RL_DEFAULT_SHADER_ATTRIB_LOCATION_TANGENT, RL_DEFAULT_SHADER_ATTRIB_NAME_TANGENT); + glBindAttribLocation(program, RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD2, RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD2); + +#ifdef RL_SUPPORT_MESH_GPU_SKINNING + glBindAttribLocation(program, RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEIDS, RL_DEFAULT_SHADER_ATTRIB_NAME_BONEIDS); + glBindAttribLocation(program, RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEWEIGHTS, RL_DEFAULT_SHADER_ATTRIB_NAME_BONEWEIGHTS); +#endif + + // NOTE: If some attrib name is no found on the shader, it locations becomes -1 + + glLinkProgram(program); + + // NOTE: All uniform variables are intitialised to 0 when a program links + + glGetProgramiv(program, GL_LINK_STATUS, &success); + + if (success == GL_FALSE) + { + TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Failed to link shader program", program); + + int maxLength = 0; + glGetProgramiv(program, GL_INFO_LOG_LENGTH, &maxLength); + + if (maxLength > 0) + { + int length = 0; + char *log = (char *)RL_CALLOC(maxLength, sizeof(char)); + glGetProgramInfoLog(program, maxLength, &length, log); + TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Link error: %s", program, log); + RL_FREE(log); + } + + glDeleteProgram(program); + + program = 0; + } + else + { + // Get the size of compiled shader program (not available on OpenGL ES 2.0) + // NOTE: If GL_LINK_STATUS is GL_FALSE, program binary length is zero + //GLint binarySize = 0; + //glGetProgramiv(id, GL_PROGRAM_BINARY_LENGTH, &binarySize); + + TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Program shader loaded successfully", program); + } +#endif + return program; +} + +// Unload shader program +void rlUnloadShaderProgram(unsigned int id) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glDeleteProgram(id); + + TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Unloaded shader program data from VRAM (GPU)", id); +#endif +} + +// Get shader location uniform +int rlGetLocationUniform(unsigned int shaderId, const char *uniformName) +{ + int location = -1; +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + location = glGetUniformLocation(shaderId, uniformName); + + //if (location == -1) TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Failed to find shader uniform: %s", shaderId, uniformName); + //else TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Shader uniform (%s) set at location: %i", shaderId, uniformName, location); +#endif + return location; +} + +// Get shader location attribute +int rlGetLocationAttrib(unsigned int shaderId, const char *attribName) +{ + int location = -1; +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + location = glGetAttribLocation(shaderId, attribName); + + //if (location == -1) TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Failed to find shader attribute: %s", shaderId, attribName); + //else TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Shader attribute (%s) set at location: %i", shaderId, attribName, location); +#endif + return location; +} + +// Set shader value uniform +void rlSetUniform(int locIndex, const void *value, int uniformType, int count) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + switch (uniformType) + { + case RL_SHADER_UNIFORM_FLOAT: glUniform1fv(locIndex, count, (float *)value); break; + case RL_SHADER_UNIFORM_VEC2: glUniform2fv(locIndex, count, (float *)value); break; + case RL_SHADER_UNIFORM_VEC3: glUniform3fv(locIndex, count, (float *)value); break; + case RL_SHADER_UNIFORM_VEC4: glUniform4fv(locIndex, count, (float *)value); break; + case RL_SHADER_UNIFORM_INT: glUniform1iv(locIndex, count, (int *)value); break; + case RL_SHADER_UNIFORM_IVEC2: glUniform2iv(locIndex, count, (int *)value); break; + case RL_SHADER_UNIFORM_IVEC3: glUniform3iv(locIndex, count, (int *)value); break; + case RL_SHADER_UNIFORM_IVEC4: glUniform4iv(locIndex, count, (int *)value); break; + #if !defined(GRAPHICS_API_OPENGL_ES2) + case RL_SHADER_UNIFORM_UINT: glUniform1uiv(locIndex, count, (unsigned int *)value); break; + case RL_SHADER_UNIFORM_UIVEC2: glUniform2uiv(locIndex, count, (unsigned int *)value); break; + case RL_SHADER_UNIFORM_UIVEC3: glUniform3uiv(locIndex, count, (unsigned int *)value); break; + case RL_SHADER_UNIFORM_UIVEC4: glUniform4uiv(locIndex, count, (unsigned int *)value); break; + #endif + case RL_SHADER_UNIFORM_SAMPLER2D: glUniform1iv(locIndex, count, (int *)value); break; + default: TRACELOG(RL_LOG_WARNING, "SHADER: Failed to set uniform value, data type not recognized"); + + // TODO: Support glUniform1uiv(), glUniform2uiv(), glUniform3uiv(), glUniform4uiv() + } +#endif +} + +// Set shader value attribute +void rlSetVertexAttributeDefault(int locIndex, const void *value, int attribType, int count) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + switch (attribType) + { + case RL_SHADER_ATTRIB_FLOAT: if (count == 1) glVertexAttrib1fv(locIndex, (float *)value); break; + case RL_SHADER_ATTRIB_VEC2: if (count == 2) glVertexAttrib2fv(locIndex, (float *)value); break; + case RL_SHADER_ATTRIB_VEC3: if (count == 3) glVertexAttrib3fv(locIndex, (float *)value); break; + case RL_SHADER_ATTRIB_VEC4: if (count == 4) glVertexAttrib4fv(locIndex, (float *)value); break; + default: TRACELOG(RL_LOG_WARNING, "SHADER: Failed to set attrib default value, data type not recognized"); + } +#endif +} + +// Set shader value uniform matrix +void rlSetUniformMatrix(int locIndex, Matrix mat) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + float matfloat[16] = { + mat.m0, mat.m1, mat.m2, mat.m3, + mat.m4, mat.m5, mat.m6, mat.m7, + mat.m8, mat.m9, mat.m10, mat.m11, + mat.m12, mat.m13, mat.m14, mat.m15 + }; + glUniformMatrix4fv(locIndex, 1, false, matfloat); +#endif +} + +// Set shader value uniform matrix +void rlSetUniformMatrices(int locIndex, const Matrix *matrices, int count) +{ +#if defined(GRAPHICS_API_OPENGL_33) + glUniformMatrix4fv(locIndex, count, true, (const float *)matrices); +#elif defined(GRAPHICS_API_OPENGL_ES2) + // WARNING: WebGL does not support Matrix transpose ("true" parameter) + // REF: https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/uniformMatrix + glUniformMatrix4fv(locIndex, count, false, (const float *)matrices); +#endif +} + +// Set shader value uniform sampler +void rlSetUniformSampler(int locIndex, unsigned int textureId) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // Check if texture is already active + for (int i = 0; i < RL_DEFAULT_BATCH_MAX_TEXTURE_UNITS; i++) + { + if (RLGL.State.activeTextureId[i] == textureId) + { + glUniform1i(locIndex, 1 + i); + return; + } + } + + // Register a new active texture for the internal batch system + // NOTE: Default texture is always activated as GL_TEXTURE0 + for (int i = 0; i < RL_DEFAULT_BATCH_MAX_TEXTURE_UNITS; i++) + { + if (RLGL.State.activeTextureId[i] == 0) + { + glUniform1i(locIndex, 1 + i); // Activate new texture unit + RLGL.State.activeTextureId[i] = textureId; // Save texture id for binding on drawing + break; + } + } +#endif +} + +// Set shader currently active (id and locations) +void rlSetShader(unsigned int id, int *locs) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + if (RLGL.State.currentShaderId != id) + { + rlDrawRenderBatch(RLGL.currentBatch); + RLGL.State.currentShaderId = id; + RLGL.State.currentShaderLocs = locs; + } +#endif +} + +// Load compute shader program +unsigned int rlLoadComputeShaderProgram(unsigned int shaderId) +{ + unsigned int program = 0; + +#if defined(GRAPHICS_API_OPENGL_43) + GLint success = 0; + program = glCreateProgram(); + glAttachShader(program, shaderId); + glLinkProgram(program); + + // NOTE: All uniform variables are intitialised to 0 when a program links + + glGetProgramiv(program, GL_LINK_STATUS, &success); + + if (success == GL_FALSE) + { + TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Failed to link compute shader program", program); + + int maxLength = 0; + glGetProgramiv(program, GL_INFO_LOG_LENGTH, &maxLength); + + if (maxLength > 0) + { + int length = 0; + char *log = (char *)RL_CALLOC(maxLength, sizeof(char)); + glGetProgramInfoLog(program, maxLength, &length, log); + TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Link error: %s", program, log); + RL_FREE(log); + } + + glDeleteProgram(program); + + program = 0; + } + else + { + // Get the size of compiled shader program (not available on OpenGL ES 2.0) + // NOTE: If GL_LINK_STATUS is GL_FALSE, program binary length is zero + //GLint binarySize = 0; + //glGetProgramiv(id, GL_PROGRAM_BINARY_LENGTH, &binarySize); + + TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Compute shader program loaded successfully", program); + } +#else + TRACELOG(RL_LOG_WARNING, "SHADER: Compute shaders not enabled. Define GRAPHICS_API_OPENGL_43"); +#endif + + return program; +} + +// Dispatch compute shader (equivalent to *draw* for graphics pilepine) +void rlComputeShaderDispatch(unsigned int groupX, unsigned int groupY, unsigned int groupZ) +{ +#if defined(GRAPHICS_API_OPENGL_43) + glDispatchCompute(groupX, groupY, groupZ); +#endif +} + +// Load shader storage buffer object (SSBO) +unsigned int rlLoadShaderBuffer(unsigned int size, const void *data, int usageHint) +{ + unsigned int ssbo = 0; + +#if defined(GRAPHICS_API_OPENGL_43) + glGenBuffers(1, &ssbo); + glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssbo); + glBufferData(GL_SHADER_STORAGE_BUFFER, size, data, usageHint? usageHint : RL_STREAM_COPY); + if (data == NULL) glClearBufferData(GL_SHADER_STORAGE_BUFFER, GL_R8UI, GL_RED_INTEGER, GL_UNSIGNED_BYTE, NULL); // Clear buffer data to 0 + glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); +#else + TRACELOG(RL_LOG_WARNING, "SSBO: SSBO not enabled. Define GRAPHICS_API_OPENGL_43"); +#endif + + return ssbo; +} + +// Unload shader storage buffer object (SSBO) +void rlUnloadShaderBuffer(unsigned int ssboId) +{ +#if defined(GRAPHICS_API_OPENGL_43) + glDeleteBuffers(1, &ssboId); +#else + TRACELOG(RL_LOG_WARNING, "SSBO: SSBO not enabled. Define GRAPHICS_API_OPENGL_43"); +#endif + +} + +// Update SSBO buffer data +void rlUpdateShaderBuffer(unsigned int id, const void *data, unsigned int dataSize, unsigned int offset) +{ +#if defined(GRAPHICS_API_OPENGL_43) + glBindBuffer(GL_SHADER_STORAGE_BUFFER, id); + glBufferSubData(GL_SHADER_STORAGE_BUFFER, offset, dataSize, data); +#endif +} + +// Get SSBO buffer size +unsigned int rlGetShaderBufferSize(unsigned int id) +{ +#if defined(GRAPHICS_API_OPENGL_43) + GLint64 size = 0; + glBindBuffer(GL_SHADER_STORAGE_BUFFER, id); + glGetBufferParameteri64v(GL_SHADER_STORAGE_BUFFER, GL_BUFFER_SIZE, &size); + return (size > 0)? (unsigned int)size : 0; +#else + return 0; +#endif +} + +// Read SSBO buffer data (GPU->CPU) +void rlReadShaderBuffer(unsigned int id, void *dest, unsigned int count, unsigned int offset) +{ +#if defined(GRAPHICS_API_OPENGL_43) + glBindBuffer(GL_SHADER_STORAGE_BUFFER, id); + glGetBufferSubData(GL_SHADER_STORAGE_BUFFER, offset, count, dest); +#endif +} + +// Bind SSBO buffer +void rlBindShaderBuffer(unsigned int id, unsigned int index) +{ +#if defined(GRAPHICS_API_OPENGL_43) + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, index, id); +#endif +} + +// Copy SSBO buffer data +void rlCopyShaderBuffer(unsigned int destId, unsigned int srcId, unsigned int destOffset, unsigned int srcOffset, unsigned int count) +{ +#if defined(GRAPHICS_API_OPENGL_43) + glBindBuffer(GL_COPY_READ_BUFFER, srcId); + glBindBuffer(GL_COPY_WRITE_BUFFER, destId); + glCopyBufferSubData(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, srcOffset, destOffset, count); +#endif +} + +// Bind image texture +void rlBindImageTexture(unsigned int id, unsigned int index, int format, bool readonly) +{ +#if defined(GRAPHICS_API_OPENGL_43) + unsigned int glInternalFormat = 0, glFormat = 0, glType = 0; + + rlGetGlTextureFormats(format, &glInternalFormat, &glFormat, &glType); + glBindImageTexture(index, id, 0, 0, 0, readonly? GL_READ_ONLY : GL_READ_WRITE, glInternalFormat); +#else + TRACELOG(RL_LOG_WARNING, "TEXTURE: Image texture binding not enabled. Define GRAPHICS_API_OPENGL_43"); +#endif +} + +// Matrix state management +//----------------------------------------------------------------------------------------- +// Get internal modelview matrix +Matrix rlGetMatrixModelview(void) +{ + Matrix matrix = rlMatrixIdentity(); +#if defined(GRAPHICS_API_OPENGL_11) + float mat[16]; + glGetFloatv(GL_MODELVIEW_MATRIX, mat); + matrix.m0 = mat[0]; + matrix.m1 = mat[1]; + matrix.m2 = mat[2]; + matrix.m3 = mat[3]; + matrix.m4 = mat[4]; + matrix.m5 = mat[5]; + matrix.m6 = mat[6]; + matrix.m7 = mat[7]; + matrix.m8 = mat[8]; + matrix.m9 = mat[9]; + matrix.m10 = mat[10]; + matrix.m11 = mat[11]; + matrix.m12 = mat[12]; + matrix.m13 = mat[13]; + matrix.m14 = mat[14]; + matrix.m15 = mat[15]; +#else + matrix = RLGL.State.modelview; +#endif + return matrix; +} + +// Get internal projection matrix +Matrix rlGetMatrixProjection(void) +{ +#if defined(GRAPHICS_API_OPENGL_11) + float mat[16]; + glGetFloatv(GL_PROJECTION_MATRIX,mat); + Matrix m; + m.m0 = mat[0]; + m.m1 = mat[1]; + m.m2 = mat[2]; + m.m3 = mat[3]; + m.m4 = mat[4]; + m.m5 = mat[5]; + m.m6 = mat[6]; + m.m7 = mat[7]; + m.m8 = mat[8]; + m.m9 = mat[9]; + m.m10 = mat[10]; + m.m11 = mat[11]; + m.m12 = mat[12]; + m.m13 = mat[13]; + m.m14 = mat[14]; + m.m15 = mat[15]; + return m; +#else + return RLGL.State.projection; +#endif +} + +// Get internal accumulated transform matrix +Matrix rlGetMatrixTransform(void) +{ + Matrix mat = rlMatrixIdentity(); +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // TODO: Consider possible transform matrices in the RLGL.State.stack + // Is this the right order? or should we start with the first stored matrix instead of the last one? + //Matrix matStackTransform = rlMatrixIdentity(); + //for (int i = RLGL.State.stackCounter; i > 0; i--) matStackTransform = rlMatrixMultiply(RLGL.State.stack[i], matStackTransform); + mat = RLGL.State.transform; +#endif + return mat; +} + +// Get internal projection matrix for stereo render (selected eye) +Matrix rlGetMatrixProjectionStereo(int eye) +{ + Matrix mat = rlMatrixIdentity(); +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + mat = RLGL.State.projectionStereo[eye]; +#endif + return mat; +} + +// Get internal view offset matrix for stereo render (selected eye) +Matrix rlGetMatrixViewOffsetStereo(int eye) +{ + Matrix mat = rlMatrixIdentity(); +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + mat = RLGL.State.viewOffsetStereo[eye]; +#endif + return mat; +} + +// Set a custom modelview matrix (replaces internal modelview matrix) +void rlSetMatrixModelview(Matrix view) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + RLGL.State.modelview = view; +#endif +} + +// Set a custom projection matrix (replaces internal projection matrix) +void rlSetMatrixProjection(Matrix projection) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + RLGL.State.projection = projection; +#endif +} + +// Set eyes projection matrices for stereo rendering +void rlSetMatrixProjectionStereo(Matrix right, Matrix left) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + RLGL.State.projectionStereo[0] = right; + RLGL.State.projectionStereo[1] = left; +#endif +} + +// Set eyes view offsets matrices for stereo rendering +void rlSetMatrixViewOffsetStereo(Matrix right, Matrix left) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + RLGL.State.viewOffsetStereo[0] = right; + RLGL.State.viewOffsetStereo[1] = left; +#endif +} + +// Load and draw a quad in NDC +void rlLoadDrawQuad(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + unsigned int quadVAO = 0; + unsigned int quadVBO = 0; + + float vertices[] = { + // Positions Texcoords + -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, + -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, + 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, + 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, + }; + + // Gen VAO to contain VBO + glGenVertexArrays(1, &quadVAO); + glBindVertexArray(quadVAO); + + // Gen and fill vertex buffer (VBO) + glGenBuffers(1, &quadVBO); + glBindBuffer(GL_ARRAY_BUFFER, quadVBO); + glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), &vertices, GL_STATIC_DRAW); + + // Bind vertex attributes (position, texcoords) + glEnableVertexAttribArray(RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION); + glVertexAttribPointer(RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION, 3, GL_FLOAT, GL_FALSE, 5*sizeof(float), (void *)0); // Positions + glEnableVertexAttribArray(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD); + glVertexAttribPointer(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD, 2, GL_FLOAT, GL_FALSE, 5*sizeof(float), (void *)(3*sizeof(float))); // Texcoords + + // Draw quad + glBindVertexArray(quadVAO); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + glBindVertexArray(0); + + // Delete buffers (VBO and VAO) + glDeleteBuffers(1, &quadVBO); + glDeleteVertexArrays(1, &quadVAO); +#endif +} + +// Load and draw a cube in NDC +void rlLoadDrawCube(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + unsigned int cubeVAO = 0; + unsigned int cubeVBO = 0; + + float vertices[] = { + // Positions Normals Texcoords + -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, + 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, + 1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f, + 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, + -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, + -1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f, + -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, + 1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, + 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, + -1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, + -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, + -1.0f, 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, + -1.0f, 1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f, + -1.0f, -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, + -1.0f, -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, + -1.0f, -1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, + -1.0f, 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, + 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, + 1.0f, -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, + 1.0f, 1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, + 1.0f, -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, + 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, + 1.0f, -1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, + -1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, + 1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 1.0f, + 1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, + 1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, + -1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, + -1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, + -1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, + 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, + 1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, + -1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, + -1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f + }; + + // Gen VAO to contain VBO + glGenVertexArrays(1, &cubeVAO); + glBindVertexArray(cubeVAO); + + // Gen and fill vertex buffer (VBO) + glGenBuffers(1, &cubeVBO); + glBindBuffer(GL_ARRAY_BUFFER, cubeVBO); + glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); + + // Bind vertex attributes (position, normals, texcoords) + glBindVertexArray(cubeVAO); + glEnableVertexAttribArray(RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION); + glVertexAttribPointer(RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION, 3, GL_FLOAT, GL_FALSE, 8*sizeof(float), (void *)0); // Positions + glEnableVertexAttribArray(RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL); + glVertexAttribPointer(RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL, 3, GL_FLOAT, GL_FALSE, 8*sizeof(float), (void *)(3*sizeof(float))); // Normals + glEnableVertexAttribArray(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD); + glVertexAttribPointer(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD, 2, GL_FLOAT, GL_FALSE, 8*sizeof(float), (void *)(6*sizeof(float))); // Texcoords + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindVertexArray(0); + + // Draw cube + glBindVertexArray(cubeVAO); + glDrawArrays(GL_TRIANGLES, 0, 36); + glBindVertexArray(0); + + // Delete VBO and VAO + glDeleteBuffers(1, &cubeVBO); + glDeleteVertexArrays(1, &cubeVAO); +#endif +} + +// Get name string for pixel format +const char *rlGetPixelFormatName(unsigned int format) +{ + switch (format) + { + case RL_PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: return "GRAYSCALE"; break; // 8 bit per pixel (no alpha) + case RL_PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: return "GRAY_ALPHA"; break; // 8*2 bpp (2 channels) + case RL_PIXELFORMAT_UNCOMPRESSED_R5G6B5: return "R5G6B5"; break; // 16 bpp + case RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8: return "R8G8B8"; break; // 24 bpp + case RL_PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: return "R5G5B5A1"; break; // 16 bpp (1 bit alpha) + case RL_PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: return "R4G4B4A4"; break; // 16 bpp (4 bit alpha) + case RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: return "R8G8B8A8"; break; // 32 bpp + case RL_PIXELFORMAT_UNCOMPRESSED_R32: return "R32"; break; // 32 bpp (1 channel - float) + case RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32: return "R32G32B32"; break; // 32*3 bpp (3 channels - float) + case RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: return "R32G32B32A32"; break; // 32*4 bpp (4 channels - float) + case RL_PIXELFORMAT_UNCOMPRESSED_R16: return "R16"; break; // 16 bpp (1 channel - half float) + case RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16: return "R16G16B16"; break; // 16*3 bpp (3 channels - half float) + case RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: return "R16G16B16A16"; break; // 16*4 bpp (4 channels - half float) + case RL_PIXELFORMAT_COMPRESSED_DXT1_RGB: return "DXT1_RGB"; break; // 4 bpp (no alpha) + case RL_PIXELFORMAT_COMPRESSED_DXT1_RGBA: return "DXT1_RGBA"; break; // 4 bpp (1 bit alpha) + case RL_PIXELFORMAT_COMPRESSED_DXT3_RGBA: return "DXT3_RGBA"; break; // 8 bpp + case RL_PIXELFORMAT_COMPRESSED_DXT5_RGBA: return "DXT5_RGBA"; break; // 8 bpp + case RL_PIXELFORMAT_COMPRESSED_ETC1_RGB: return "ETC1_RGB"; break; // 4 bpp + case RL_PIXELFORMAT_COMPRESSED_ETC2_RGB: return "ETC2_RGB"; break; // 4 bpp + case RL_PIXELFORMAT_COMPRESSED_ETC2_EAC_RGBA: return "ETC2_RGBA"; break; // 8 bpp + case RL_PIXELFORMAT_COMPRESSED_PVRT_RGB: return "PVRT_RGB"; break; // 4 bpp + case RL_PIXELFORMAT_COMPRESSED_PVRT_RGBA: return "PVRT_RGBA"; break; // 4 bpp + case RL_PIXELFORMAT_COMPRESSED_ASTC_4x4_RGBA: return "ASTC_4x4_RGBA"; break; // 8 bpp + case RL_PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA: return "ASTC_8x8_RGBA"; break; // 2 bpp + default: return "UNKNOWN"; break; + } +} + +//---------------------------------------------------------------------------------- +// Module specific Functions Definition +//---------------------------------------------------------------------------------- +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) +// Load default shader (just vertex positioning and texture coloring) +// NOTE: This shader program is used for internal buffers +// NOTE: Loaded: RLGL.State.defaultShaderId, RLGL.State.defaultShaderLocs +static void rlLoadShaderDefault(void) +{ + RLGL.State.defaultShaderLocs = (int *)RL_CALLOC(RL_MAX_SHADER_LOCATIONS, sizeof(int)); + + // NOTE: All locations must be reseted to -1 (no location) + for (int i = 0; i < RL_MAX_SHADER_LOCATIONS; i++) RLGL.State.defaultShaderLocs[i] = -1; + + // Vertex shader directly defined, no external file required + const char *defaultVShaderCode = +#if defined(GRAPHICS_API_OPENGL_21) + "#version 120 \n" + "attribute vec3 vertexPosition; \n" + "attribute vec2 vertexTexCoord; \n" + "attribute vec4 vertexColor; \n" + "varying vec2 fragTexCoord; \n" + "varying vec4 fragColor; \n" +#elif defined(GRAPHICS_API_OPENGL_33) + "#version 330 \n" + "in vec3 vertexPosition; \n" + "in vec2 vertexTexCoord; \n" + "in vec4 vertexColor; \n" + "out vec2 fragTexCoord; \n" + "out vec4 fragColor; \n" +#endif + +#if defined(GRAPHICS_API_OPENGL_ES3) + "#version 300 es \n" + "precision mediump float; \n" // Precision required for OpenGL ES3 (WebGL 2) (on some browsers) + "in vec3 vertexPosition; \n" + "in vec2 vertexTexCoord; \n" + "in vec4 vertexColor; \n" + "out vec2 fragTexCoord; \n" + "out vec4 fragColor; \n" +#elif defined(GRAPHICS_API_OPENGL_ES2) + "#version 100 \n" + "precision mediump float; \n" // Precision required for OpenGL ES2 (WebGL) (on some browsers) + "attribute vec3 vertexPosition; \n" + "attribute vec2 vertexTexCoord; \n" + "attribute vec4 vertexColor; \n" + "varying vec2 fragTexCoord; \n" + "varying vec4 fragColor; \n" +#endif + + "uniform mat4 mvp; \n" + "void main() \n" + "{ \n" + " fragTexCoord = vertexTexCoord; \n" + " fragColor = vertexColor; \n" + " gl_Position = mvp*vec4(vertexPosition, 1.0); \n" + "} \n"; + + // Fragment shader directly defined, no external file required + const char *defaultFShaderCode = +#if defined(GRAPHICS_API_OPENGL_21) + "#version 120 \n" + "varying vec2 fragTexCoord; \n" + "varying vec4 fragColor; \n" + "uniform sampler2D texture0; \n" + "uniform vec4 colDiffuse; \n" + "void main() \n" + "{ \n" + " vec4 texelColor = texture2D(texture0, fragTexCoord); \n" + " gl_FragColor = texelColor*colDiffuse*fragColor; \n" + "} \n"; +#elif defined(GRAPHICS_API_OPENGL_33) + "#version 330 \n" + "in vec2 fragTexCoord; \n" + "in vec4 fragColor; \n" + "out vec4 finalColor; \n" + "uniform sampler2D texture0; \n" + "uniform vec4 colDiffuse; \n" + "void main() \n" + "{ \n" + " vec4 texelColor = texture(texture0, fragTexCoord); \n" + " finalColor = texelColor*colDiffuse*fragColor; \n" + "} \n"; +#endif + +#if defined(GRAPHICS_API_OPENGL_ES3) + "#version 300 es \n" + "precision mediump float; \n" // Precision required for OpenGL ES3 (WebGL 2) + "in vec2 fragTexCoord; \n" + "in vec4 fragColor; \n" + "out vec4 finalColor; \n" + "uniform sampler2D texture0; \n" + "uniform vec4 colDiffuse; \n" + "void main() \n" + "{ \n" + " vec4 texelColor = texture(texture0, fragTexCoord); \n" + " finalColor = texelColor*colDiffuse*fragColor; \n" + "} \n"; +#elif defined(GRAPHICS_API_OPENGL_ES2) + "#version 100 \n" + "precision mediump float; \n" // Precision required for OpenGL ES2 (WebGL) + "varying vec2 fragTexCoord; \n" + "varying vec4 fragColor; \n" + "uniform sampler2D texture0; \n" + "uniform vec4 colDiffuse; \n" + "void main() \n" + "{ \n" + " vec4 texelColor = texture2D(texture0, fragTexCoord); \n" + " gl_FragColor = texelColor*colDiffuse*fragColor; \n" + "} \n"; +#endif + + // NOTE: Compiled vertex/fragment shaders are not deleted, + // they are kept for re-use as default shaders in case some shader loading fails + RLGL.State.defaultVShaderId = rlCompileShader(defaultVShaderCode, GL_VERTEX_SHADER); // Compile default vertex shader + RLGL.State.defaultFShaderId = rlCompileShader(defaultFShaderCode, GL_FRAGMENT_SHADER); // Compile default fragment shader + + RLGL.State.defaultShaderId = rlLoadShaderProgram(RLGL.State.defaultVShaderId, RLGL.State.defaultFShaderId); + + if (RLGL.State.defaultShaderId > 0) + { + TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Default shader loaded successfully", RLGL.State.defaultShaderId); + + // Set default shader locations: attributes locations + RLGL.State.defaultShaderLocs[RL_SHADER_LOC_VERTEX_POSITION] = glGetAttribLocation(RLGL.State.defaultShaderId, RL_DEFAULT_SHADER_ATTRIB_NAME_POSITION); + RLGL.State.defaultShaderLocs[RL_SHADER_LOC_VERTEX_TEXCOORD01] = glGetAttribLocation(RLGL.State.defaultShaderId, RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD); + RLGL.State.defaultShaderLocs[RL_SHADER_LOC_VERTEX_COLOR] = glGetAttribLocation(RLGL.State.defaultShaderId, RL_DEFAULT_SHADER_ATTRIB_NAME_COLOR); + + // Set default shader locations: uniform locations + RLGL.State.defaultShaderLocs[RL_SHADER_LOC_MATRIX_MVP] = glGetUniformLocation(RLGL.State.defaultShaderId, RL_DEFAULT_SHADER_UNIFORM_NAME_MVP); + RLGL.State.defaultShaderLocs[RL_SHADER_LOC_COLOR_DIFFUSE] = glGetUniformLocation(RLGL.State.defaultShaderId, RL_DEFAULT_SHADER_UNIFORM_NAME_COLOR); + RLGL.State.defaultShaderLocs[RL_SHADER_LOC_MAP_DIFFUSE] = glGetUniformLocation(RLGL.State.defaultShaderId, RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE0); + } + else TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Failed to load default shader", RLGL.State.defaultShaderId); +} + +// Unload default shader +// NOTE: Unloads: RLGL.State.defaultShaderId, RLGL.State.defaultShaderLocs +static void rlUnloadShaderDefault(void) +{ + glUseProgram(0); + + glDetachShader(RLGL.State.defaultShaderId, RLGL.State.defaultVShaderId); + glDetachShader(RLGL.State.defaultShaderId, RLGL.State.defaultFShaderId); + glDeleteShader(RLGL.State.defaultVShaderId); + glDeleteShader(RLGL.State.defaultFShaderId); + + glDeleteProgram(RLGL.State.defaultShaderId); + + RL_FREE(RLGL.State.defaultShaderLocs); + + TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Default shader unloaded successfully", RLGL.State.defaultShaderId); +} + +#if defined(RLGL_SHOW_GL_DETAILS_INFO) +// Get compressed format official GL identifier name +static const char *rlGetCompressedFormatName(int format) +{ + switch (format) + { + // GL_EXT_texture_compression_s3tc + case 0x83F0: return "GL_COMPRESSED_RGB_S3TC_DXT1_EXT"; break; + case 0x83F1: return "GL_COMPRESSED_RGBA_S3TC_DXT1_EXT"; break; + case 0x83F2: return "GL_COMPRESSED_RGBA_S3TC_DXT3_EXT"; break; + case 0x83F3: return "GL_COMPRESSED_RGBA_S3TC_DXT5_EXT"; break; + // GL_3DFX_texture_compression_FXT1 + case 0x86B0: return "GL_COMPRESSED_RGB_FXT1_3DFX"; break; + case 0x86B1: return "GL_COMPRESSED_RGBA_FXT1_3DFX"; break; + // GL_IMG_texture_compression_pvrtc + case 0x8C00: return "GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG"; break; + case 0x8C01: return "GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG"; break; + case 0x8C02: return "GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG"; break; + case 0x8C03: return "GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG"; break; + // GL_OES_compressed_ETC1_RGB8_texture + case 0x8D64: return "GL_ETC1_RGB8_OES"; break; + // GL_ARB_texture_compression_rgtc + case 0x8DBB: return "GL_COMPRESSED_RED_RGTC1"; break; + case 0x8DBC: return "GL_COMPRESSED_SIGNED_RED_RGTC1"; break; + case 0x8DBD: return "GL_COMPRESSED_RG_RGTC2"; break; + case 0x8DBE: return "GL_COMPRESSED_SIGNED_RG_RGTC2"; break; + // GL_ARB_texture_compression_bptc + case 0x8E8C: return "GL_COMPRESSED_RGBA_BPTC_UNORM_ARB"; break; + case 0x8E8D: return "GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB"; break; + case 0x8E8E: return "GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_ARB"; break; + case 0x8E8F: return "GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB"; break; + // GL_ARB_ES3_compatibility + case 0x9274: return "GL_COMPRESSED_RGB8_ETC2"; break; + case 0x9275: return "GL_COMPRESSED_SRGB8_ETC2"; break; + case 0x9276: return "GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2"; break; + case 0x9277: return "GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2"; break; + case 0x9278: return "GL_COMPRESSED_RGBA8_ETC2_EAC"; break; + case 0x9279: return "GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC"; break; + case 0x9270: return "GL_COMPRESSED_R11_EAC"; break; + case 0x9271: return "GL_COMPRESSED_SIGNED_R11_EAC"; break; + case 0x9272: return "GL_COMPRESSED_RG11_EAC"; break; + case 0x9273: return "GL_COMPRESSED_SIGNED_RG11_EAC"; break; + // GL_KHR_texture_compression_astc_hdr + case 0x93B0: return "GL_COMPRESSED_RGBA_ASTC_4x4_KHR"; break; + case 0x93B1: return "GL_COMPRESSED_RGBA_ASTC_5x4_KHR"; break; + case 0x93B2: return "GL_COMPRESSED_RGBA_ASTC_5x5_KHR"; break; + case 0x93B3: return "GL_COMPRESSED_RGBA_ASTC_6x5_KHR"; break; + case 0x93B4: return "GL_COMPRESSED_RGBA_ASTC_6x6_KHR"; break; + case 0x93B5: return "GL_COMPRESSED_RGBA_ASTC_8x5_KHR"; break; + case 0x93B6: return "GL_COMPRESSED_RGBA_ASTC_8x6_KHR"; break; + case 0x93B7: return "GL_COMPRESSED_RGBA_ASTC_8x8_KHR"; break; + case 0x93B8: return "GL_COMPRESSED_RGBA_ASTC_10x5_KHR"; break; + case 0x93B9: return "GL_COMPRESSED_RGBA_ASTC_10x6_KHR"; break; + case 0x93BA: return "GL_COMPRESSED_RGBA_ASTC_10x8_KHR"; break; + case 0x93BB: return "GL_COMPRESSED_RGBA_ASTC_10x10_KHR"; break; + case 0x93BC: return "GL_COMPRESSED_RGBA_ASTC_12x10_KHR"; break; + case 0x93BD: return "GL_COMPRESSED_RGBA_ASTC_12x12_KHR"; break; + case 0x93D0: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR"; break; + case 0x93D1: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR"; break; + case 0x93D2: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR"; break; + case 0x93D3: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR"; break; + case 0x93D4: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR"; break; + case 0x93D5: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR"; break; + case 0x93D6: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR"; break; + case 0x93D7: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR"; break; + case 0x93D8: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR"; break; + case 0x93D9: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR"; break; + case 0x93DA: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR"; break; + case 0x93DB: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR"; break; + case 0x93DC: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR"; break; + case 0x93DD: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR"; break; + default: return "GL_COMPRESSED_UNKNOWN"; break; + } +} +#endif // RLGL_SHOW_GL_DETAILS_INFO + +#endif // GRAPHICS_API_OPENGL_33 || GRAPHICS_API_OPENGL_ES2 + +// Get pixel data size in bytes (image or texture) +// NOTE: Size depends on pixel format +static int rlGetPixelDataSize(int width, int height, int format) +{ + int dataSize = 0; // Size in bytes + int bpp = 0; // Bits per pixel + + switch (format) + { + case RL_PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: bpp = 8; break; + case RL_PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: + case RL_PIXELFORMAT_UNCOMPRESSED_R5G6B5: + case RL_PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: + case RL_PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: bpp = 16; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: bpp = 32; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8: bpp = 24; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R32: bpp = 32; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32: bpp = 32*3; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: bpp = 32*4; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R16: bpp = 16; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16: bpp = 16*3; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: bpp = 16*4; break; + case RL_PIXELFORMAT_COMPRESSED_DXT1_RGB: + case RL_PIXELFORMAT_COMPRESSED_DXT1_RGBA: + case RL_PIXELFORMAT_COMPRESSED_ETC1_RGB: + case RL_PIXELFORMAT_COMPRESSED_ETC2_RGB: + case RL_PIXELFORMAT_COMPRESSED_PVRT_RGB: + case RL_PIXELFORMAT_COMPRESSED_PVRT_RGBA: bpp = 4; break; + case RL_PIXELFORMAT_COMPRESSED_DXT3_RGBA: + case RL_PIXELFORMAT_COMPRESSED_DXT5_RGBA: + case RL_PIXELFORMAT_COMPRESSED_ETC2_EAC_RGBA: + case RL_PIXELFORMAT_COMPRESSED_ASTC_4x4_RGBA: bpp = 8; break; + case RL_PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA: bpp = 2; break; + default: break; + } + + double bytesPerPixel = (double)bpp/8.0; + dataSize = (int)(bytesPerPixel*width*height); // Total data size in bytes + + // Most compressed formats works on 4x4 blocks, + // if texture is smaller, minimum dataSize is 8 or 16 + if ((width < 4) && (height < 4)) + { + if ((format >= RL_PIXELFORMAT_COMPRESSED_DXT1_RGB) && (format < RL_PIXELFORMAT_COMPRESSED_DXT3_RGBA)) dataSize = 8; + else if ((format >= RL_PIXELFORMAT_COMPRESSED_DXT3_RGBA) && (format < RL_PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA)) dataSize = 16; + } + + return dataSize; +} + +// Auxiliar math functions + +// Get float array of matrix data +static rl_float16 rlMatrixToFloatV(Matrix mat) +{ + rl_float16 result = { 0 }; + + result.v[0] = mat.m0; + result.v[1] = mat.m1; + result.v[2] = mat.m2; + result.v[3] = mat.m3; + result.v[4] = mat.m4; + result.v[5] = mat.m5; + result.v[6] = mat.m6; + result.v[7] = mat.m7; + result.v[8] = mat.m8; + result.v[9] = mat.m9; + result.v[10] = mat.m10; + result.v[11] = mat.m11; + result.v[12] = mat.m12; + result.v[13] = mat.m13; + result.v[14] = mat.m14; + result.v[15] = mat.m15; + + return result; +} + +// Get identity matrix +static Matrix rlMatrixIdentity(void) +{ + Matrix result = { + 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f + }; + + return result; +} + +// Get two matrix multiplication +// NOTE: When multiplying matrices... the order matters! +static Matrix rlMatrixMultiply(Matrix left, Matrix right) +{ + Matrix result = { 0 }; + + result.m0 = left.m0*right.m0 + left.m1*right.m4 + left.m2*right.m8 + left.m3*right.m12; + result.m1 = left.m0*right.m1 + left.m1*right.m5 + left.m2*right.m9 + left.m3*right.m13; + result.m2 = left.m0*right.m2 + left.m1*right.m6 + left.m2*right.m10 + left.m3*right.m14; + result.m3 = left.m0*right.m3 + left.m1*right.m7 + left.m2*right.m11 + left.m3*right.m15; + result.m4 = left.m4*right.m0 + left.m5*right.m4 + left.m6*right.m8 + left.m7*right.m12; + result.m5 = left.m4*right.m1 + left.m5*right.m5 + left.m6*right.m9 + left.m7*right.m13; + result.m6 = left.m4*right.m2 + left.m5*right.m6 + left.m6*right.m10 + left.m7*right.m14; + result.m7 = left.m4*right.m3 + left.m5*right.m7 + left.m6*right.m11 + left.m7*right.m15; + result.m8 = left.m8*right.m0 + left.m9*right.m4 + left.m10*right.m8 + left.m11*right.m12; + result.m9 = left.m8*right.m1 + left.m9*right.m5 + left.m10*right.m9 + left.m11*right.m13; + result.m10 = left.m8*right.m2 + left.m9*right.m6 + left.m10*right.m10 + left.m11*right.m14; + result.m11 = left.m8*right.m3 + left.m9*right.m7 + left.m10*right.m11 + left.m11*right.m15; + result.m12 = left.m12*right.m0 + left.m13*right.m4 + left.m14*right.m8 + left.m15*right.m12; + result.m13 = left.m12*right.m1 + left.m13*right.m5 + left.m14*right.m9 + left.m15*right.m13; + result.m14 = left.m12*right.m2 + left.m13*right.m6 + left.m14*right.m10 + left.m15*right.m14; + result.m15 = left.m12*right.m3 + left.m13*right.m7 + left.m14*right.m11 + left.m15*right.m15; + + return result; +} + +// Transposes provided matrix +static Matrix rlMatrixTranspose(Matrix mat) +{ + Matrix result = { 0 }; + + result.m0 = mat.m0; + result.m1 = mat.m4; + result.m2 = mat.m8; + result.m3 = mat.m12; + result.m4 = mat.m1; + result.m5 = mat.m5; + result.m6 = mat.m9; + result.m7 = mat.m13; + result.m8 = mat.m2; + result.m9 = mat.m6; + result.m10 = mat.m10; + result.m11 = mat.m14; + result.m12 = mat.m3; + result.m13 = mat.m7; + result.m14 = mat.m11; + result.m15 = mat.m15; + + return result; +} + +// Invert provided matrix +static Matrix rlMatrixInvert(Matrix mat) +{ + Matrix result = { 0 }; + + // Cache the matrix values (speed optimization) + float a00 = mat.m0, a01 = mat.m1, a02 = mat.m2, a03 = mat.m3; + float a10 = mat.m4, a11 = mat.m5, a12 = mat.m6, a13 = mat.m7; + float a20 = mat.m8, a21 = mat.m9, a22 = mat.m10, a23 = mat.m11; + float a30 = mat.m12, a31 = mat.m13, a32 = mat.m14, a33 = mat.m15; + + float b00 = a00*a11 - a01*a10; + float b01 = a00*a12 - a02*a10; + float b02 = a00*a13 - a03*a10; + float b03 = a01*a12 - a02*a11; + float b04 = a01*a13 - a03*a11; + float b05 = a02*a13 - a03*a12; + float b06 = a20*a31 - a21*a30; + float b07 = a20*a32 - a22*a30; + float b08 = a20*a33 - a23*a30; + float b09 = a21*a32 - a22*a31; + float b10 = a21*a33 - a23*a31; + float b11 = a22*a33 - a23*a32; + + // Calculate the invert determinant (inlined to avoid double-caching) + float invDet = 1.0f/(b00*b11 - b01*b10 + b02*b09 + b03*b08 - b04*b07 + b05*b06); + + result.m0 = (a11*b11 - a12*b10 + a13*b09)*invDet; + result.m1 = (-a01*b11 + a02*b10 - a03*b09)*invDet; + result.m2 = (a31*b05 - a32*b04 + a33*b03)*invDet; + result.m3 = (-a21*b05 + a22*b04 - a23*b03)*invDet; + result.m4 = (-a10*b11 + a12*b08 - a13*b07)*invDet; + result.m5 = (a00*b11 - a02*b08 + a03*b07)*invDet; + result.m6 = (-a30*b05 + a32*b02 - a33*b01)*invDet; + result.m7 = (a20*b05 - a22*b02 + a23*b01)*invDet; + result.m8 = (a10*b10 - a11*b08 + a13*b06)*invDet; + result.m9 = (-a00*b10 + a01*b08 - a03*b06)*invDet; + result.m10 = (a30*b04 - a31*b02 + a33*b00)*invDet; + result.m11 = (-a20*b04 + a21*b02 - a23*b00)*invDet; + result.m12 = (-a10*b09 + a11*b07 - a12*b06)*invDet; + result.m13 = (a00*b09 - a01*b07 + a02*b06)*invDet; + result.m14 = (-a30*b03 + a31*b01 - a32*b00)*invDet; + result.m15 = (a20*b03 - a21*b01 + a22*b00)*invDet; + + return result; +} + +#endif // RLGL_IMPLEMENTATION |
