From b83dec57b593a9c3ae3bec7e93d6144b116ddf7c Mon Sep 17 00:00:00 2001 From: Milan Nikolic Date: Sat, 27 Aug 2022 16:00:43 +0200 Subject: [PATCH] Update C sources --- raylib/config.h | 60 ++- raylib/raudio.c | 559 ++++++++++++------- raylib/raylib.h | 159 +++--- raylib/raymath.h | 532 +++++++++++++----- raylib/rcamera.h | 73 ++- raylib/rcore.c | 1275 +++++++++++++++++++++++++++----------------- raylib/rgestures.h | 10 +- raylib/rlgl.h | 204 ++++--- raylib/rmodels.c | 678 ++++++++++++++++++----- raylib/rshapes.c | 67 +-- raylib/rtext.c | 278 ++++++++-- raylib/rtextures.c | 240 ++++++--- raylib/utils.c | 48 +- raylib/utils.h | 6 +- 14 files changed, 2900 insertions(+), 1289 deletions(-) diff --git a/raylib/config.h b/raylib/config.h index 882e698..97f4990 100644 --- a/raylib/config.h +++ b/raylib/config.h @@ -6,7 +6,7 @@ * * LICENSE: zlib/libpng * -* Copyright (c) 2018-2021 Ahmad Fatoum & Ramon Santamaria (@raysan5) +* Copyright (c) 2018-2022 Ahmad Fatoum & 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. @@ -26,7 +26,17 @@ **********************************************************************************************/ //------------------------------------------------------------------------------------ -// Module: core - Configuration Flags +// Module selection - Some modules could be avoided +// Mandatory modules: rcore, rlgl, utils +//------------------------------------------------------------------------------------ +#define SUPPORT_MODULE_RSHAPES 1 +#define SUPPORT_MODULE_RTEXTURES 1 +#define SUPPORT_MODULE_RTEXT 1 // WARNING: It requires SUPPORT_MODULE_RTEXTURES to load sprite font textures +#define SUPPORT_MODULE_RMODELS 1 +#define SUPPORT_MODULE_RAUDIO 1 + +//------------------------------------------------------------------------------------ +// Module: rcore - Configuration Flags //------------------------------------------------------------------------------------ // Camera module is included (rcamera.h) and multiple predefined cameras are available: free, 1st/3rd person, orbital #define SUPPORT_CAMERA_SYSTEM 1 @@ -36,8 +46,6 @@ #define SUPPORT_MOUSE_GESTURES 1 // Reconfigure standard input to receive key inputs, works with SSH connection. #define SUPPORT_SSH_KEYBOARD_RPI 1 -// Draw a mouse pointer on screen -//#define SUPPORT_MOUSE_CURSOR_POINT 1 // Setting a higher resolution can improve the accuracy of time-out intervals in wait functions. // However, it can also reduce overall system performance, because the thread scheduler switches tasks more often. #define SUPPORT_WINMM_HIGHRES_TIMER 1 @@ -53,8 +61,6 @@ #define SUPPORT_GIF_RECORDING 1 // Support CompressData() and DecompressData() functions #define SUPPORT_COMPRESSION_API 1 -// Support saving binary data automatically to a generated storage.data file. This file is managed internally. -#define SUPPORT_DATA_STORAGE 1 // Support automatic generated events, loading and recording of those events when required //#define SUPPORT_EVENTS_AUTOMATION 1 // Support custom frame control, only for advance users @@ -62,21 +68,19 @@ // Enabling this flag allows manual control of the frame processes, use at your own risk //#define SUPPORT_CUSTOM_FRAME_CONTROL 1 -// core: Configuration values +// rcore: Configuration values //------------------------------------------------------------------------------------ -#if defined(__linux__) - #define MAX_FILEPATH_LENGTH 4096 // Maximum length for filepaths (Linux PATH_MAX default value) -#else - #define MAX_FILEPATH_LENGTH 512 // Maximum length supported for filepaths -#endif +#define MAX_FILEPATH_CAPACITY 8192 // Maximum file paths capacity +#define MAX_FILEPATH_LENGTH 4096 // Maximum length for filepaths (Linux PATH_MAX default value) -#define MAX_GAMEPADS 4 // Max number of gamepads supported -#define MAX_GAMEPAD_AXIS 8 // Max number of axis supported (per gamepad) -#define MAX_GAMEPAD_BUTTONS 32 // Max bumber of buttons supported (per gamepad) +#define MAX_KEYBOARD_KEYS 512 // Maximum number of keyboard keys supported +#define MAX_MOUSE_BUTTONS 8 // Maximum number of mouse buttons supported +#define MAX_GAMEPADS 4 // Maximum number of gamepads supported +#define MAX_GAMEPAD_AXIS 8 // Maximum number of axis supported (per gamepad) +#define MAX_GAMEPAD_BUTTONS 32 // Maximum number of buttons supported (per gamepad) #define MAX_TOUCH_POINTS 8 // Maximum number of touch points supported -#define MAX_KEY_PRESSED_QUEUE 16 // Max number of characters in the key input queue - -#define STORAGE_DATA_FILE "storage.data" // Automatic storage filename +#define MAX_KEY_PRESSED_QUEUE 16 // Maximum number of keys in the key input queue +#define MAX_CHAR_PRESSED_QUEUE 16 // Maximum number of characters in the char input queue #define MAX_DECOMPRESSION_SIZE 64 // Max size allocated for decompression in MB @@ -124,7 +128,7 @@ //------------------------------------------------------------------------------------ -// Module: shapes - Configuration Flags +// Module: rshapes - Configuration Flags //------------------------------------------------------------------------------------ // Use QUADS instead of TRIANGLES for drawing when possible // Some lines-based shapes could still use lines @@ -132,7 +136,7 @@ //------------------------------------------------------------------------------------ -// Module: textures - Configuration Flags +// Module: rtextures - Configuration Flags //------------------------------------------------------------------------------------ // Selecte desired fileformats to be supported for image data loading #define SUPPORT_FILEFORMAT_PNG 1 @@ -140,6 +144,7 @@ //#define SUPPORT_FILEFORMAT_TGA 1 #define SUPPORT_FILEFORMAT_JPG 1 #define SUPPORT_FILEFORMAT_GIF 1 +#define SUPPORT_FILEFORMAT_QOI 1 //#define SUPPORT_FILEFORMAT_PSD 1 #define SUPPORT_FILEFORMAT_DDS 1 #define SUPPORT_FILEFORMAT_HDR 1 @@ -148,7 +153,7 @@ //#define SUPPORT_FILEFORMAT_PKM 1 //#define SUPPORT_FILEFORMAT_PVR 1 -// Support image export functionality (.png, .bmp, .tga, .jpg) +// Support image export functionality (.png, .bmp, .tga, .jpg, .qoi) #define SUPPORT_IMAGE_EXPORT 1 // Support procedural image generation functionality (gradient, spot, perlin-noise, cellular) #define SUPPORT_IMAGE_GENERATION 1 @@ -158,7 +163,7 @@ //------------------------------------------------------------------------------------ -// Module: text - Configuration Flags +// Module: rtext - Configuration Flags //------------------------------------------------------------------------------------ // Default font is loaded on window initialization to be available for the user to render simple text // NOTE: If enabled, uses external module functions to load default raylib font @@ -171,7 +176,7 @@ // If not defined, still some functions are supported: TextLength(), TextFormat() #define SUPPORT_TEXT_MANIPULATION 1 -// text: Configuration values +// rtext: Configuration values //------------------------------------------------------------------------------------ #define MAX_TEXT_BUFFER_LENGTH 1024 // Size of internal static buffers used on some functions: // TextFormat(), TextSubtext(), TextToUpper(), TextToLower(), TextToPascal(), TextSplit() @@ -179,7 +184,7 @@ //------------------------------------------------------------------------------------ -// Module: models - Configuration Flags +// Module: rmodels - Configuration Flags //------------------------------------------------------------------------------------ // Selected desired model fileformats to be supported for loading #define SUPPORT_FILEFORMAT_OBJ 1 @@ -187,17 +192,18 @@ #define SUPPORT_FILEFORMAT_IQM 1 #define SUPPORT_FILEFORMAT_GLTF 1 #define SUPPORT_FILEFORMAT_VOX 1 +#define SUPPORT_FILEFORMAT_M3D 1 // Support procedural mesh generation functions, uses external par_shapes.h library // NOTE: Some generated meshes DO NOT include generated texture coordinates #define SUPPORT_MESH_GENERATION 1 -// models: Configuration values +// rmodels: Configuration values //------------------------------------------------------------------------------------ #define MAX_MATERIAL_MAPS 12 // Maximum number of shader maps supported #define MAX_MESH_VERTEX_BUFFERS 7 // Maximum vertex buffers (VBO) per mesh //------------------------------------------------------------------------------------ -// Module: audio - Configuration Flags +// Module: raudio - Configuration Flags //------------------------------------------------------------------------------------ // Desired audio fileformats to be supported for loading #define SUPPORT_FILEFORMAT_WAV 1 @@ -207,7 +213,7 @@ #define SUPPORT_FILEFORMAT_MP3 1 //#define SUPPORT_FILEFORMAT_FLAC 1 -// audio: Configuration values +// raudio: Configuration values //------------------------------------------------------------------------------------ #define AUDIO_DEVICE_FORMAT ma_format_f32 // Device output format (miniaudio: float-32bit) #define AUDIO_DEVICE_CHANNELS 2 // Device output channels: stereo diff --git a/raylib/raudio.c b/raylib/raudio.c index 6e2f685..e92952f 100644 --- a/raylib/raudio.c +++ b/raylib/raudio.c @@ -1,8 +1,9 @@ +//go:build !noaudio // +build !noaudio /********************************************************************************************** * -* raudio v1.0 - A simple and easy-to-use audio library based on miniaudio +* raudio v1.1 - A simple and easy-to-use audio library based on miniaudio * * FEATURES: * - Manage audio device (init/close) @@ -14,6 +15,9 @@ * * CONFIGURATION: * +* #define SUPPORT_MODULE_RAUDIO +* raudio module is included in the build +* * #define RAUDIO_STANDALONE * Define to use the module as standalone library (independently of raylib). * Required types and functions are defined in the same module. @@ -49,7 +53,7 @@ * * LICENSE: zlib/libpng * -* Copyright (c) 2013-2021 Ramon Santamaria (@raysan5) +* Copyright (c) 2013-2022 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. @@ -70,9 +74,9 @@ #if defined(RAUDIO_STANDALONE) #include "raudio.h" - #include // Required for: va_list, va_start(), vfprintf(), va_end() #else #include "raylib.h" // Declares module functions + // Check if config flags have been externally provided on compilation line #if !defined(EXTERNAL_CONFIG_FLAGS) #include "config.h" // Defines module configuration flags @@ -80,6 +84,8 @@ #include "utils.h" // Required for: fopen() Android mapping #endif +#if defined(SUPPORT_MODULE_RAUDIO) + #if defined(_WIN32) // To avoid conflicting windows.h symbols with raylib, some flags are defined // WARNING: Those flags avoid inclusion of some Win32 headers that could be required @@ -171,26 +177,25 @@ typedef struct tagBITMAPINFOHEADER { #include // Required for: malloc(), free() #include // Required for: FILE, fopen(), fclose(), fread() +#include // Required for: strcmp() [Used in IsFileExtension(), LoadWaveFromMemory(), LoadMusicStreamFromMemory()] #if defined(RAUDIO_STANDALONE) - #include // Required for: strcmp() [Used in IsFileExtension()] - #ifndef TRACELOG - #define TRACELOG(level, ...) (void)0 + #define TRACELOG(level, ...) printf(__VA_ARGS__) #endif // Allow custom memory allocators #ifndef RL_MALLOC - #define RL_MALLOC(sz) malloc(sz) + #define RL_MALLOC(sz) malloc(sz) #endif #ifndef RL_CALLOC - #define RL_CALLOC(n,sz) calloc(n,sz) + #define RL_CALLOC(n,sz) calloc(n,sz) #endif #ifndef RL_REALLOC - #define RL_REALLOC(ptr,sz) realloc(ptr,sz) + #define RL_REALLOC(ptr,sz) realloc(ptr,sz) #endif #ifndef RL_FREE - #define RL_FREE(ptr) free(ptr) + #define RL_FREE(ptr) free(ptr) #endif #endif @@ -245,10 +250,6 @@ typedef struct tagBITMAPINFOHEADER { #include "external/dr_flac.h" // FLAC loading functions #endif -#if defined(_MSC_VER) - #undef bool -#endif - //---------------------------------------------------------------------------------- // Defines and Macros //---------------------------------------------------------------------------------- @@ -265,10 +266,6 @@ typedef struct tagBITMAPINFOHEADER { #ifndef MAX_AUDIO_BUFFER_POOL_CHANNELS #define MAX_AUDIO_BUFFER_POOL_CHANNELS 16 // Audio pool channels #endif -#ifndef DEFAULT_AUDIO_BUFFER_SIZE - #define DEFAULT_AUDIO_BUFFER_SIZE 4096 // Default audio buffer size -#endif - //---------------------------------------------------------------------------------- // Types and Structures Definition @@ -309,16 +306,20 @@ typedef enum { AUDIO_BUFFER_USAGE_STREAM } AudioBufferUsage; -// Audio buffer structure +// Audio buffer struct struct rAudioBuffer { ma_data_converter converter; // Audio data converter + AudioCallback callback; // Audio buffer callback for buffer filling on audio threads + rAudioProcessor *processor; // Audio processor + float volume; // Audio buffer volume float pitch; // Audio buffer pitch + float pan; // Audio buffer pan (0.0f to 1.0f) bool playing; // Audio buffer state: AUDIO_PLAYING bool paused; // Audio buffer state: AUDIO_PAUSED - bool looping; // Audio buffer looping, always true for AudioStreams + bool looping; // Audio buffer looping, default to true for AudioStreams int usage; // Audio buffer usage mode: STATIC or STREAM bool isSubBufferProcessed[2]; // SubBuffer processed (virtual double buffer) @@ -332,6 +333,14 @@ struct rAudioBuffer { rAudioBuffer *prev; // Previous audio buffer on the list }; +// Audio processor struct +// NOTE: Useful to apply effects to an AudioBuffer +struct rAudioProcessor { + AudioCallback process; // Processor callback function + rAudioProcessor *next; // Next audio processor on the list + rAudioProcessor *prev; // Previous audio processor on the list +}; + #define AudioBuffer rAudioBuffer // HACK: To avoid CoreAudio (macOS) symbol collision // Audio data context @@ -341,6 +350,8 @@ typedef struct AudioData { ma_device device; // miniaudio device ma_mutex lock; // miniaudio mutex lock bool isReady; // Check if audio device is ready + size_t pcmBufferSize; // Pre-allocated buffer size + void *pcmBuffer; // Pre-allocated buffer to read audio data from file/memory } System; struct { AudioBuffer *first; // Pointer to first AudioBuffer in the list @@ -369,15 +380,13 @@ static AudioData AUDIO = { // Global AUDIO context //---------------------------------------------------------------------------------- // Module specific Functions Declaration //---------------------------------------------------------------------------------- -static void OnLog(ma_context *pContext, ma_device *pDevice, ma_uint32 logLevel, const char *message); +static void OnLog(void *pUserData, ma_uint32 level, const char *pMessage); static void OnSendAudioDataToDevice(ma_device *pDevice, void *pFramesOut, const void *pFramesInput, ma_uint32 frameCount); -static void MixAudioFrames(float *framesOut, const float *framesIn, ma_uint32 frameCount, float localVolume); +static void MixAudioFrames(float *framesOut, const float *framesIn, ma_uint32 frameCount, AudioBuffer *buffer); #if defined(RAUDIO_STANDALONE) static bool IsFileExtension(const char *fileName, const char *ext); // Check file extension static const char *GetFileExtension(const char *fileName); // Get pointer to extension for a filename string (includes the dot: .png) -static bool TextIsEqual(const char *text1, const char *text2); // Check if two text string are equal -static const char *TextToLower(const char *text); // Get lower case version of provided string static unsigned char *LoadFileData(const char *fileName, unsigned int *bytesRead); // Load file data as byte array (read) static bool SaveFileData(const char *fileName, void *data, unsigned int bytesToWrite); // Save data to file from byte array (write) @@ -398,6 +407,7 @@ void PauseAudioBuffer(AudioBuffer *buffer); void ResumeAudioBuffer(AudioBuffer *buffer); void SetAudioBufferVolume(AudioBuffer *buffer, float volume); void SetAudioBufferPitch(AudioBuffer *buffer, float pitch); +void SetAudioBufferPan(AudioBuffer *buffer, float pan); void TrackAudioBuffer(AudioBuffer *buffer); void UntrackAudioBuffer(AudioBuffer *buffer); @@ -409,7 +419,7 @@ void InitAudioDevice(void) { // Init audio context ma_context_config ctxConfig = ma_context_config_init(); - ctxConfig.logCallback = OnLog; + ma_log_callback_init(OnLog, NULL); ma_result result = ma_context_init(NULL, 0, &ctxConfig, &AUDIO.System.context); if (result != MA_SUCCESS) @@ -463,8 +473,7 @@ void InitAudioDevice(void) // Init dummy audio buffers pool for multichannel sound playing for (int i = 0; i < MAX_AUDIO_BUFFER_POOL_CHANNELS; i++) { - // WARNING: An empty audio buffer is created (data = 0) - // AudioBuffer data just points to loaded sound data + // WARNING: An empty audio buffer is created (data = 0) and added to list, AudioBuffer data is filled on PlaySoundMulti() AUDIO.MultiChannel.pool[i] = LoadAudioBuffer(AUDIO_DEVICE_FORMAT, AUDIO_DEVICE_CHANNELS, AUDIO.System.device.sampleRate, 0, AUDIO_BUFFER_USAGE_STATIC); } @@ -490,7 +499,7 @@ void CloseAudioDevice(void) //UnloadAudioBuffer(AUDIO.MultiChannel.pool[i]); if (AUDIO.MultiChannel.pool[i] != NULL) { - ma_data_converter_uninit(&AUDIO.MultiChannel.pool[i]->converter); + ma_data_converter_uninit(&AUDIO.MultiChannel.pool[i]->converter, NULL); UntrackAudioBuffer(AUDIO.MultiChannel.pool[i]); //RL_FREE(buffer->data); // Already unloaded by UnloadSound() RL_FREE(AUDIO.MultiChannel.pool[i]); @@ -502,6 +511,7 @@ void CloseAudioDevice(void) ma_context_uninit(&AUDIO.System.context); AUDIO.System.isReady = false; + RL_FREE(AUDIO.System.pcmBuffer); TRACELOG(LOG_INFO, "AUDIO: Device closed successfully"); } @@ -539,9 +549,9 @@ AudioBuffer *LoadAudioBuffer(ma_format format, ma_uint32 channels, ma_uint32 sam // Audio data runs through a format converter ma_data_converter_config converterConfig = ma_data_converter_config_init(format, AUDIO_DEVICE_FORMAT, channels, AUDIO_DEVICE_CHANNELS, sampleRate, AUDIO.System.device.sampleRate); - converterConfig.resampling.allowDynamicSampleRate = true; // Pitch shifting + converterConfig.allowDynamicSampleRate = true; - ma_result result = ma_data_converter_init(&converterConfig, &audioBuffer->converter); + ma_result result = ma_data_converter_init(&converterConfig, NULL, &audioBuffer->converter); if (result != MA_SUCCESS) { @@ -553,9 +563,15 @@ AudioBuffer *LoadAudioBuffer(ma_format format, ma_uint32 channels, ma_uint32 sam // Init audio buffer values audioBuffer->volume = 1.0f; audioBuffer->pitch = 1.0f; + audioBuffer->pan = 0.5f; + + audioBuffer->callback = NULL; + audioBuffer->processor = NULL; + audioBuffer->playing = false; audioBuffer->paused = false; audioBuffer->looping = false; + audioBuffer->usage = usage; audioBuffer->frameCursorPos = 0; audioBuffer->sizeInFrames = sizeInFrames; @@ -576,7 +592,7 @@ void UnloadAudioBuffer(AudioBuffer *buffer) { if (buffer != NULL) { - ma_data_converter_uninit(&buffer->converter); + ma_data_converter_uninit(&buffer->converter, NULL); UntrackAudioBuffer(buffer); RL_FREE(buffer->data); RL_FREE(buffer); @@ -650,13 +666,22 @@ void SetAudioBufferPitch(AudioBuffer *buffer, float pitch) // Note that this changes the duration of the sound: // - higher pitches will make the sound faster // - lower pitches make it slower - ma_uint32 outputSampleRate = (ma_uint32)((float)buffer->converter.config.sampleRateOut/pitch); - ma_data_converter_set_rate(&buffer->converter, buffer->converter.config.sampleRateIn, outputSampleRate); + ma_uint32 outputSampleRate = (ma_uint32)((float)buffer->converter.sampleRateOut/pitch); + ma_data_converter_set_rate(&buffer->converter, buffer->converter.sampleRateIn, outputSampleRate); buffer->pitch = pitch; } } +// Set pan for an audio buffer +void SetAudioBufferPan(AudioBuffer *buffer, float pan) +{ + if (pan < 0.0f) pan = 0.0f; + else if (pan > 1.0f) pan = 1.0f; + + if (buffer != NULL) buffer->pan = pan; +} + // Track audio buffer to linked list next position void TrackAudioBuffer(AudioBuffer *buffer) { @@ -713,16 +738,14 @@ Wave LoadWave(const char *fileName) } // Load wave from memory buffer, fileType refers to extension: i.e. ".wav" +// WARNING: File extension must be provided in lower-case Wave LoadWaveFromMemory(const char *fileType, const unsigned char *fileData, int dataSize) { Wave wave = { 0 }; - char fileExtLower[16] = { 0 }; - strcpy(fileExtLower, TextToLower(fileType)); - if (false) { } #if defined(SUPPORT_FILEFORMAT_WAV) - else if (TextIsEqual(fileExtLower, ".wav")) + else if (strcmp(fileType, ".wav") == 0) { drwav wav = { 0 }; bool success = drwav_init_memory(&wav, fileData, dataSize, NULL); @@ -744,7 +767,7 @@ Wave LoadWaveFromMemory(const char *fileType, const unsigned char *fileData, int } #endif #if defined(SUPPORT_FILEFORMAT_OGG) - else if (TextIsEqual(fileExtLower, ".ogg")) + else if (strcmp(fileType, ".ogg") == 0) { stb_vorbis *oggData = stb_vorbis_open_memory((unsigned char *)fileData, dataSize, NULL, NULL); @@ -766,7 +789,7 @@ Wave LoadWaveFromMemory(const char *fileType, const unsigned char *fileData, int } #endif #if defined(SUPPORT_FILEFORMAT_FLAC) - else if (TextIsEqual(fileExtLower, ".flac")) + else if (strcmp(fileType, ".flac") == 0) { unsigned long long int totalFrameCount = 0; @@ -779,7 +802,7 @@ Wave LoadWaveFromMemory(const char *fileType, const unsigned char *fileData, int } #endif #if defined(SUPPORT_FILEFORMAT_MP3) - else if (TextIsEqual(fileExtLower, ".mp3")) + else if (strcmp(fileType, ".mp3") == 0) { drmp3_config config = { 0 }; unsigned long long int totalFrameCount = 0; @@ -864,17 +887,15 @@ Sound LoadSoundFromWave(Wave wave) // Unload wave data void UnloadWave(Wave wave) { - if (wave.data != NULL) RL_FREE(wave.data); - - TRACELOG(LOG_INFO, "WAVE: Unloaded wave data from RAM"); + RL_FREE(wave.data); + //TRACELOG(LOG_INFO, "WAVE: Unloaded wave data from RAM"); } // Unload sound void UnloadSound(Sound sound) { UnloadAudioBuffer(sound.stream.buffer); - - TRACELOG(LOG_INFO, "WAVE: Unloaded sound data from RAM"); + //TRACELOG(LOG_INFO, "SOUND: Unloaded sound data from RAM"); } // Update sound buffer with new data @@ -885,7 +906,7 @@ void UpdateSound(Sound sound, const void *data, int sampleCount) StopAudioBuffer(sound.stream.buffer); // TODO: May want to lock/unlock this since this data buffer is read at mixing time - memcpy(sound.stream.buffer->data, data, sampleCount*ma_get_bytes_per_frame(sound.stream.buffer->converter.config.formatIn, sound.stream.buffer->converter.config.channelsIn)); + memcpy(sound.stream.buffer->data, data, sampleCount*ma_get_bytes_per_frame(sound.stream.buffer->converter.formatIn, sound.stream.buffer->converter.channelsIn)); } } @@ -901,7 +922,8 @@ bool ExportWave(Wave wave, const char *fileName) drwav wav = { 0 }; drwav_data_format format = { 0 }; format.container = drwav_container_riff; - format.format = DR_WAVE_FORMAT_PCM; + if (wave.sampleSize == 32) format.format = DR_WAVE_FORMAT_IEEE_FLOAT; + else format.format = DR_WAVE_FORMAT_PCM; format.channels = wave.channels; format.sampleRate = wave.sampleRate; format.bitsPerSample = wave.sampleSize; @@ -948,42 +970,50 @@ bool ExportWaveAsCode(Wave wave, const char *fileName) int byteCount = 0; byteCount += sprintf(txtData + byteCount, "\n//////////////////////////////////////////////////////////////////////////////////\n"); byteCount += sprintf(txtData + byteCount, "// //\n"); - byteCount += sprintf(txtData + byteCount, "// WaveAsCode exporter v1.0 - Wave data exported as an array of bytes //\n"); + byteCount += sprintf(txtData + byteCount, "// WaveAsCode exporter v1.1 - Wave data exported as an array of bytes //\n"); byteCount += sprintf(txtData + byteCount, "// //\n"); byteCount += sprintf(txtData + byteCount, "// more info and bugs-report: github.com/raysan5/raylib //\n"); byteCount += sprintf(txtData + byteCount, "// feedback and support: ray[at]raylib.com //\n"); byteCount += sprintf(txtData + byteCount, "// //\n"); - byteCount += sprintf(txtData + byteCount, "// Copyright (c) 2018-2021 Ramon Santamaria (@raysan5) //\n"); + byteCount += sprintf(txtData + byteCount, "// Copyright (c) 2018-2022 Ramon Santamaria (@raysan5) //\n"); byteCount += sprintf(txtData + byteCount, "// //\n"); byteCount += sprintf(txtData + byteCount, "//////////////////////////////////////////////////////////////////////////////////\n\n"); - char varFileName[256] = { 0 }; -#if !defined(RAUDIO_STANDALONE) - // Get file name from path and convert variable name to uppercase - strcpy(varFileName, GetFileNameWithoutExt(fileName)); - for (int i = 0; varFileName[i] != '\0'; i++) if (varFileName[i] >= 'a' && varFileName[i] <= 'z') { varFileName[i] = varFileName[i] - 32; } -#else - strcpy(varFileName, fileName); -#endif + char fileNameLower[256] = { 0 }; + char fileNameUpper[256] = { 0 }; + for (int i = 0; fileName[i] != '.'; i++) { fileNameLower[i] = fileName[i]; } // Get filename without extension + for (int i = 0; fileNameLower[i] != '\0'; i++) if (fileNameLower[i] >= 'a' && fileNameLower[i] <= 'z') { fileNameUpper[i] = fileNameLower[i] - 32; } byteCount += sprintf(txtData + byteCount, "// Wave data information\n"); - byteCount += sprintf(txtData + byteCount, "#define %s_FRAME_COUNT %u\n", varFileName, wave.frameCount); - byteCount += sprintf(txtData + byteCount, "#define %s_FRAME_COUNT %u\n", varFileName, wave.frameCount); - byteCount += sprintf(txtData + byteCount, "#define %s_SAMPLE_RATE %u\n", varFileName, wave.sampleRate); - byteCount += sprintf(txtData + byteCount, "#define %s_SAMPLE_SIZE %u\n", varFileName, wave.sampleSize); - byteCount += sprintf(txtData + byteCount, "#define %s_CHANNELS %u\n\n", varFileName, wave.channels); + byteCount += sprintf(txtData + byteCount, "#define %s_FRAME_COUNT %u\n", fileNameUpper, wave.frameCount); + byteCount += sprintf(txtData + byteCount, "#define %s_SAMPLE_RATE %u\n", fileNameUpper, wave.sampleRate); + byteCount += sprintf(txtData + byteCount, "#define %s_SAMPLE_SIZE %u\n", fileNameUpper, wave.sampleSize); + byteCount += sprintf(txtData + byteCount, "#define %s_CHANNELS %u\n\n", fileNameUpper, wave.channels); - // Write byte data as hexadecimal text - // NOTE: Frame data exported is interlaced: Frame01[Sample-Channel01, Sample-Channel02, ...], Frame02[], Frame03[] - byteCount += sprintf(txtData + byteCount, "static unsigned char %s_DATA[%i] = { ", varFileName, waveDataSize); - for (int i = 0; i < waveDataSize - 1; i++) byteCount += sprintf(txtData + byteCount, ((i%TEXT_BYTES_PER_LINE == 0)? "0x%x,\n" : "0x%x, "), ((unsigned char *)wave.data)[i]); - byteCount += sprintf(txtData + byteCount, "0x%x };\n", ((unsigned char *)wave.data)[waveDataSize - 1]); + // Write wave data as an array of values + // Wave data is exported as byte array for 8/16bit and float array for 32bit float data + // NOTE: Frame data exported is channel-interlaced: frame01[sampleChannel1, sampleChannel2, ...], frame02[], frame03[] + if (wave.sampleSize == 32) + { + byteCount += sprintf(txtData + byteCount, "static float %sData[%i] = {\n", fileNameLower, waveDataSize/4); + for (int i = 1; i < waveDataSize/4; i++) byteCount += sprintf(txtData + byteCount, ((i%TEXT_BYTES_PER_LINE == 0)? "%.4ff,\n " : "%.4ff, "), ((float *)wave.data)[i - 1]); + byteCount += sprintf(txtData + byteCount, "%.4ff };\n", ((float *)wave.data)[waveDataSize/4 - 1]); + } + else + { + byteCount += sprintf(txtData + byteCount, "static unsigned char %sData[%i] = { ", fileNameLower, waveDataSize); + for (int i = 1; i < waveDataSize; i++) byteCount += sprintf(txtData + byteCount, ((i%TEXT_BYTES_PER_LINE == 0)? "0x%x,\n " : "0x%x, "), ((unsigned char *)wave.data)[i - 1]); + byteCount += sprintf(txtData + byteCount, "0x%x };\n", ((unsigned char *)wave.data)[waveDataSize - 1]); + } // NOTE: Text data length exported is determined by '\0' (NULL) character success = SaveFileText(fileName, txtData); RL_FREE(txtData); + if (success != 0) TRACELOG(LOG_INFO, "FILEIO: [%s] Wave as code exported successfully", fileName); + else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to export wave as code", fileName); + return success; } @@ -1041,14 +1071,17 @@ void PlaySoundMulti(Sound sound) AUDIO.MultiChannel.channels[index] = AUDIO.MultiChannel.poolCounter; AUDIO.MultiChannel.poolCounter++; - AUDIO.MultiChannel.pool[index]->volume = sound.stream.buffer->volume; - AUDIO.MultiChannel.pool[index]->pitch = sound.stream.buffer->pitch; + SetAudioBufferVolume(AUDIO.MultiChannel.pool[index], sound.stream.buffer->volume); + SetAudioBufferPitch(AUDIO.MultiChannel.pool[index], sound.stream.buffer->pitch); + SetAudioBufferPan(AUDIO.MultiChannel.pool[index], sound.stream.buffer->pan); + AUDIO.MultiChannel.pool[index]->looping = sound.stream.buffer->looping; AUDIO.MultiChannel.pool[index]->usage = sound.stream.buffer->usage; AUDIO.MultiChannel.pool[index]->isSubBufferProcessed[0] = false; AUDIO.MultiChannel.pool[index]->isSubBufferProcessed[1] = false; AUDIO.MultiChannel.pool[index]->sizeInFrames = sound.stream.buffer->sizeInFrames; - AUDIO.MultiChannel.pool[index]->data = sound.stream.buffer->data; + + AUDIO.MultiChannel.pool[index]->data = sound.stream.buffer->data; // Fill dummy track with data for playing PlayAudioBuffer(AUDIO.MultiChannel.pool[index]); } @@ -1108,6 +1141,12 @@ void SetSoundPitch(Sound sound, float pitch) SetAudioBufferPitch(sound.stream.buffer, pitch); } +// Set pan for a sound +void SetSoundPan(Sound sound, float pan) +{ + SetAudioBufferPan(sound.stream.buffer, pan); +} + // Convert wave data to desired format void WaveFormat(Wave *wave, int sampleRate, int sampleSize, int channels) { @@ -1115,8 +1154,8 @@ void WaveFormat(Wave *wave, int sampleRate, int sampleSize, int channels) ma_format formatOut = ((sampleSize == 8)? ma_format_u8 : ((sampleSize == 16)? ma_format_s16 : ma_format_f32)); ma_uint32 frameCountIn = wave->frameCount; - ma_uint32 frameCount = (ma_uint32)ma_convert_frames(NULL, 0, formatOut, channels, sampleRate, NULL, frameCountIn, formatIn, wave->channels, wave->sampleRate); + if (frameCount == 0) { TRACELOG(LOG_WARNING, "WAVE: Failed to get frame count for format conversion"); @@ -1136,6 +1175,7 @@ void WaveFormat(Wave *wave, int sampleRate, int sampleSize, int channels) wave->sampleSize = sampleSize; wave->sampleRate = sampleRate; wave->channels = channels; + RL_FREE(wave->data); wave->data = data; } @@ -1165,8 +1205,7 @@ Wave WaveCopy(Wave wave) // NOTE: Security check in case of out-of-range void WaveCrop(Wave *wave, int initSample, int finalSample) { - if ((initSample >= 0) && (initSample < finalSample) && - (finalSample > 0) && ((unsigned int)finalSample < (wave->frameCount*wave->channels))) + if ((initSample >= 0) && (initSample < finalSample) && ((unsigned int)finalSample < (wave->frameCount*wave->channels))) { int sampleCount = finalSample - initSample; @@ -1379,18 +1418,16 @@ Music LoadMusicStream(const char *fileName) return music; } -// extension including period ".mod" -Music LoadMusicStreamFromMemory(const char *fileType, unsigned char *data, int dataSize) +// Load music stream from memory buffer, fileType refers to extension: i.e. ".wav" +// WARNING: File extension must be provided in lower-case +Music LoadMusicStreamFromMemory(const char *fileType, const unsigned char *data, int dataSize) { Music music = { 0 }; bool musicLoaded = false; - char fileExtLower[16] = { 0 }; - strcpy(fileExtLower, TextToLower(fileType)); - if (false) { } #if defined(SUPPORT_FILEFORMAT_WAV) - else if (TextIsEqual(fileExtLower, ".wav")) + else if (strcmp(fileType, ".wav") == 0) { drwav *ctxWav = RL_CALLOC(1, sizeof(drwav)); @@ -1412,7 +1449,7 @@ Music LoadMusicStreamFromMemory(const char *fileType, unsigned char *data, int d } #endif #if defined(SUPPORT_FILEFORMAT_FLAC) - else if (TextIsEqual(fileExtLower, ".flac")) + else if (strcmp(fileType, ".flac") == 0) { music.ctxType = MUSIC_AUDIO_FLAC; music.ctxData = drflac_open_memory((const void*)data, dataSize, NULL); @@ -1429,7 +1466,7 @@ Music LoadMusicStreamFromMemory(const char *fileType, unsigned char *data, int d } #endif #if defined(SUPPORT_FILEFORMAT_MP3) - else if (TextIsEqual(fileExtLower, ".mp3")) + else if (strcmp(fileType, ".mp3") == 0) { drmp3 *ctxMp3 = RL_CALLOC(1, sizeof(drmp3)); int success = drmp3_init_memory(ctxMp3, (const void*)data, dataSize, NULL); @@ -1447,7 +1484,7 @@ Music LoadMusicStreamFromMemory(const char *fileType, unsigned char *data, int d } #endif #if defined(SUPPORT_FILEFORMAT_OGG) - else if (TextIsEqual(fileExtLower, ".ogg")) + else if (strcmp(fileType, ".ogg") == 0) { // Open ogg audio stream music.ctxType = MUSIC_AUDIO_OGG; @@ -1469,7 +1506,7 @@ Music LoadMusicStreamFromMemory(const char *fileType, unsigned char *data, int d } #endif #if defined(SUPPORT_FILEFORMAT_XM) - else if (TextIsEqual(fileExtLower, ".xm")) + else if (strcmp(fileType, ".xm") == 0) { jar_xm_context_t *ctxXm = NULL; int result = jar_xm_create_context_safe(&ctxXm, (const char *)data, dataSize, AUDIO.System.device.sampleRate); @@ -1479,16 +1516,14 @@ Music LoadMusicStreamFromMemory(const char *fileType, unsigned char *data, int d jar_xm_set_max_loop_count(ctxXm, 0); // Set infinite number of loops unsigned int bits = 32; - if (AUDIO_DEVICE_FORMAT == ma_format_s16) - bits = 16; - else if (AUDIO_DEVICE_FORMAT == ma_format_u8) - bits = 8; + if (AUDIO_DEVICE_FORMAT == ma_format_s16) bits = 16; + else if (AUDIO_DEVICE_FORMAT == ma_format_u8) bits = 8; // NOTE: Only stereo is supported for XM music.stream = LoadAudioStream(AUDIO.System.device.sampleRate, bits, 2); music.frameCount = (unsigned int)jar_xm_get_remaining_samples(ctxXm); // NOTE: Always 2 channels (stereo) music.looping = true; // Looping enabled by default - jar_xm_reset(ctxXm); // make sure we start at the beginning of the song + jar_xm_reset(ctxXm); // make sure we start at the beginning of the song music.ctxData = ctxXm; musicLoaded = true; @@ -1496,7 +1531,7 @@ Music LoadMusicStreamFromMemory(const char *fileType, unsigned char *data, int d } #endif #if defined(SUPPORT_FILEFORMAT_MOD) - else if (TextIsEqual(fileExtLower, ".mod")) + else if (strcmp(fileType, ".mod") == 0) { jar_mod_context_t *ctxMod = (jar_mod_context_t *)RL_MALLOC(sizeof(jar_mod_context_t)); int result = 0; @@ -1509,7 +1544,7 @@ Music LoadMusicStreamFromMemory(const char *fileType, unsigned char *data, int d for (int i = 0; i < it; i++) newData[i] = data[i]; // Memory loaded version for jar_mod_load_file() - if (dataSize && dataSize < 32*1024*1024) + if (dataSize && (dataSize < 32*1024*1024)) { ctxMod->modfilesize = dataSize; ctxMod->modfile = newData; @@ -1589,7 +1624,7 @@ void UnloadMusicStream(Music music) else if (music.ctxType == MUSIC_AUDIO_FLAC) drflac_free((drflac *)music.ctxData, NULL); #endif #if defined(SUPPORT_FILEFORMAT_MP3) - else if (music.ctxType == MUSIC_AUDIO_MP3) { drmp3_uninit((drmp3 *)music.ctxData); RL_FREE(music.ctxData); } + else if (music.ctxType == MUSIC_AUDIO_MP3) { drmp3_uninit((drmp3 *)music.ctxData); RL_FREE(music.ctxData); } #endif #if defined(SUPPORT_FILEFORMAT_XM) else if (music.ctxType == MUSIC_MODULE_XM) jar_xm_free_context((jar_xm_context_t *)music.ctxData); @@ -1635,16 +1670,16 @@ void StopMusicStream(Music music) switch (music.ctxType) { #if defined(SUPPORT_FILEFORMAT_WAV) - case MUSIC_AUDIO_WAV: drwav_seek_to_pcm_frame((drwav *)music.ctxData, 0); break; + case MUSIC_AUDIO_WAV: drwav_seek_to_first_pcm_frame((drwav *)music.ctxData); break; #endif #if defined(SUPPORT_FILEFORMAT_OGG) case MUSIC_AUDIO_OGG: stb_vorbis_seek_start((stb_vorbis *)music.ctxData); break; #endif #if defined(SUPPORT_FILEFORMAT_FLAC) - case MUSIC_AUDIO_FLAC: drflac_seek_to_pcm_frame((drflac *)music.ctxData, 0); break; + case MUSIC_AUDIO_FLAC: drflac__seek_to_first_frame((drflac *)music.ctxData); break; #endif #if defined(SUPPORT_FILEFORMAT_MP3) - case MUSIC_AUDIO_MP3: drmp3_seek_to_pcm_frame((drmp3 *)music.ctxData, 0); break; + case MUSIC_AUDIO_MP3: drmp3_seek_to_start_of_stream((drmp3 *)music.ctxData); break; #endif #if defined(SUPPORT_FILEFORMAT_XM) case MUSIC_MODULE_XM: jar_xm_reset((jar_xm_context_t *)music.ctxData); break; @@ -1689,64 +1724,107 @@ void UpdateMusicStream(Music music) { if (music.stream.buffer == NULL) return; - bool streamEnding = false; unsigned int subBufferSizeInFrames = music.stream.buffer->sizeInFrames/2; - // NOTE: Using dynamic allocation because it could require more than 16KB - void *pcm = RL_CALLOC(subBufferSizeInFrames*music.stream.channels*music.stream.sampleSize/8, 1); - - int frameCountToStream = 0; // Total size of data in frames to be streamed - - // TODO: Get the framesLeft using framesProcessed... but first, get total frames processed correctly... - //ma_uint32 frameSizeInBytes = ma_get_bytes_per_sample(music.stream.buffer->dsp.formatConverterIn.config.formatIn)*music.stream.buffer->dsp.formatConverterIn.config.channels; - unsigned int framesLeft = music.frameCount - music.stream.buffer->framesProcessed; - - while (IsAudioStreamProcessed(music.stream)) + // On first call of this function we lazily pre-allocated a temp buffer to read audio files/memory data in + int frameSize = music.stream.channels*music.stream.sampleSize/8; + unsigned int pcmSize = subBufferSizeInFrames*frameSize; + if (AUDIO.System.pcmBufferSize < pcmSize) { - if (framesLeft >= subBufferSizeInFrames) frameCountToStream = subBufferSizeInFrames; - else frameCountToStream = framesLeft; + RL_FREE(AUDIO.System.pcmBuffer); + AUDIO.System.pcmBuffer = RL_CALLOC(1, pcmSize); + AUDIO.System.pcmBufferSize = pcmSize; + } + // Check both sub-buffers to check if they require refilling + for (int i = 0; i < 2; i++) + { + if ((music.stream.buffer != NULL) && !music.stream.buffer->isSubBufferProcessed[i]) continue; // No refilling required, move to next sub-buffer + + unsigned int framesLeft = music.frameCount - music.stream.buffer->framesProcessed; // Frames left to be processed + unsigned int framesToStream = 0; // Total frames to be streamed + if ((framesLeft >= subBufferSizeInFrames) || music.looping) framesToStream = subBufferSizeInFrames; + else framesToStream = framesLeft; + + int frameCountStillNeeded = framesToStream; + int frameCountRedTotal = 0; switch (music.ctxType) { #if defined(SUPPORT_FILEFORMAT_WAV) case MUSIC_AUDIO_WAV: { - // NOTE: Returns the number of samples to process (not required) - if (music.stream.sampleSize == 16) drwav_read_pcm_frames_s16((drwav *)music.ctxData, frameCountToStream, (short *)pcm); - else if (music.stream.sampleSize == 32) drwav_read_pcm_frames_f32((drwav *)music.ctxData, frameCountToStream, (float *)pcm); - + if (music.stream.sampleSize == 16) + { + while (true) + { + int frameCountRed = drwav_read_pcm_frames_s16((drwav *)music.ctxData, frameCountStillNeeded, (short *)((char *)AUDIO.System.pcmBuffer + frameCountRedTotal*frameSize)); + frameCountRedTotal += frameCountRed; + frameCountStillNeeded -= frameCountRed; + if (frameCountStillNeeded == 0) break; + else drwav_seek_to_first_pcm_frame((drwav *)music.ctxData); + } + } + else if (music.stream.sampleSize == 32) + { + while (true) + { + int frameCountRed = drwav_read_pcm_frames_f32((drwav *)music.ctxData, frameCountStillNeeded, (float *)((char *)AUDIO.System.pcmBuffer + frameCountRedTotal*frameSize)); + frameCountRedTotal += frameCountRed; + frameCountStillNeeded -= frameCountRed; + if (frameCountStillNeeded == 0) break; + else drwav_seek_to_first_pcm_frame((drwav *)music.ctxData); + } + } } break; #endif #if defined(SUPPORT_FILEFORMAT_OGG) case MUSIC_AUDIO_OGG: { - // NOTE: Returns the number of samples to process (be careful! we ask for number of shorts!) - stb_vorbis_get_samples_short_interleaved((stb_vorbis *)music.ctxData, music.stream.channels, (short *)pcm, frameCountToStream*music.stream.channels); - + while (true) + { + int frameCountRed = stb_vorbis_get_samples_short_interleaved((stb_vorbis *)music.ctxData, music.stream.channels, (short *)((char *)AUDIO.System.pcmBuffer + frameCountRedTotal*frameSize), frameCountStillNeeded*music.stream.channels); + frameCountRedTotal += frameCountRed; + frameCountStillNeeded -= frameCountRed; + if (frameCountStillNeeded == 0) break; + else stb_vorbis_seek_start((stb_vorbis *)music.ctxData); + } } break; #endif #if defined(SUPPORT_FILEFORMAT_FLAC) case MUSIC_AUDIO_FLAC: { - // NOTE: Returns the number of samples to process (not required) - drflac_read_pcm_frames_s16((drflac *)music.ctxData, frameCountToStream*music.stream.channels, (short *)pcm); - + while (true) + { + int frameCountRed = drflac_read_pcm_frames_s16((drflac *)music.ctxData, frameCountStillNeeded, (short *)((char *)AUDIO.System.pcmBuffer + frameCountRedTotal*frameSize)); + frameCountRedTotal += frameCountRed; + frameCountStillNeeded -= frameCountRed; + if (frameCountStillNeeded == 0) break; + else drflac__seek_to_first_frame((drflac *)music.ctxData); + } } break; #endif #if defined(SUPPORT_FILEFORMAT_MP3) case MUSIC_AUDIO_MP3: { - drmp3_read_pcm_frames_f32((drmp3 *)music.ctxData, frameCountToStream, (float *)pcm); - + while (true) + { + int frameCountRed = drmp3_read_pcm_frames_f32((drmp3 *)music.ctxData, frameCountStillNeeded, (float *)((char *)AUDIO.System.pcmBuffer + frameCountRedTotal*frameSize)); + frameCountRedTotal += frameCountRed; + frameCountStillNeeded -= frameCountRed; + if (frameCountStillNeeded == 0) break; + else drmp3_seek_to_start_of_stream((drmp3 *)music.ctxData); + } } break; #endif #if defined(SUPPORT_FILEFORMAT_XM) case MUSIC_MODULE_XM: { // NOTE: Internally we consider 2 channels generation, so sampleCount/2 - if (AUDIO_DEVICE_FORMAT == ma_format_f32) jar_xm_generate_samples((jar_xm_context_t *)music.ctxData, (float *)pcm, frameCountToStream); - else if (AUDIO_DEVICE_FORMAT == ma_format_s16) jar_xm_generate_samples_16bit((jar_xm_context_t *)music.ctxData, (short *)pcm, frameCountToStream); - else if (AUDIO_DEVICE_FORMAT == ma_format_u8) jar_xm_generate_samples_8bit((jar_xm_context_t *)music.ctxData, (char *)pcm, frameCountToStream); + if (AUDIO_DEVICE_FORMAT == ma_format_f32) jar_xm_generate_samples((jar_xm_context_t *)music.ctxData, (float *)AUDIO.System.pcmBuffer, framesToStream); + else if (AUDIO_DEVICE_FORMAT == ma_format_s16) jar_xm_generate_samples_16bit((jar_xm_context_t *)music.ctxData, (short *)AUDIO.System.pcmBuffer, framesToStream); + else if (AUDIO_DEVICE_FORMAT == ma_format_u8) jar_xm_generate_samples_8bit((jar_xm_context_t *)music.ctxData, (char *)AUDIO.System.pcmBuffer, framesToStream); + + //jar_xm_reset((jar_xm_context_t *)music.ctxData); } break; #endif @@ -1754,38 +1832,33 @@ void UpdateMusicStream(Music music) case MUSIC_MODULE_MOD: { // NOTE: 3rd parameter (nbsample) specify the number of stereo 16bits samples you want, so sampleCount/2 - jar_mod_fillbuffer((jar_mod_context_t *)music.ctxData, (short *)pcm, frameCountToStream, 0); + jar_mod_fillbuffer((jar_mod_context_t *)music.ctxData, (short *)AUDIO.System.pcmBuffer, framesToStream, 0); + + //jar_mod_seek_start((jar_mod_context_t *)music.ctxData); + } break; #endif default: break; } - UpdateAudioStream(music.stream, pcm, frameCountToStream); + UpdateAudioStream(music.stream, AUDIO.System.pcmBuffer, framesToStream); - framesLeft -= frameCountToStream; + music.stream.buffer->framesProcessed = music.stream.buffer->framesProcessed%music.frameCount; - if (framesLeft <= 0) + if (framesLeft <= subBufferSizeInFrames) { - streamEnding = true; - break; + if (!music.looping) + { + // Streaming is ending, we filled latest frames from input + StopMusicStream(music); + return; + } } } - // Free allocated pcm data - RL_FREE(pcm); - - // Reset audio stream for looping - if (streamEnding) - { - StopMusicStream(music); // Stop music (and reset) - if (music.looping) PlayMusicStream(music); // Play again - } - else - { - // NOTE: In case window is minimized, music stream is stopped, - // just make sure to play again on window restore - if (IsMusicStreamPlaying(music)) PlayMusicStream(music); - } + // NOTE: In case window is minimized, music stream is stopped, + // just make sure to play again on window restore + if (IsMusicStreamPlaying(music)) PlayMusicStream(music); } // Check if any music is playing @@ -1806,6 +1879,12 @@ void SetMusicPitch(Music music, float pitch) SetAudioBufferPitch(music.stream.buffer, pitch); } +// Set pan for a music +void SetMusicPan(Music music, float pan) +{ + SetAudioBufferPan(music.stream.buffer, pan); +} + // Get music time length (in seconds) float GetMusicTimeLength(Music music) { @@ -1834,7 +1913,13 @@ float GetMusicTimePlayed(Music music) #endif { //ma_uint32 frameSizeInBytes = ma_get_bytes_per_sample(music.stream.buffer->dsp.formatConverterIn.config.formatIn)*music.stream.buffer->dsp.formatConverterIn.config.channels; - unsigned int framesPlayed = music.stream.buffer->framesProcessed; + int framesProcessed = (int)music.stream.buffer->framesProcessed; + int subBufferSize = (int)music.stream.buffer->sizeInFrames/2; + int framesInFirstBuffer = music.stream.buffer->isSubBufferProcessed[0]? 0 : subBufferSize; + int framesInSecondBuffer = music.stream.buffer->isSubBufferProcessed[1]? 0 : subBufferSize; + int framesSentToMix = music.stream.buffer->frameCursorPos%subBufferSize; + int framesPlayed = (framesProcessed - framesInFirstBuffer - framesInSecondBuffer + framesSentToMix)%(int)music.frameCount; + if (framesPlayed < 0) framesPlayed += music.frameCount; secondsPlayed = (float)framesPlayed/music.stream.sampleRate; } } @@ -1909,16 +1994,14 @@ void UpdateAudioStream(AudioStream stream, const void *data, int frameCount) ma_uint32 subBufferSizeInFrames = stream.buffer->sizeInFrames/2; unsigned char *subBuffer = stream.buffer->data + ((subBufferSizeInFrames*stream.channels*(stream.sampleSize/8))*subBufferToUpdate); - // TODO: Get total frames processed on this buffer... DOES NOT WORK. + // Total frames processed in buffer is always the complete size, filled with 0 if required stream.buffer->framesProcessed += subBufferSizeInFrames; // Does this API expect a whole buffer to be updated in one go? // Assuming so, but if not will need to change this logic. if (subBufferSizeInFrames >= (ma_uint32)frameCount) { - ma_uint32 framesToWrite = subBufferSizeInFrames; - - if (framesToWrite > (ma_uint32)frameCount) framesToWrite = (ma_uint32)frameCount; + ma_uint32 framesToWrite = (ma_uint32)frameCount; ma_uint32 bytesToWrite = framesToWrite*stream.channels*(stream.sampleSize/8); memcpy(subBuffer, data, bytesToWrite); @@ -1986,28 +2069,98 @@ void SetAudioStreamPitch(AudioStream stream, float pitch) SetAudioBufferPitch(stream.buffer, pitch); } +// Set pan for audio stream +void SetAudioStreamPan(AudioStream stream, float pan) +{ + SetAudioBufferPan(stream.buffer, pan); +} + // Default size for new audio streams void SetAudioStreamBufferSizeDefault(int size) { AUDIO.Buffer.defaultSize = size; } +// Audio thread callback to request new data +void SetAudioStreamCallback(AudioStream stream, AudioCallback callback) +{ + if (stream.buffer != NULL) stream.buffer->callback = callback; +} + +// Add processor to audio stream. Contrary to buffers, the order of processors is important. +// The new processor must be added at the end. As there aren't supposed to be a lot of processors attached to +// a given stream, we iterate through the list to find the end. That way we don't need a pointer to the last element. +void AttachAudioStreamProcessor(AudioStream stream, AudioCallback process) +{ + ma_mutex_lock(&AUDIO.System.lock); + + rAudioProcessor *processor = (rAudioProcessor *)RL_CALLOC(1, sizeof(rAudioProcessor)); + processor->process = process; + + rAudioProcessor *last = stream.buffer->processor; + + while (last && last->next) + { + last = last->next; + } + if (last) + { + processor->prev = last; + last->next = processor; + } + else stream.buffer->processor = processor; + + ma_mutex_unlock(&AUDIO.System.lock); +} + +void DetachAudioStreamProcessor(AudioStream stream, AudioCallback process) +{ + ma_mutex_lock(&AUDIO.System.lock); + + rAudioProcessor *processor = stream.buffer->processor; + + while (processor) + { + rAudioProcessor *next = processor->next; + rAudioProcessor *prev = processor->prev; + + if (processor->process == process) + { + if (stream.buffer->processor == processor) stream.buffer->processor = next; + if (prev) prev->next = next; + if (next) next->prev = prev; + + RL_FREE(processor); + } + + processor = next; + } + + ma_mutex_unlock(&AUDIO.System.lock); +} + //---------------------------------------------------------------------------------- // Module specific Functions Definition //---------------------------------------------------------------------------------- // Log callback function -static void OnLog(ma_context *pContext, ma_device *pDevice, ma_uint32 logLevel, const char *message) +static void OnLog(void *pUserData, ma_uint32 level, const char *pMessage) { - (void)pContext; - (void)pDevice; - - TRACELOG(LOG_WARNING, "miniaudio: %s", message); // All log messages from miniaudio are errors + TRACELOG(LOG_WARNING, "miniaudio: %s", pMessage); // All log messages from miniaudio are errors } // Reads audio data from an AudioBuffer object in internal format. static ma_uint32 ReadAudioBufferFramesInInternalFormat(AudioBuffer *audioBuffer, void *framesOut, ma_uint32 frameCount) { + // Using audio buffer callback + if (audioBuffer->callback) + { + audioBuffer->callback(framesOut, frameCount); + audioBuffer->framesProcessed += frameCount; + + return frameCount; + } + ma_uint32 subBufferSizeInFrames = (audioBuffer->sizeInFrames > 1)? audioBuffer->sizeInFrames/2 : audioBuffer->sizeInFrames; ma_uint32 currentSubBufferIndex = audioBuffer->frameCursorPos/subBufferSizeInFrames; @@ -2019,7 +2172,7 @@ static ma_uint32 ReadAudioBufferFramesInInternalFormat(AudioBuffer *audioBuffer, isSubBufferProcessed[0] = audioBuffer->isSubBufferProcessed[0]; isSubBufferProcessed[1] = audioBuffer->isSubBufferProcessed[1]; - ma_uint32 frameSizeInBytes = ma_get_bytes_per_frame(audioBuffer->converter.config.formatIn, audioBuffer->converter.config.channelsIn); + ma_uint32 frameSizeInBytes = ma_get_bytes_per_frame(audioBuffer->converter.formatIn, audioBuffer->converter.channelsIn); // Fill out every frame until we find a buffer that's marked as processed. Then fill the remainder with 0 ma_uint32 framesRead = 0; @@ -2099,20 +2252,21 @@ static ma_uint32 ReadAudioBufferFramesInMixingFormat(AudioBuffer *audioBuffer, f // detail to remember here is that we never, ever attempt to read more input data than is required for the specified number of output // frames. This can be achieved with ma_data_converter_get_required_input_frame_count(). ma_uint8 inputBuffer[4096] = { 0 }; - ma_uint32 inputBufferFrameCap = sizeof(inputBuffer)/ma_get_bytes_per_frame(audioBuffer->converter.config.formatIn, audioBuffer->converter.config.channelsIn); + ma_uint32 inputBufferFrameCap = sizeof(inputBuffer)/ma_get_bytes_per_frame(audioBuffer->converter.formatIn, audioBuffer->converter.channelsIn); ma_uint32 totalOutputFramesProcessed = 0; while (totalOutputFramesProcessed < frameCount) { ma_uint64 outputFramesToProcessThisIteration = frameCount - totalOutputFramesProcessed; + ma_uint64 inputFramesToProcessThisIteration = 0; - ma_uint64 inputFramesToProcessThisIteration = ma_data_converter_get_required_input_frame_count(&audioBuffer->converter, outputFramesToProcessThisIteration); + (void)ma_data_converter_get_required_input_frame_count(&audioBuffer->converter, outputFramesToProcessThisIteration, &inputFramesToProcessThisIteration); if (inputFramesToProcessThisIteration > inputBufferFrameCap) { inputFramesToProcessThisIteration = inputBufferFrameCap; } - float *runningFramesOut = framesOut + (totalOutputFramesProcessed*audioBuffer->converter.config.channelsOut); + float *runningFramesOut = framesOut + (totalOutputFramesProcessed*audioBuffer->converter.channelsOut); /* At this point we can convert the data to our mixing format. */ ma_uint64 inputFramesProcessedThisIteration = ReadAudioBufferFramesInInternalFormat(audioBuffer, inputBuffer, (ma_uint32)inputFramesToProcessThisIteration); /* Safe cast. */ @@ -2181,7 +2335,15 @@ static void OnSendAudioDataToDevice(ma_device *pDevice, void *pFramesOut, const float *framesOut = (float *)pFramesOut + (framesRead*AUDIO.System.device.playback.channels); float *framesIn = tempBuffer; - MixAudioFrames(framesOut, framesIn, framesJustRead, audioBuffer->volume); + // Apply processors chain if defined + rAudioProcessor *processor = audioBuffer->processor; + while (processor) + { + processor->process(framesIn, framesJustRead); + processor = processor->next; + } + + MixAudioFrames(framesOut, framesIn, framesJustRead, audioBuffer); framesToRead -= framesJustRead; framesRead += framesJustRead; @@ -2221,18 +2383,45 @@ static void OnSendAudioDataToDevice(ma_device *pDevice, void *pFramesOut, const ma_mutex_unlock(&AUDIO.System.lock); } -// This is the main mixing function. Mixing is pretty simple in this project - it's just an accumulation. -// NOTE: framesOut is both an input and an output. It will be initially filled with zeros outside of this function. -static void MixAudioFrames(float *framesOut, const float *framesIn, ma_uint32 frameCount, float localVolume) +// Main mixing function, pretty simple in this project, just an accumulation +// NOTE: framesOut is both an input and an output, it is initially filled with zeros outside of this function +static void MixAudioFrames(float *framesOut, const float *framesIn, ma_uint32 frameCount, AudioBuffer *buffer) { - for (ma_uint32 iFrame = 0; iFrame < frameCount; ++iFrame) - { - for (ma_uint32 iChannel = 0; iChannel < AUDIO.System.device.playback.channels; ++iChannel) - { - float *frameOut = framesOut + (iFrame*AUDIO.System.device.playback.channels); - const float *frameIn = framesIn + (iFrame*AUDIO.System.device.playback.channels); + const float localVolume = buffer->volume; + const ma_uint32 channels = AUDIO.System.device.playback.channels; - frameOut[iChannel] += (frameIn[iChannel]*localVolume); + if (channels == 2) // We consider panning + { + const float left = buffer->pan; + const float right = 1.0f - left; + + // Fast sine approximation in [0..1] for pan law: y = 0.5f*x*(3 - x*x); + const float levels[2] = { localVolume*0.5f*left*(3.0f - left*left), localVolume*0.5f*right*(3.0f - right*right) }; + + float *frameOut = framesOut; + const float *frameIn = framesIn; + + for (ma_uint32 frame = 0; frame < frameCount; frame++) + { + frameOut[0] += (frameIn[0]*levels[0]); + frameOut[1] += (frameIn[1]*levels[1]); + + frameOut += 2; + frameIn += 2; + } + } + else // We do not consider panning + { + for (ma_uint32 frame = 0; frame < frameCount; frame++) + { + for (ma_uint32 c = 0; c < channels; c++) + { + float *frameOut = framesOut + (frame*channels); + const float *frameIn = framesIn + (frame*channels); + + // Output accumulates input multiplied by volume to provided output (usually 0) + frameOut[c] += (frameIn[c]*localVolume); + } } } } @@ -2263,38 +2452,6 @@ static const char *GetFileExtension(const char *fileName) return dot; } -// Check if two text string are equal -// REQUIRES: strcmp() -static bool TextIsEqual(const char *text1, const char *text2) -{ - bool result = false; - - if (strcmp(text1, text2) == 0) result = true; - - return result; -} - -// Get lower case version of provided string -// REQUIRES: tolower() -static const char *TextToLower(const char *text) -{ - #define MAX_TEXT_BUFFER_LENGTH 1024 - - static char buffer[MAX_TEXT_BUFFER_LENGTH] = { 0 }; - - for (int i = 0; i < MAX_TEXT_BUFFER_LENGTH; i++) - { - if (text[i] != '\0') - { - buffer[i] = (char)tolower(text[i]); - //if ((text[i] >= 'A') && (text[i] <= 'Z')) buffer[i] = text[i] + 32; - } - else { buffer[i] = '\0'; break; } - } - - return buffer; -} - // Load data from file into a buffer static unsigned char *LoadFileData(const char *fileName, unsigned int *bytesRead) { @@ -2380,3 +2537,5 @@ static bool SaveFileText(const char *fileName, char *text) #endif #undef AudioBuffer + +#endif // SUPPORT_MODULE_RAUDIO diff --git a/raylib/raylib.h b/raylib/raylib.h index e5453de..0746ca2 100644 --- a/raylib/raylib.h +++ b/raylib/raylib.h @@ -1,6 +1,6 @@ /********************************************************************************************** * -* raylib v4.0 - A simple and easy-to-use library to enjoy videogames programming (www.raylib.com) +* raylib v4.2 - A simple and easy-to-use library to enjoy videogames programming (www.raylib.com) * * FEATURES: * - NO external dependencies, all required libraries included with raylib @@ -33,8 +33,8 @@ * * OPTIONAL DEPENDENCIES (included): * [rcore] msf_gif (Miles Fogle) for GIF recording -* [rcore] sinfl (Micha Mettke) for DEFLATE decompression algorythm -* [rcore] sdefl (Micha Mettke) for DEFLATE compression algorythm +* [rcore] sinfl (Micha Mettke) for DEFLATE decompression algorithm +* [rcore] sdefl (Micha Mettke) for DEFLATE compression algorithm * [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_resize (Sean Barret) for image resizing algorithms @@ -43,6 +43,7 @@ * [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] Model3D (bzt) for models loading (M3D, https://bztsrc.gitlab.io/model3d) * [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 @@ -56,7 +57,7 @@ * 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-2021 Ramon Santamaria (@raysan5) +* Copyright (c) 2013-2022 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. @@ -80,12 +81,15 @@ #include // Required for: va_list - Only used by TraceLogCallback -#define RAYLIB_VERSION "4.0" +#define RAYLIB_VERSION "4.2" // Function specifiers in case library is build/used as a shared library (Windows) // NOTE: Microsoft specifiers to tell compiler that symbols are imported/exported from a .dll #if defined(_WIN32) #if defined(BUILD_LIBTYPE_SHARED) + #if defined(__TINYC__) + #define __declspec(x) __attribute__((x)) + #endif #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) @@ -110,6 +114,7 @@ #endif // Allow custom memory allocators +// NOTE: Require recompiling raylib sources #ifndef RL_MALLOC #define RL_MALLOC(sz) malloc(sz) #endif @@ -177,10 +182,10 @@ // Structures Definition //---------------------------------------------------------------------------------- // Boolean type -#if defined(__STDC__) && __STDC_VERSION__ >= 199901L +#if (defined(__STDC__) && __STDC_VERSION__ >= 199901L) || (defined(_MSC_VER) && _MSC_VER >= 1800) #include #elif !defined(__cplusplus) && !defined(bool) - typedef enum bool { false, true } bool; + typedef enum bool { false = 0, true = !false } bool; #define RL_BOOL_TYPE #endif @@ -322,7 +327,7 @@ typedef struct Mesh { // 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 second texture coordinates (useful for lightmaps) (shader-location = 5) + 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) @@ -425,11 +430,15 @@ typedef struct Wave { 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) @@ -478,6 +487,13 @@ typedef struct VrStereoConfig { 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; + //---------------------------------------------------------------------------------- // Enumerators Definition //---------------------------------------------------------------------------------- @@ -497,6 +513,7 @@ typedef enum { 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_MSAA_4X_HINT = 0x00000020, // Set to try enabling MSAA 4X FLAG_INTERLACED_HINT = 0x00010000 // Set to try enabling interlaced video format (for V3D) } ConfigFlags; @@ -799,7 +816,7 @@ typedef enum { // 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 aproximation + 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 @@ -839,7 +856,8 @@ typedef enum { BLEND_MULTIPLIED, // Blend textures multiplying colors BLEND_ADD_COLORS, // Blend textures adding colors (alternative) BLEND_SUBTRACT_COLORS, // Blend textures subtracting colors (alternative) - BLEND_CUSTOM // Belnd textures using custom src/dst factors (use rlSetBlendMode()) + BLEND_ALPHA_PREMULTIPLY, // Blend premultiplied textures considering alpha + BLEND_CUSTOM // Blend textures using custom src/dst factors (use rlSetBlendMode()) } BlendMode; // Gesture @@ -885,8 +903,8 @@ typedef enum { typedef void (*TraceLogCallback)(int logLevel, const char *text, va_list args); // Logging: Redirect trace log messages typedef unsigned char *(*LoadFileDataCallback)(const char *fileName, unsigned int *bytesRead); // FileIO: Load binary data typedef bool (*SaveFileDataCallback)(const char *fileName, void *data, unsigned int bytesToWrite); // 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 +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 @@ -913,7 +931,7 @@ RLAPI bool IsWindowMaximized(void); // Check if wi RLAPI bool IsWindowFocused(void); // Check if window is currently focused (only PLATFORM_DESKTOP) 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 SetWindowState(unsigned int flags); // Set window configuration state using flags (only PLATFORM_DESKTOP) RLAPI void ClearWindowState(unsigned int flags); // Clear window configuration state flags RLAPI void ToggleFullscreen(void); // Toggle window state: fullscreen/windowed (only PLATFORM_DESKTOP) RLAPI void MaximizeWindow(void); // Set window state: maximized, if resizable (only PLATFORM_DESKTOP) @@ -925,14 +943,17 @@ RLAPI void SetWindowPosition(int x, int y); // Set window RLAPI void SetWindowMonitor(int monitor); // Set monitor for the current window (fullscreen mode) RLAPI void SetWindowMinSize(int width, int height); // Set window minimum 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] (only PLATFORM_DESKTOP) 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 connected monitor RLAPI Vector2 GetMonitorPosition(int monitor); // Get specified monitor position -RLAPI int GetMonitorWidth(int monitor); // Get specified monitor width (max available by monitor) -RLAPI int GetMonitorHeight(int monitor); // Get specified monitor height (max available by monitor) +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 @@ -941,6 +962,8 @@ RLAPI Vector2 GetWindowScaleDPI(void); // Get window RLAPI const char *GetMonitorName(int monitor); // Get the human-readable, UTF-8 encoded name of the primary monitor RLAPI void SetClipboardText(const char *text); // Set clipboard text content RLAPI const char *GetClipboardText(void); // Get clipboard text 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 // Custom frame control functions // NOTE: Those functions are intended for advance users that want full control over the frame processing @@ -948,7 +971,7 @@ RLAPI const char *GetClipboardText(void); // Get clipboa // 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(float ms); // Wait for some milliseconds (halt program execution) +RLAPI void WaitTime(double seconds); // Wait for some time (halt program execution) // Cursor-related functions RLAPI void ShowCursor(void); // Shows cursor @@ -998,9 +1021,9 @@ RLAPI Ray GetMouseRay(Vector2 mousePosition, Camera camera); // Get a ray t RLAPI Matrix GetCameraMatrix(Camera camera); // Get camera transform matrix (view matrix) RLAPI Matrix GetCameraMatrix2D(Camera2D camera); // Get camera 2d transform matrix RLAPI Vector2 GetWorldToScreen(Vector3 position, Camera camera); // Get the screen space position for a 3d world space position +RLAPI Vector2 GetScreenToWorld2D(Vector2 position, Camera2D camera); // Get the world space position for a 2d camera screen 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 // Timing-related functions RLAPI void SetTargetFPS(int fps); // Set target FPS (maximum) @@ -1020,6 +1043,8 @@ RLAPI void *MemAlloc(int size); // Internal me RLAPI void *MemRealloc(void *ptr, int size); // Internal memory reallocator RLAPI void MemFree(void *ptr); // Internal memory free +RLAPI void OpenURL(const char *url); // Open URL with default system browser (if available) + // Set custom callbacks // WARNING: Callbacks setup is intended for advance users RLAPI void SetTraceLogCallback(TraceLogCallback callback); // Set custom trace log @@ -1029,40 +1054,39 @@ RLAPI void SetLoadFileTextCallback(LoadFileTextCallback callback); // Set custom RLAPI void SetSaveFileTextCallback(SaveFileTextCallback callback); // Set custom file text data saver // Files management functions -RLAPI unsigned char *LoadFileData(const char *fileName, unsigned int *bytesRead); // Load file data as byte array (read) +RLAPI unsigned char *LoadFileData(const char *fileName, unsigned int *bytesRead); // 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, unsigned int bytesToWrite); // Save data to file from byte array (write), returns true on success +RLAPI bool SaveFileData(const char *fileName, void *data, unsigned int bytesToWrite); // Save data to file from byte array (write), returns true on success +RLAPI bool ExportDataAsCode(const char *data, unsigned int size, 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 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 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 char **GetDirectoryFiles(const char *dirPath, int *count); // Get filenames in a directory path (memory should be freed) -RLAPI void ClearDirectoryFiles(void); // Clear directory files paths buffers (free memory) +RLAPI const char *GetApplicationDirectory(void); // Get the directory if the running application (uses static string) 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 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 +RLAPI void UnloadDirectoryFiles(FilePathList files); // Unload filepaths RLAPI bool IsFileDropped(void); // Check if a file has been dropped into window -RLAPI char **GetDroppedFiles(int *count); // Get dropped files names (memory should be freed) -RLAPI void ClearDroppedFiles(void); // Clear dropped files paths buffer (free memory) +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(unsigned char *data, int dataLength, int *compDataLength); // Compress data (DEFLATE algorithm) -RLAPI unsigned char *DecompressData(unsigned char *compData, int compDataLength, int *dataLength); // Decompress data (DEFLATE algorithm) -RLAPI char *EncodeDataBase64(const unsigned char *data, int dataLength, int *outputLength); // Encode data to Base64 string -RLAPI unsigned char *DecodeDataBase64(unsigned char *data, int *outputLength); // Decode Base64 string data - -// Persistent storage management -RLAPI bool SaveStorageValue(unsigned int position, int value); // Save integer value to storage file (to defined position), returns true on success -RLAPI int LoadStorageValue(unsigned int position); // Load integer value from storage file (from defined position) - -RLAPI void OpenURL(const char *url); // Open URL with default system browser (if available) +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() //------------------------------------------------------------------------------------ // Input Handling Functions (Module: core) @@ -1101,7 +1125,8 @@ RLAPI Vector2 GetMouseDelta(void); // Get mouse delta 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 Y +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 @@ -1290,10 +1315,10 @@ RLAPI void DrawTextureV(Texture2D texture, Vector2 position, Color tint); 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 DrawTextureQuad(Texture2D texture, Vector2 tiling, Vector2 offset, Rectangle quad, Color tint); // Draw texture quad with tiling and offset parameters -RLAPI void DrawTextureTiled(Texture2D texture, Rectangle source, Rectangle dest, Vector2 origin, float rotation, float scale, Color tint); // Draw part of a texture (defined by a rectangle) with rotation and scale tiled into dest. +RLAPI void DrawTextureTiled(Texture2D texture, Rectangle source, Rectangle dest, Vector2 origin, float rotation, float scale, Color tint); // Draw part of a texture (defined by a rectangle) with rotation and scale tiled into dest. 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 -RLAPI void DrawTexturePoly(Texture2D texture, Vector2 center, Vector2 *points, Vector2 *texcoords, int pointCount, Color tint); // Draw a textured polygon +RLAPI void DrawTexturePoly(Texture2D texture, Vector2 center, Vector2 *points, Vector2 *texcoords, int pointCount, Color tint); // Draw a textured polygon // Color/pixel related functions RLAPI Color Fade(Color color, float alpha); // Get color with alpha applied, alpha goes from 0.0f to 1.0f @@ -1316,20 +1341,22 @@ RLAPI int GetPixelDataSize(int width, int height, int format); // G // 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 *fontChars, int glyphCount); // Load font from file with extended parameters +RLAPI Font LoadFontEx(const char *fileName, int fontSize, int *fontChars, int glyphCount); // Load font from file with extended parameters, use NULL for fontChars and 0 for glyphCount to load the default character set 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 *fontChars, int glyphCount); // Load font from memory buffer, fileType refers to extension: i.e. '.ttf' -RLAPI GlyphInfo *LoadFontData(const unsigned char *fileData, int dataSize, int fontSize, int *fontChars, int glyphCount, int type); // Load font data for further use -RLAPI Image GenImageFontAtlas(const GlyphInfo *chars, Rectangle **recs, int glyphCount, int fontSize, int padding, int packMethod); // Generate image font atlas using chars info -RLAPI void UnloadFontData(GlyphInfo *chars, int glyphCount); // Unload font chars info data (RAM) -RLAPI void UnloadFont(Font font); // Unload Font from GPU memory (VRAM) +RLAPI GlyphInfo *LoadFontData(const unsigned char *fileData, int dataSize, int fontSize, int *fontChars, int glyphCount, int type); // Load font data for further use +RLAPI Image GenImageFontAtlas(const GlyphInfo *chars, Rectangle **recs, int glyphCount, int fontSize, int padding, int packMethod); // Generate image font atlas using chars info +RLAPI void UnloadFontData(GlyphInfo *chars, 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 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 DrawTextCodepoint(Font font, int codepoint, Vector2 position, float fontSize, Color tint); // Draw one character (codepoint) +RLAPI void DrawTextCodepoints(Font font, const int *codepoints, int count, Vector2 position, float fontSize, float spacing, Color tint); // Draw multiple character (codepoint) // Text font info functions RLAPI int MeasureText(const char *text, int fontSize); // Measure string width for default font @@ -1344,7 +1371,7 @@ RLAPI void UnloadCodepoints(int *codepoints); // Unload RLAPI int GetCodepointCount(const char *text); // Get total number of codepoints in a UTF-8 encoded string RLAPI int GetCodepoint(const char *text, int *bytesProcessed); // Get next codepoint in a UTF-8 encoded string, 0x3f('?') is returned on failure RLAPI const char *CodepointToUTF8(int codepoint, int *byteSize); // Encode one codepoint into UTF-8 byte array (array length returned as parameter) -RLAPI char *TextCodepointsToUTF8(int *codepoints, int length); // Encode text as codepoints array into UTF-8 text string (WARNING: memory must be freed!) +RLAPI char *TextCodepointsToUTF8(const int *codepoints, int length); // Encode text as codepoints array into UTF-8 text string (WARNING: memory must be freed!) // Text strings management functions (no UTF-8 strings, only byte chars) // NOTE: Some strings allocate memory internally for returned strings, just be careful! @@ -1373,7 +1400,7 @@ RLAPI void DrawLine3D(Vector3 startPos, Vector3 endPos, Color color); 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(Vector3 *points, int pointCount, Color color); // Draw a triangle strip defined by points +RLAPI void DrawTriangleStrip3D(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 @@ -1414,14 +1441,13 @@ RLAPI void DrawBillboardPro(Camera camera, Texture2D texture, Rectangle source, // 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, void *data, int dataSize, int offset); // Update mesh vertex data in GPU for a specific buffer index +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, Matrix *transforms, int instances); // Draw multiple mesh instances with material and different transforms +RLAPI void DrawMeshInstanced(Mesh mesh, Material material, const Matrix *transforms, int instances); // Draw multiple mesh instances with material and different transforms RLAPI bool ExportMesh(Mesh mesh, const char *fileName); // Export mesh data to file, returns true on success RLAPI BoundingBox GetMeshBoundingBox(Mesh mesh); // Compute mesh bounding box limits RLAPI void GenMeshTangents(Mesh *mesh); // Compute mesh tangents -RLAPI void GenMeshBinormals(Mesh *mesh); // Compute mesh binormals // Mesh generation functions RLAPI Mesh GenMeshPoly(int sides, float radius); // Generate polygonal mesh @@ -1447,23 +1473,23 @@ RLAPI void SetModelMeshMaterial(Model *model, int meshId, int materialId); RLAPI ModelAnimation *LoadModelAnimations(const char *fileName, unsigned int *animCount); // Load model animations from file RLAPI void UpdateModelAnimation(Model model, ModelAnimation anim, int frame); // Update model animation pose RLAPI void UnloadModelAnimation(ModelAnimation anim); // Unload animation data -RLAPI void UnloadModelAnimations(ModelAnimation* animations, unsigned int count); // Unload animation array data +RLAPI void UnloadModelAnimations(ModelAnimation *animations, unsigned int count); // 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 GetRayCollisionModel(Ray ray, Model model); // Get collision info between ray and model -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 +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 @@ -1493,15 +1519,16 @@ RLAPI int GetSoundsPlaying(void); // Get num 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 WaveFormat(Wave *wave, int sampleRate, int sampleSize, int channels); // Convert wave data to desired format +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 initSample, int finalSample); // Crop a wave to defined samples range -RLAPI float *LoadWaveSamples(Wave wave); // Load samples data from wave as a floats array +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, unsigned char *data, int dataSize); // Load music stream from data +RLAPI Music LoadMusicStreamFromMemory(const char *fileType, const unsigned char *data, int dataSize); // Load music stream from data 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 @@ -1512,12 +1539,13 @@ RLAPI void ResumeMusicStream(Music music); // Resume 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 void UnloadAudioStream(AudioStream stream); // Unload audio stream and free memory +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 @@ -1527,7 +1555,12 @@ RLAPI bool IsAudioStreamPlaying(AudioStream stream); // Check i 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 +RLAPI void DetachAudioStreamProcessor(AudioStream stream, AudioCallback processor); // Detach audio stream processor from stream #if defined(__cplusplus) } diff --git a/raylib/raymath.h b/raylib/raymath.h index 48846a7..fbe4cea 100644 --- a/raylib/raymath.h +++ b/raylib/raymath.h @@ -18,14 +18,14 @@ * - 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" anmed variable for return +* - Functions use always a "result" variable for return * - Functions are always defined inline * - Angles are always in radians (DEG2RAD/RAD2DEG macros provided for convenience) * * * LICENSE: zlib/libpng * -* Copyright (c) 2015-2021 Ramon Santamaria (@raysan5) +* Copyright (c) 2015-2022 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. @@ -77,6 +77,10 @@ #define PI 3.14159265358979323846f #endif +#ifndef EPSILON + #define EPSILON 0.000001f +#endif + #ifndef DEG2RAD #define DEG2RAD (PI/180.0f) #endif @@ -154,7 +158,7 @@ typedef struct float16 { float v[16]; } float16; -#include // Required for: sinf(), cosf(), tan(), atan2f(), sqrtf(), fminf(), fmaxf(), fabs() +#include // Required for: sinf(), cosf(), tan(), atan2f(), sqrtf(), floor(), fminf(), fmaxf(), fabs() //---------------------------------------------------------------------------------- // Module Functions Definition - Utils math @@ -189,7 +193,23 @@ RMAPI float Normalize(float value, float start, float end) // 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; + 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) +{ + int result = (fabsf(x - y)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(x), fabsf(y)))); return result; } @@ -278,12 +298,18 @@ RMAPI float Vector2Distance(Vector2 v1, Vector2 v2) return result; } -// Calculate angle from two vectors in X-axis +// 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 from two vectors RMAPI float Vector2Angle(Vector2 v1, Vector2 v2) { - float result = atan2f(v2.y - v1.y, v2.x - v1.x)*(180.0f/PI); - - if (result < 0) result += 360.0f; + float result = atan2f(v2.y, v2.x) - atan2f(v1.y, v1.x); return result; } @@ -328,13 +354,29 @@ RMAPI Vector2 Vector2Normalize(Vector2 v) if (length > 0) { - result.x = v.x*1.0f/length; - result.y = v.y*1.0f/length; + 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) { @@ -364,8 +406,11 @@ RMAPI Vector2 Vector2Rotate(Vector2 v, float angle) { Vector2 result = { 0 }; - result.x = v.x*cosf(angle) - v.y*sinf(angle); - result.y = v.x*sinf(angle) + v.y*cosf(angle); + 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; } @@ -389,6 +434,62 @@ RMAPI Vector2 Vector2MoveTowards(Vector2 v, Vector2 target, float 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); + + if (length < min) + { + float scale = min/length; + result.x = v.x*scale; + result.y = v.y*scale; + } + else if (length > max) + { + float 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) +{ + 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; +} + //---------------------------------------------------------------------------------- // Module Functions Definition - Vector3 math //---------------------------------------------------------------------------------- @@ -473,14 +574,14 @@ RMAPI Vector3 Vector3Perpendicular(Vector3 v) float min = (float) fabs(v.x); Vector3 cardinalAxis = {1.0f, 0.0f, 0.0f}; - if (fabs(v.y) < min) + if (fabsf(v.y) < min) { min = (float) fabs(v.y); Vector3 tmp = {0.0f, 1.0f, 0.0f}; cardinalAxis = tmp; } - if (fabs(v.z) < min) + if (fabsf(v.z) < min) { Vector3 tmp = {0.0f, 0.0f, 1.0f}; cardinalAxis = tmp; @@ -531,17 +632,28 @@ RMAPI float Vector3Distance(Vector3 v1, Vector3 v2) return result; } -// Calculate angle between two vectors in XY and XZ -RMAPI Vector2 Vector3Angle(Vector3 v1, Vector3 v2) +// Calculate square distance between two vectors +RMAPI float Vector3DistanceSqr(Vector3 v1, Vector3 v2) { - Vector2 result = { 0 }; + 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; - result.x = atan2f(dx, dz); // Angle in XZ - result.y = atan2f(dy, sqrtf(dx*dx + dz*dz)); // Angle in XY + 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; } @@ -641,6 +753,58 @@ RMAPI Vector3 Vector3RotateByQuaternion(Vector3 v, Quaternion q) 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; +} + // Calculate linear interpolation between two vectors RMAPI Vector3 Vector3Lerp(Vector3 v1, Vector3 v2, float amount) { @@ -815,6 +979,92 @@ RMAPI float3 Vector3ToFloatV(Vector3 v) 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); + + if (length < min) + { + float scale = min/length; + result.x = v.x*scale; + result.y = v.y*scale; + result.z = v.z*scale; + } + else if (length > max) + { + float 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) +{ + 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 where v specifies the +// normalized direction of the incoming ray, n specifies the +// normalized normal vector of the interface of two optical media, +// and r specifies the 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 - Matrix math //---------------------------------------------------------------------------------- @@ -920,45 +1170,6 @@ RMAPI Matrix MatrixInvert(Matrix mat) return result; } -// Normalize provided matrix -RMAPI Matrix MatrixNormalize(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; - - // MatrixDeterminant(mat) - float det = 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; - - result.m0 = mat.m0/det; - result.m1 = mat.m1/det; - result.m2 = mat.m2/det; - result.m3 = mat.m3/det; - result.m4 = mat.m4/det; - result.m5 = mat.m5/det; - result.m6 = mat.m6/det; - result.m7 = mat.m7/det; - result.m8 = mat.m8/det; - result.m9 = mat.m9/det; - result.m10 = mat.m10/det; - result.m11 = mat.m11/det; - result.m12 = mat.m12/det; - result.m13 = mat.m13/det; - result.m14 = mat.m14/det; - result.m15 = mat.m15/det; - - return result; -} - // Get identity matrix RMAPI Matrix MatrixIdentity(void) { @@ -1102,7 +1313,8 @@ RMAPI Matrix MatrixRotate(Vector3 axis, float angle) return result; } -// Get x-rotation matrix (angle in radians) +// 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, @@ -1114,14 +1326,15 @@ RMAPI Matrix MatrixRotateX(float angle) float sinres = sinf(angle); result.m5 = cosres; - result.m6 = -sinres; - result.m9 = sinres; + result.m6 = sinres; + result.m9 = -sinres; result.m10 = cosres; return result; } -// Get y-rotation matrix (angle in radians) +// 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, @@ -1133,14 +1346,15 @@ RMAPI Matrix MatrixRotateY(float angle) float sinres = sinf(angle); result.m0 = cosres; - result.m2 = sinres; - result.m8 = -sinres; + result.m2 = -sinres; + result.m8 = sinres; result.m10 = cosres; return result; } -// Get z-rotation matrix (angle in radians) +// 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, @@ -1152,74 +1366,76 @@ RMAPI Matrix MatrixRotateZ(float angle) float sinres = sinf(angle); result.m0 = cosres; - result.m1 = -sinres; - result.m4 = sinres; + result.m1 = sinres; + result.m4 = -sinres; result.m5 = cosres; return result; } -// Get xyz-rotation matrix (angles in radians) -RMAPI Matrix MatrixRotateXYZ(Vector3 ang) +// 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(-ang.z); - float sinz = sinf(-ang.z); - float cosy = cosf(-ang.y); - float siny = sinf(-ang.y); - float cosx = cosf(-ang.x); - float sinx = sinf(-ang.x); + 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.m4 = (cosz*siny*sinx) - (sinz*cosx); - result.m8 = (cosz*siny*cosx) + (sinz*sinx); + result.m1 = (cosz*siny*sinx) - (sinz*cosx); + result.m2 = (cosz*siny*cosx) + (sinz*sinx); - result.m1 = sinz*cosy; + result.m4 = sinz*cosy; result.m5 = (sinz*siny*sinx) + (cosz*cosx); - result.m9 = (sinz*siny*cosx) - (cosz*sinx); + result.m6 = (sinz*siny*cosx) - (cosz*sinx); - result.m2 = -siny; - result.m6 = cosy*sinx; + result.m8 = -siny; + result.m9 = cosy*sinx; result.m10= cosy*cosx; return result; } -// Get zyx-rotation matrix (angles in radians) -RMAPI Matrix MatrixRotateZYX(Vector3 ang) +// Get zyx-rotation matrix +// NOTE: Angle must be provided in radians +RMAPI Matrix MatrixRotateZYX(Vector3 angle) { Matrix result = { 0 }; - float cz = cosf(ang.z); - float sz = sinf(ang.z); - float cy = cosf(ang.y); - float sy = sinf(ang.y); - float cx = cosf(ang.x); - float sx = sinf(ang.x); + 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.m1 = cz*sy*sx - cx*sz; - result.m2 = sz*sx + cz*cx*sy; - result.m3 = 0; - - result.m4 = cy*sz; - result.m5 = cz*cx + sz*sy*sx; - result.m6 = cx*sz*sy - cz*sx; - result.m7 = 0; - - result.m8 = -sy; - result.m9 = cy*sx; - result.m10 = cy*cx; - result.m11 = 0; - + 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; @@ -1269,7 +1485,7 @@ RMAPI Matrix MatrixFrustum(double left, double right, double bottom, double top, } // Get perspective projection matrix -// NOTE: Angle should be provided in radians +// NOTE: Fovy angle must be provided in radians RMAPI Matrix MatrixPerspective(double fovy, double aspect, double near, double far) { Matrix result = { 0 }; @@ -1478,10 +1694,9 @@ RMAPI Quaternion QuaternionInvert(Quaternion q) { Quaternion result = q; - float length = sqrtf(q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w); - float lengthSq = length*length; + float lengthSq = q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w; - if (lengthSq != 0.0) + if (lengthSq != 0.0f) { float invLength = 1.0f/lengthSq; @@ -1515,12 +1730,10 @@ RMAPI Quaternion QuaternionScale(Quaternion q, float mul) { Quaternion result = { 0 }; - float qax = q.x, qay = q.y, qaz = q.z, qaw = q.w; - - result.x = qax*mul + qaw*mul + qay*mul - qaz*mul; - result.y = qay*mul + qaw*mul + qaz*mul - qax*mul; - result.z = qaz*mul + qaw*mul + qax*mul - qay*mul; - result.w = qaw*mul - qax*mul - qay*mul - qaz*mul; + result.x = q.x*mul; + result.y = q.y*mul; + result.z = q.z*mul; + result.w = q.w*mul; return result; } @@ -1584,14 +1797,14 @@ RMAPI Quaternion QuaternionSlerp(Quaternion q1, Quaternion q2, float amount) cosHalfTheta = -cosHalfTheta; } - if (fabs(cosHalfTheta) >= 1.0f) result = q1; + 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 (fabs(sinHalfTheta) < 0.001f) + if (fabsf(sinHalfTheta) < 0.001f) { result.x = (q1.x*0.5f + q2.x*0.5f); result.y = (q1.y*0.5f + q2.y*0.5f); @@ -1646,30 +1859,60 @@ RMAPI Quaternion QuaternionFromMatrix(Matrix mat) { Quaternion result = { 0 }; - if ((mat.m0 > mat.m5) && (mat.m0 > mat.m10)) - { - float s = sqrtf(1.0f + mat.m0 - mat.m5 - mat.m10)*2; + 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; - result.x = 0.25f*s; - result.y = (mat.m4 + mat.m1)/s; - result.z = (mat.m2 + mat.m8)/s; - result.w = (mat.m9 - mat.m6)/s; - } - else if (mat.m5 > mat.m10) + int biggestIndex = 0; + float fourBiggestSquaredMinus1 = fourWSquaredMinus1; + if (fourXSquaredMinus1 > fourBiggestSquaredMinus1) { - float s = sqrtf(1.0f + mat.m5 - mat.m0 - mat.m10)*2; - result.x = (mat.m4 + mat.m1)/s; - result.y = 0.25f*s; - result.z = (mat.m9 + mat.m6)/s; - result.w = (mat.m2 - mat.m8)/s; + fourBiggestSquaredMinus1 = fourXSquaredMinus1; + biggestIndex = 1; } - else + + if (fourYSquaredMinus1 > fourBiggestSquaredMinus1) { - float s = sqrtf(1.0f + mat.m10 - mat.m0 - mat.m5)*2; - result.x = (mat.m2 + mat.m8)/s; - result.y = (mat.m9 + mat.m6)/s; - result.z = 0.25f*s; - result.w = (mat.m4 - mat.m1)/s; + 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; @@ -1709,7 +1952,7 @@ RMAPI Matrix QuaternionToMatrix(Quaternion q) } // Get rotation quaternion for an angle and axis -// NOTE: angle must be provided in radians +// NOTE: Angle must be provided in radians RMAPI Quaternion QuaternionFromAxisAngle(Vector3 axis, float angle) { Quaternion result = { 0.0f, 0.0f, 0.0f, 1.0f }; @@ -1757,7 +2000,7 @@ RMAPI Quaternion QuaternionFromAxisAngle(Vector3 axis, float angle) // Get the rotation angle and axis for a given quaternion RMAPI void QuaternionToAxisAngle(Quaternion q, Vector3 *outAxis, float *outAngle) { - if (fabs(q.w) > 1.0f) + 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); @@ -1850,4 +2093,19 @@ RMAPI Quaternion QuaternionTransform(Quaternion q, Matrix mat) return result; } +// Check whether two given quaternions are almost equal +RMAPI int QuaternionEquals(Quaternion p, Quaternion q) +{ + 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; +} + #endif // RAYMATH_H diff --git a/raylib/rcamera.h b/raylib/rcamera.h index 2a86e36..37ea13a 100644 --- a/raylib/rcamera.h +++ b/raylib/rcamera.h @@ -22,7 +22,7 @@ * * LICENSE: zlib/libpng * -* Copyright (c) 2015-2021 Ramon Santamaria (@raysan5) +* Copyright (c) 2015-2022 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. @@ -103,7 +103,7 @@ // Module Functions Declaration //---------------------------------------------------------------------------------- -#ifdef __cplusplus +#if defined(__cplusplus) extern "C" { // Prevents name mangling of functions #endif @@ -119,7 +119,7 @@ void SetCameraMoveControls(int keyFront, int keyBack, int keyUp, int keyDown); // Set camera move controls (1st person and 3rd person cameras) #endif -#ifdef __cplusplus +#if defined(__cplusplus) } #endif @@ -150,7 +150,7 @@ void SetCameraMoveControls(int keyFront, int keyBack, #endif // Camera mouse movement sensitivity -#define CAMERA_MOUSE_MOVE_SENSITIVITY 0.003f +#define CAMERA_MOUSE_MOVE_SENSITIVITY 0.5f // TODO: it should be independant of framerate #define CAMERA_MOUSE_SCROLL_SENSITIVITY 1.5f // FREE_CAMERA @@ -163,7 +163,7 @@ void SetCameraMoveControls(int keyFront, int keyBack, #define CAMERA_FREE_PANNING_DIVIDER 5.1f // ORBITAL_CAMERA -#define CAMERA_ORBITAL_SPEED 0.01f // Radians per frame +#define CAMERA_ORBITAL_SPEED 0.5f // Radians per second // FIRST_PERSON //#define CAMERA_FIRST_PERSON_MOUSE_SENSITIVITY 0.003f @@ -171,9 +171,11 @@ void SetCameraMoveControls(int keyFront, int keyBack, #define CAMERA_FIRST_PERSON_MIN_CLAMP 89.0f #define CAMERA_FIRST_PERSON_MAX_CLAMP -89.0f -#define CAMERA_FIRST_PERSON_STEP_TRIGONOMETRIC_DIVIDER 8.0f -#define CAMERA_FIRST_PERSON_STEP_DIVIDER 30.0f -#define CAMERA_FIRST_PERSON_WAVING_DIVIDER 200.0f +// When walking, y-position of the player moves up-down at step frequency (swinging) but +// also the body slightly tilts left-right on every step, when all the body weight is left over one foot (tilting) +#define CAMERA_FIRST_PERSON_STEP_FREQUENCY 1.8f // Step frequency when walking (steps per second) +#define CAMERA_FIRST_PERSON_SWINGING_DELTA 0.03f // Maximum up-down swinging distance when walking +#define CAMERA_FIRST_PERSON_TILTING_DELTA 0.005f // Maximum left-right tilting distance when walking // THIRD_PERSON //#define CAMERA_THIRD_PERSON_MOUSE_SENSITIVITY 0.003f @@ -183,7 +185,7 @@ void SetCameraMoveControls(int keyFront, int keyBack, #define CAMERA_THIRD_PERSON_OFFSET (Vector3){ 0.4f, 0.0f, 0.0f } // PLAYER (used by camera) -#define PLAYER_MOVEMENT_SENSITIVITY 20.0f +#define PLAYER_MOVEMENT_SENSITIVITY 2.0f //---------------------------------------------------------------------------------- // Types and Structures Definition @@ -204,7 +206,6 @@ typedef struct { float targetDistance; // Camera distance from position to target float playerEyesPosition; // Player eyes position from ground (in meters) Vector2 angle; // Camera angle in plane XZ - Vector2 previousMousePosition; // Previous mouse position // Camera movement control keys int moveControl[6]; // Move controls (CAMERA_FIRST_PERSON) @@ -221,7 +222,6 @@ static CameraData CAMERA = { // Global CAMERA state context .targetDistance = 0, .playerEyesPosition = 1.85f, .angle = { 0 }, - .previousMousePosition = { 0 }, .moveControl = { 'W', 'S', 'D', 'A', 'E', 'Q' }, .smoothZoomControl = 341, // raylib: KEY_LEFT_CONTROL .altControl = 342, // raylib: KEY_LEFT_ALT @@ -265,8 +265,6 @@ void SetCameraMode(Camera camera, int mode) CAMERA.playerEyesPosition = camera.position.y; // Init player eyes position to camera Y position - CAMERA.previousMousePosition = GetMousePosition(); // Init mouse position - // Lock cursor for first person and third person cameras if ((mode == CAMERA_FIRST_PERSON) || (mode == CAMERA_THIRD_PERSON)) DisableCursor(); else EnableCursor(); @@ -281,13 +279,12 @@ void SetCameraMode(Camera camera, int mode) // Keys: IsKeyDown() void UpdateCamera(Camera *camera) { - static int swingCounter = 0; // Used for 1st person swinging movement + static float swingCounter = 0.0f; // Used for 1st person swinging movement // TODO: Compute CAMERA.targetDistance and CAMERA.angle here (?) // Mouse movement detection - Vector2 mousePositionDelta = { 0.0f, 0.0f }; - Vector2 mousePosition = GetMousePosition(); + Vector2 mousePositionDelta = GetMouseDelta(); float mouseWheelMove = GetMouseWheelMove(); // Keys input detection @@ -302,14 +299,6 @@ void UpdateCamera(Camera *camera) IsKeyDown(CAMERA.moveControl[MOVE_UP]), IsKeyDown(CAMERA.moveControl[MOVE_DOWN]) }; - if (CAMERA.mode != CAMERA_CUSTOM) - { - mousePositionDelta.x = mousePosition.x - CAMERA.previousMousePosition.x; - mousePositionDelta.y = mousePosition.y - CAMERA.previousMousePosition.y; - - CAMERA.previousMousePosition = mousePosition; - } - // Support for multiple automatic camera modes // NOTE: In case of CAMERA_CUSTOM nothing happens here, user must update it manually switch (CAMERA.mode) @@ -388,9 +377,9 @@ void UpdateCamera(Camera *camera) else { // Camera panning - camera->target.x += ((mousePositionDelta.x*CAMERA_FREE_MOUSE_SENSITIVITY)*cosf(CAMERA.angle.x) + (mousePositionDelta.y*CAMERA_FREE_MOUSE_SENSITIVITY)*sinf(CAMERA.angle.x)*sinf(CAMERA.angle.y))*(CAMERA.targetDistance/CAMERA_FREE_PANNING_DIVIDER); + camera->target.x += ((mousePositionDelta.x*CAMERA_FREE_MOUSE_SENSITIVITY)*cosf(CAMERA.angle.x) + (mousePositionDelta.y*-CAMERA_FREE_MOUSE_SENSITIVITY)*sinf(CAMERA.angle.x)*sinf(CAMERA.angle.y))*(CAMERA.targetDistance/CAMERA_FREE_PANNING_DIVIDER); camera->target.y += ((mousePositionDelta.y*CAMERA_FREE_MOUSE_SENSITIVITY)*cosf(CAMERA.angle.y))*(CAMERA.targetDistance/CAMERA_FREE_PANNING_DIVIDER); - camera->target.z += ((mousePositionDelta.x*-CAMERA_FREE_MOUSE_SENSITIVITY)*sinf(CAMERA.angle.x) + (mousePositionDelta.y*CAMERA_FREE_MOUSE_SENSITIVITY)*cosf(CAMERA.angle.x)*sinf(CAMERA.angle.y))*(CAMERA.targetDistance/CAMERA_FREE_PANNING_DIVIDER); + camera->target.z += ((mousePositionDelta.x*-CAMERA_FREE_MOUSE_SENSITIVITY)*sinf(CAMERA.angle.x) + (mousePositionDelta.y*-CAMERA_FREE_MOUSE_SENSITIVITY)*cosf(CAMERA.angle.x)*sinf(CAMERA.angle.y))*(CAMERA.targetDistance/CAMERA_FREE_PANNING_DIVIDER); } } @@ -402,7 +391,7 @@ void UpdateCamera(Camera *camera) } break; case CAMERA_ORBITAL: // Camera just orbits around target, only zoom allowed { - CAMERA.angle.x += CAMERA_ORBITAL_SPEED; // Camera orbit angle + CAMERA.angle.x += CAMERA_ORBITAL_SPEED*GetFrameTime(); // Camera orbit angle CAMERA.targetDistance -= (mouseWheelMove*CAMERA_MOUSE_SCROLL_SENSITIVITY); // Camera zoom // Camera distance clamp @@ -419,20 +408,20 @@ void UpdateCamera(Camera *camera) camera->position.x += (sinf(CAMERA.angle.x)*direction[MOVE_BACK] - sinf(CAMERA.angle.x)*direction[MOVE_FRONT] - cosf(CAMERA.angle.x)*direction[MOVE_LEFT] + - cosf(CAMERA.angle.x)*direction[MOVE_RIGHT])/PLAYER_MOVEMENT_SENSITIVITY; + cosf(CAMERA.angle.x)*direction[MOVE_RIGHT])*PLAYER_MOVEMENT_SENSITIVITY*GetFrameTime(); camera->position.y += (sinf(CAMERA.angle.y)*direction[MOVE_FRONT] - sinf(CAMERA.angle.y)*direction[MOVE_BACK] + - 1.0f*direction[MOVE_UP] - 1.0f*direction[MOVE_DOWN])/PLAYER_MOVEMENT_SENSITIVITY; + 1.0f*direction[MOVE_UP] - 1.0f*direction[MOVE_DOWN])*PLAYER_MOVEMENT_SENSITIVITY*GetFrameTime(); camera->position.z += (cosf(CAMERA.angle.x)*direction[MOVE_BACK] - cosf(CAMERA.angle.x)*direction[MOVE_FRONT] + sinf(CAMERA.angle.x)*direction[MOVE_LEFT] - - sinf(CAMERA.angle.x)*direction[MOVE_RIGHT])/PLAYER_MOVEMENT_SENSITIVITY; + sinf(CAMERA.angle.x)*direction[MOVE_RIGHT])*PLAYER_MOVEMENT_SENSITIVITY*GetFrameTime(); // Camera orientation calculation - CAMERA.angle.x += (mousePositionDelta.x*-CAMERA_MOUSE_MOVE_SENSITIVITY); - CAMERA.angle.y += (mousePositionDelta.y*-CAMERA_MOUSE_MOVE_SENSITIVITY); + CAMERA.angle.x -= mousePositionDelta.x*CAMERA_MOUSE_MOVE_SENSITIVITY*GetFrameTime(); + CAMERA.angle.y -= mousePositionDelta.y*CAMERA_MOUSE_MOVE_SENSITIVITY*GetFrameTime(); // Angle clamp if (CAMERA.angle.y > CAMERA_FIRST_PERSON_MIN_CLAMP*DEG2RAD) CAMERA.angle.y = CAMERA_FIRST_PERSON_MIN_CLAMP*DEG2RAD; @@ -490,15 +479,17 @@ void UpdateCamera(Camera *camera) camera->target.y = camera->position.y - matTransform.m13; camera->target.z = camera->position.z - matTransform.m14; - // If movement detected (some key pressed), increase swinging - for (int i = 0; i < 6; i++) if (direction[i]) { swingCounter++; break; } - // Camera position update // NOTE: On CAMERA_FIRST_PERSON player Y-movement is limited to player 'eyes position' - camera->position.y = CAMERA.playerEyesPosition - sinf(swingCounter/CAMERA_FIRST_PERSON_STEP_TRIGONOMETRIC_DIVIDER)/CAMERA_FIRST_PERSON_STEP_DIVIDER; + camera->position.y = CAMERA.playerEyesPosition; - camera->up.x = sinf(swingCounter/(CAMERA_FIRST_PERSON_STEP_TRIGONOMETRIC_DIVIDER*2))/CAMERA_FIRST_PERSON_WAVING_DIVIDER; - camera->up.z = -sinf(swingCounter/(CAMERA_FIRST_PERSON_STEP_TRIGONOMETRIC_DIVIDER*2))/CAMERA_FIRST_PERSON_WAVING_DIVIDER; + // Camera swinging (y-movement), only when walking (some key pressed) + for (int i = 0; i < 6; i++) if (direction[i]) { swingCounter += GetFrameTime(); break; } + camera->position.y -= sinf(2*PI*CAMERA_FIRST_PERSON_STEP_FREQUENCY*swingCounter)*CAMERA_FIRST_PERSON_SWINGING_DELTA; + + // Camera waiving (xz-movement), only when walking (some key pressed) + camera->up.x = sinf(2*PI*CAMERA_FIRST_PERSON_STEP_FREQUENCY*swingCounter)*CAMERA_FIRST_PERSON_TILTING_DELTA; + camera->up.z = -sinf(2*PI*CAMERA_FIRST_PERSON_STEP_FREQUENCY*swingCounter)*CAMERA_FIRST_PERSON_TILTING_DELTA; } break; case CAMERA_THIRD_PERSON: // Camera moves as in a third-person game, following target at a distance, controls are configurable @@ -506,16 +497,16 @@ void UpdateCamera(Camera *camera) camera->position.x += (sinf(CAMERA.angle.x)*direction[MOVE_BACK] - sinf(CAMERA.angle.x)*direction[MOVE_FRONT] - cosf(CAMERA.angle.x)*direction[MOVE_LEFT] + - cosf(CAMERA.angle.x)*direction[MOVE_RIGHT])/PLAYER_MOVEMENT_SENSITIVITY; + cosf(CAMERA.angle.x)*direction[MOVE_RIGHT])*PLAYER_MOVEMENT_SENSITIVITY*GetFrameTime(); camera->position.y += (sinf(CAMERA.angle.y)*direction[MOVE_FRONT] - sinf(CAMERA.angle.y)*direction[MOVE_BACK] + - 1.0f*direction[MOVE_UP] - 1.0f*direction[MOVE_DOWN])/PLAYER_MOVEMENT_SENSITIVITY; + 1.0f*direction[MOVE_UP] - 1.0f*direction[MOVE_DOWN])*PLAYER_MOVEMENT_SENSITIVITY*GetFrameTime(); camera->position.z += (cosf(CAMERA.angle.x)*direction[MOVE_BACK] - cosf(CAMERA.angle.x)*direction[MOVE_FRONT] + sinf(CAMERA.angle.x)*direction[MOVE_LEFT] - - sinf(CAMERA.angle.x)*direction[MOVE_RIGHT])/PLAYER_MOVEMENT_SENSITIVITY; + sinf(CAMERA.angle.x)*direction[MOVE_RIGHT])*PLAYER_MOVEMENT_SENSITIVITY*GetFrameTime(); // Camera orientation calculation CAMERA.angle.x += (mousePositionDelta.x*-CAMERA_MOUSE_MOVE_SENSITIVITY); diff --git a/raylib/rcore.c b/raylib/rcore.c index 78876e4..de4a3ae 100644 --- a/raylib/rcore.c +++ b/raylib/rcore.c @@ -22,9 +22,11 @@ * Windowing and input system configured for Android device, app activity managed internally in this module. * NOTE: OpenGL ES 2.0 is required and graphic device is managed by EGL * -* #define PLATFORM_RPI +* #define PLATFORM_RPI (deprecated - RPI OS Buster only) * Windowing and input system configured for Raspberry Pi in native mode (no XWindow required), * graphic device is managed by EGL and inputs are processed is raw mode, reading from /dev/input/ +* WARNING: This platform is deprecated, since RPI OS Bullseye, the old Dispmanx libraries are not +* supported and you must be using PLATFORM_DRM * * #define PLATFORM_DRM * Windowing and input system configured for DRM native mode (RPI4 and other devices) @@ -55,9 +57,6 @@ * WARNING: Reconfiguring standard input could lead to undesired effects, like breaking other running processes or * blocking the device if not restored properly. Use with care. * -* #define SUPPORT_MOUSE_CURSOR_POINT -* Draw a mouse pointer on screen -* * #define SUPPORT_BUSY_WAIT_LOOP * Use busy wait loop for timing sync, if not defined, a high-resolution timer is setup and used * @@ -78,9 +77,6 @@ * provided by stb_image and stb_image_write libraries, so, those libraries must be enabled on textures module * for linkage * -* #define SUPPORT_DATA_STORAGE -* Support saving binary data automatically to a generated storage.data file. This file is managed internally -* * #define SUPPORT_EVENTS_AUTOMATION * Support automatic generated events, loading and recording of those events when required * @@ -93,7 +89,7 @@ * * LICENSE: zlib/libpng * -* Copyright (c) 2013-2021 Ramon Santamaria (@raysan5) +* Copyright (c) 2013-2022 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. @@ -160,21 +156,43 @@ #define _POSIX_C_SOURCE 199309L // Required for: CLOCK_MONOTONIC if compiled with c99 without gnu ext. #endif +// Platform specific defines to handle GetApplicationDirectory() +#if defined (PLATFORM_DESKTOP) + #if defined(_WIN32) + #ifndef MAX_PATH + #define MAX_PATH 1025 + #endif + __declspec(dllimport) unsigned long __stdcall GetModuleFileNameA(void *hModule, void *lpFilename, unsigned long nSize); + __declspec(dllimport) unsigned long __stdcall GetModuleFileNameW(void *hModule, void *lpFilename, unsigned long nSize); + __declspec(dllimport) int __stdcall WideCharToMultiByte(unsigned int cp, unsigned long flags, void *widestr, int cchwide, void *str, int cbmb, void *defchar, int *used_default); + #elif defined(__linux__) + #include + #elif defined(__APPLE__) + #include + #include + #endif // OSs +#endif // PLATFORM_DESKTOP + #include // Required for: srand(), rand(), atexit() #include // Required for: sprintf() [Used in OpenURL()] -#include // Required for: strrchr(), strcmp(), strlen() +#include // Required for: strrchr(), strcmp(), strlen(), memset() #include // Required for: time() [Used in InitTimer()] #include // Required for: tan() [Used in BeginMode3D()], atan2f() [Used in LoadVrStereoConfig()] -#include // Required for: stat() [Used in GetFileModTime()] +#define _CRT_INTERNAL_NONSTDC_NAMES 1 +#include // Required for: stat(), S_ISREG [Used in GetFileModTime(), IsFilePath()] + +#if !defined(S_ISREG) && defined(S_IFMT) && defined(S_IFREG) + #define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) +#endif #if defined(PLATFORM_DESKTOP) && defined(_WIN32) && (defined(_MSC_VER) || defined(__TINYC__)) #define DIRENT_MALLOC RL_MALLOC #define DIRENT_FREE RL_FREE - #include "external/dirent.h" // Required for: DIR, opendir(), closedir() [Used in GetDirectoryFiles()] + #include "external/dirent.h" // Required for: DIR, opendir(), closedir() [Used in LoadDirectoryFiles()] #else - #include // Required for: DIR, opendir(), closedir() [Used in GetDirectoryFiles()] + #include // Required for: DIR, opendir(), closedir() [Used in LoadDirectoryFiles()] #endif #if defined(_WIN32) @@ -196,8 +214,12 @@ // Support retrieving native window handlers #if defined(_WIN32) + typedef void *PVOID; + typedef PVOID HANDLE; + typedef HANDLE HWND; #define GLFW_EXPOSE_NATIVE_WIN32 - #include "GLFW/glfw3native.h" // WARNING: It requires customization to avoid windows.h inclusion! + #define GLFW_NATIVE_INCLUDE_NONE // To avoid some symbols re-definition in windows.h + #include "GLFW/glfw3native.h" #if defined(SUPPORT_WINMM_HIGHRES_TIMER) && !defined(SUPPORT_BUSY_WAIT_LOOP) // NOTE: Those functions require linking with winmm library @@ -205,7 +227,7 @@ unsigned int __stdcall timeEndPeriod(unsigned int uPeriod); #endif #endif - #if defined(__linux__) || defined(__FreeBSD__) + #if defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) #include // Required for: timespec, nanosleep(), select() - POSIX //#define GLFW_EXPOSE_NATIVE_X11 // WARNING: Exposing Xlib.h > X.h results in dup symbols for Font type @@ -219,12 +241,19 @@ //#define GLFW_EXPOSE_NATIVE_COCOA // WARNING: Fails due to type redefinition #include "GLFW/glfw3native.h" // Required for: glfwGetCocoaWindow() #endif + + // TODO: HACK: Added flag if not provided by GLFW when using external library + // Latest GLFW release (GLFW 3.3.8) does not implement this flag, it was added for 3.4.0-dev + #if !defined(GLFW_MOUSE_PASSTHROUGH) + #define GLFW_MOUSE_PASSTHROUGH 0x0002000D + #endif #endif #if defined(PLATFORM_ANDROID) //#include // Required for: Android sensors functions (accelerometer, gyroscope, light...) #include // Required for: AWINDOW_FLAG_FULLSCREEN definition and others #include // Required for: android_app struct and activity management + #include // Required for: JNIEnv and JavaVM [Used in OpenURL()] #include // Native platform windowing system interface //#include // OpenGL ES 2.0 library (not required in this module, only in rlgl) @@ -275,12 +304,11 @@ #define DEFAULT_EVDEV_PATH "/dev/input/" // Path to the linux input events #endif +#ifndef MAX_FILEPATH_CAPACITY + #define MAX_FILEPATH_CAPACITY 8192 // Maximum capacity for filepath +#endif #ifndef MAX_FILEPATH_LENGTH - #if defined(__linux__) - #define MAX_FILEPATH_LENGTH 4096 // Maximum length for filepaths (Linux PATH_MAX default value) - #else - #define MAX_FILEPATH_LENGTH 512 // Maximum length supported for filepaths - #endif + #define MAX_FILEPATH_LENGTH 4096 // Maximum length for filepaths (Linux PATH_MAX default value) #endif #ifndef MAX_KEYBOARD_KEYS @@ -308,12 +336,6 @@ #define MAX_CHAR_PRESSED_QUEUE 16 // Maximum number of characters in the char input queue #endif -#if defined(SUPPORT_DATA_STORAGE) - #ifndef STORAGE_DATA_FILE - #define STORAGE_DATA_FILE "storage.data" // Automatic storage filename - #endif -#endif - #ifndef MAX_DECOMPRESSION_SIZE #define MAX_DECOMPRESSION_SIZE 64 // Maximum size allocated for decompression in MB #endif @@ -376,6 +398,7 @@ typedef struct CoreData { bool fullscreen; // Check if fullscreen mode is enabled bool shouldClose; // Check if window set for closing bool resizedLastFrame; // Check if window has been resized last frame + bool eventWaiting; // Wait for events before ending frame Point position; // Window position on screen (required on fullscreen toggle) Size display; // Display width and height (monitor, device-screen, LCD, ...) @@ -385,8 +408,8 @@ typedef struct CoreData { Point renderOffset; // Offset from render area (must be divided by 2) Matrix screenScale; // Matrix to scale screen (framebuffer rendering) - char **dropFilesPath; // Store dropped files paths as strings - int dropFileCount; // Count dropped files strings + char **dropFilepaths; // Store dropped files paths pointers (provided by GLFW) + unsigned int dropFileCount; // Count dropped files strings } Window; #if defined(PLATFORM_ANDROID) @@ -437,8 +460,8 @@ typedef struct CoreData { char currentButtonState[MAX_MOUSE_BUTTONS]; // Registers current mouse button state char previousButtonState[MAX_MOUSE_BUTTONS]; // Registers previous mouse button state - float currentWheelMove; // Registers current mouse wheel variation - float previousWheelMove; // Registers previous mouse wheel variation + Vector2 currentWheelMove; // Registers current mouse wheel variation + Vector2 previousWheelMove; // Registers previous mouse wheel variation #if defined(PLATFORM_RPI) || defined(PLATFORM_DRM) // NOTE: currentButtonState[] can't be written directly due to multithreading, app could miss the update char currentButtonStateEvdev[MAX_MOUSE_BUTTONS]; // Holds the new mouse state for the next polling event to grab @@ -482,17 +505,16 @@ typedef struct CoreData { //---------------------------------------------------------------------------------- // Global Variables Definition //---------------------------------------------------------------------------------- -static CoreData CORE = { 0 }; // Global CORE state context +const char *raylibVersion = RAYLIB_VERSION; // raylib version symbol, it could be required for some bindings -static char **dirFilesPath = NULL; // Store directory files paths as strings -static int dirFileCount = 0; // Count directory files strings +static CoreData CORE = { 0 }; // Global CORE state context #if defined(SUPPORT_SCREEN_CAPTURE) static int screenshotCounter = 0; // Screenshots counter #endif #if defined(SUPPORT_GIF_RECORDING) -static int gifFrameCounter = 0; // GIF frames counter +static int gifFrameCounter = 0; // GIF frames counter static bool gifRecording = false; // GIF recording state static MsfGifState gifState = { 0 }; // MSGIF context state #endif @@ -510,7 +532,7 @@ typedef enum AutomationEventType { INPUT_MOUSE_BUTTON_UP, // param[0]: button INPUT_MOUSE_BUTTON_DOWN, // param[0]: button INPUT_MOUSE_POSITION, // param[0]: x, param[1]: y - INPUT_MOUSE_WHEEL_MOTION, // param[0]: delta + INPUT_MOUSE_WHEEL_MOTION, // param[0]: x delta, param[1]: y delta INPUT_GAMEPAD_CONNECT, // param[0]: gamepad INPUT_GAMEPAD_DISCONNECT, // param[0]: gamepad INPUT_GAMEPAD_BUTTON_UP, // param[0]: button @@ -588,7 +610,7 @@ static bool eventsRecording = false; // Record events //---------------------------------------------------------------------------------- // Other Modules Functions Declaration (required by core) //---------------------------------------------------------------------------------- -#if defined(SUPPORT_DEFAULT_FONT) +#if defined(SUPPORT_MODULE_RTEXT) && defined(SUPPORT_DEFAULT_FONT) extern void LoadFontDefault(void); // [Module: text] Loads default font on InitWindow() extern void UnloadFontDefault(void); // [Module: text] Unloads default font from GPU memory #endif @@ -601,6 +623,9 @@ static bool InitGraphicsDevice(int width, int height); // Initialize graphics d static void SetupFramebuffer(int width, int height); // Setup main framebuffer static void SetupViewport(int width, int height); // Set viewport for a provided width and height +static void ScanDirectoryFiles(const char *basePath, FilePathList *list, const char *filter); // Scan all files and directories in a base path +static void ScanDirectoryFilesRecursively(const char *basePath, FilePathList *list, const char *filter); // Scan all files and directories recursively from a base path + #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) static void ErrorCallback(int error, const char *description); // GLFW3 Error Callback, runs on GLFW3 error // Window callbacks events @@ -626,11 +651,13 @@ static int32_t AndroidInputCallback(struct android_app *app, AInputEvent *event) #endif #if defined(PLATFORM_WEB) +static EM_BOOL EmscriptenFullscreenChangeCallback(int eventType, const EmscriptenFullscreenChangeEvent *event, void *userData); +static EM_BOOL EmscriptenWindowResizedCallback(int eventType, const EmscriptenUiEvent *event, void *userData); +static EM_BOOL EmscriptenResizeCallback(int eventType, const EmscriptenUiEvent *event, void *userData); + static EM_BOOL EmscriptenMouseCallback(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData); static EM_BOOL EmscriptenTouchCallback(int eventType, const EmscriptenTouchEvent *touchEvent, void *userData); static EM_BOOL EmscriptenGamepadCallback(int eventType, const EmscriptenGamepadEvent *gamepadEvent, void *userData); -static EM_BOOL EmscriptenResizeCallback(int eventType, const EmscriptenUiEvent *e, void *userData); - #endif #if defined(PLATFORM_RPI) || defined(PLATFORM_DRM) @@ -668,6 +695,10 @@ static void PlayAutomationEvent(unsigned int frame); // Play frame events void __stdcall Sleep(unsigned long msTimeout); // Required for: WaitTime() #endif +#if !defined(SUPPORT_MODULE_RTEXT) +const char *TextFormat(const char *text, ...); // Formatting of text with variables to 'embed' +#endif // !SUPPORT_MODULE_RTEXT + //---------------------------------------------------------------------------------- // Module Functions Definition - Window and OpenGL Context Functions //---------------------------------------------------------------------------------- @@ -700,13 +731,46 @@ void InitWindow(int width, int height, const char *title) { TRACELOG(LOG_INFO, "Initializing raylib %s", RAYLIB_VERSION); + TRACELOG(LOG_INFO, "Supported raylib modules:"); + TRACELOG(LOG_INFO, " > rcore:..... loaded (mandatory)"); + TRACELOG(LOG_INFO, " > rlgl:...... loaded (mandatory)"); +#if defined(SUPPORT_MODULE_RSHAPES) + TRACELOG(LOG_INFO, " > rshapes:... loaded (optional)"); +#else + TRACELOG(LOG_INFO, " > rshapes:... not loaded (optional)"); +#endif +#if defined(SUPPORT_MODULE_RTEXTURES) + TRACELOG(LOG_INFO, " > rtextures:. loaded (optional)"); +#else + TRACELOG(LOG_INFO, " > rtextures:. not loaded (optional)"); +#endif +#if defined(SUPPORT_MODULE_RTEXT) + TRACELOG(LOG_INFO, " > rtext:..... loaded (optional)"); +#else + TRACELOG(LOG_INFO, " > rtext:..... not loaded (optional)"); +#endif +#if defined(SUPPORT_MODULE_RMODELS) + TRACELOG(LOG_INFO, " > rmodels:... loaded (optional)"); +#else + TRACELOG(LOG_INFO, " > rmodels:... not loaded (optional)"); +#endif +#if defined(SUPPORT_MODULE_RAUDIO) + TRACELOG(LOG_INFO, " > raudio:.... loaded (optional)"); +#else + TRACELOG(LOG_INFO, " > raudio:.... not loaded (optional)"); +#endif + if ((title != NULL) && (title[0] != 0)) CORE.Window.title = title; - // Initialize required global values different than 0 + // Initialize global input state + memset(&CORE.Input, 0, sizeof(CORE.Input)); CORE.Input.Keyboard.exitKey = KEY_ESCAPE; CORE.Input.Mouse.scale = (Vector2){ 1.0f, 1.0f }; CORE.Input.Mouse.cursor = MOUSE_CURSOR_ARROW; CORE.Input.Gamepad.lastButtonPressed = -1; +#if defined(SUPPORT_EVENTS_WAITING) + CORE.Window.eventWaiting = true; +#endif #if defined(PLATFORM_ANDROID) CORE.Window.screen.width = width; @@ -793,24 +857,30 @@ void InitWindow(int width, int height, const char *title) // Initialize base path for storage CORE.Storage.basePath = GetWorkingDirectory(); -#if defined(SUPPORT_DEFAULT_FONT) +#if defined(SUPPORT_MODULE_RTEXT) && defined(SUPPORT_DEFAULT_FONT) // Load default font - // NOTE: External functions (defined in module: text) + // WARNING: External function: Module required: rtext LoadFontDefault(); + #if defined(SUPPORT_MODULE_RSHAPES) Rectangle rec = GetFontDefault().recs[95]; // NOTE: We setup a 1px padding on char rectangle to avoid pixel bleeding on MSAA filtering - SetShapesTexture(GetFontDefault().texture, (Rectangle){ rec.x + 1, rec.y + 1, rec.width - 2, rec.height - 2 }); + SetShapesTexture(GetFontDefault().texture, (Rectangle){ rec.x + 1, rec.y + 1, rec.width - 2, rec.height - 2 }); // WARNING: Module required: rshapes + #endif #else + #if defined(SUPPORT_MODULE_RSHAPES) // Set default texture and rectangle to be used for shapes drawing // NOTE: rlgl default texture is a 1x1 pixel UNCOMPRESSED_R8G8B8A8 Texture2D texture = { rlGetTextureIdDefault(), 1, 1, 1, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8 }; - SetShapesTexture(texture, (Rectangle){ 0.0f, 0.0f, 1.0f, 1.0f }); + SetShapesTexture(texture, (Rectangle){ 0.0f, 0.0f, 1.0f, 1.0f }); // WARNING: Module required: rshapes + #endif #endif -#if defined(PLATFORM_DESKTOP) +#if defined(SUPPORT_MODULE_RTEXT) && defined(SUPPORT_DEFAULT_FONT) if ((CORE.Window.flags & FLAG_WINDOW_HIGHDPI) > 0) { // Set default font texture filter for HighDPI (blurry) - SetTextureFilter(GetFontDefault().texture, TEXTURE_FILTER_BILINEAR); + // RL_TEXTURE_FILTER_LINEAR - tex filter: BILINEAR, no mipmaps + rlTextureParameters(GetFontDefault().texture.id, RL_TEXTURE_MIN_FILTER, RL_TEXTURE_FILTER_LINEAR); + rlTextureParameters(GetFontDefault().texture.id, RL_TEXTURE_MAG_FILTER, RL_TEXTURE_FILTER_LINEAR); } #endif @@ -822,13 +892,18 @@ void InitWindow(int width, int height, const char *title) #endif #if defined(PLATFORM_WEB) + // Setup callback funtions for the DOM events + emscripten_set_fullscreenchange_callback("#canvas", NULL, 1, EmscriptenFullscreenChangeCallback); + + // WARNING: Below resize code was breaking fullscreen mode for sample games and examples, it needs review // Check fullscreen change events(note this is done on the window since most browsers don't support this on #canvas) //emscripten_set_fullscreenchange_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 1, EmscriptenResizeCallback); // Check Resize event (note this is done on the window since most browsers don't support this on #canvas) - emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 1, EmscriptenResizeCallback); + //emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 1, EmscriptenResizeCallback); // Trigger this once to get initial window sizing - EmscriptenResizeCallback(EMSCRIPTEN_EVENT_RESIZE, NULL, NULL); - // Support keyboard events + //EmscriptenResizeCallback(EMSCRIPTEN_EVENT_RESIZE, NULL, NULL); + + // Support keyboard events -> Not used, GLFW.JS takes care of that //emscripten_set_keypress_callback("#canvas", NULL, 1, EmscriptenKeyboardCallback); //emscripten_set_keydown_callback("#canvas", NULL, 1, EmscriptenKeyboardCallback); @@ -869,8 +944,8 @@ void CloseWindow(void) } #endif -#if defined(SUPPORT_DEFAULT_FONT) - UnloadFontDefault(); +#if defined(SUPPORT_MODULE_RTEXT) && defined(SUPPORT_DEFAULT_FONT) + UnloadFontDefault(); // WARNING: Module required: rtext #endif rlglClose(); // De-init rlgl @@ -1111,39 +1186,38 @@ bool IsWindowState(unsigned int flag) void ToggleFullscreen(void) { #if defined(PLATFORM_DESKTOP) - // NOTE: glfwSetWindowMonitor() doesn't work properly (bugs) if (!CORE.Window.fullscreen) { // Store previous window position (in case we exit fullscreen) glfwGetWindowPos(CORE.Window.handle, &CORE.Window.position.x, &CORE.Window.position.y); int monitorCount = 0; - GLFWmonitor** monitors = glfwGetMonitors(&monitorCount); - int monitorIndex = GetCurrentMonitor(); + GLFWmonitor **monitors = glfwGetMonitors(&monitorCount); // Use current monitor, so we correctly get the display the window is on - GLFWmonitor* monitor = monitorIndex < monitorCount ? monitors[monitorIndex] : NULL; + GLFWmonitor *monitor = (monitorIndex < monitorCount)? monitors[monitorIndex] : NULL; - if (!monitor) + if (monitor == NULL) { TRACELOG(LOG_WARNING, "GLFW: Failed to get monitor"); - CORE.Window.fullscreen = false; // Toggle fullscreen flag + CORE.Window.fullscreen = false; CORE.Window.flags &= ~FLAG_FULLSCREEN_MODE; glfwSetWindowMonitor(CORE.Window.handle, NULL, 0, 0, CORE.Window.screen.width, CORE.Window.screen.height, GLFW_DONT_CARE); - return; } + else + { + CORE.Window.fullscreen = true; + CORE.Window.flags |= FLAG_FULLSCREEN_MODE; - CORE.Window.fullscreen = true; // Toggle fullscreen flag - CORE.Window.flags |= FLAG_FULLSCREEN_MODE; - - glfwSetWindowMonitor(CORE.Window.handle, monitor, 0, 0, CORE.Window.screen.width, CORE.Window.screen.height, GLFW_DONT_CARE); + glfwSetWindowMonitor(CORE.Window.handle, monitor, 0, 0, CORE.Window.screen.width, CORE.Window.screen.height, GLFW_DONT_CARE); + } } else { - CORE.Window.fullscreen = false; // Toggle fullscreen flag + CORE.Window.fullscreen = false; CORE.Window.flags &= ~FLAG_FULLSCREEN_MODE; glfwSetWindowMonitor(CORE.Window.handle, NULL, CORE.Window.position.x, CORE.Window.position.y, CORE.Window.screen.width, CORE.Window.screen.height, GLFW_DONT_CARE); @@ -1154,6 +1228,7 @@ void ToggleFullscreen(void) if (CORE.Window.flags & FLAG_VSYNC_HINT) glfwSwapInterval(1); #endif #if defined(PLATFORM_WEB) +/* EM_ASM ( // This strategy works well while using raylib minimal web shell for emscripten, @@ -1161,14 +1236,17 @@ void ToggleFullscreen(void) // is a good strategy but maybe games prefer to keep current canvas resolution and // display it in fullscreen, adjusting monitor resolution if possible if (document.fullscreenElement) document.exitFullscreen(); - else Module.requestFullscreen(false, true); + else Module.requestFullscreen(true, true); //false, true); ); - +*/ + //EM_ASM(Module.requestFullscreen(false, false);); /* if (!CORE.Window.fullscreen) { // Option 1: Request fullscreen for the canvas element - // This option does not seem to work at all + // This option does not seem to work at all: + // emscripten_request_pointerlock() and emscripten_request_fullscreen() are affected by web security, + // the user must click once on the canvas to hide the pointer or transition to full screen //emscripten_request_fullscreen("#canvas", false); // Option 2: Request fullscreen for the canvas element with strategy @@ -1197,20 +1275,25 @@ void ToggleFullscreen(void) int width, height; emscripten_get_canvas_element_size("#canvas", &width, &height); TRACELOG(LOG_WARNING, "Emscripten: Enter fullscreen: Canvas size: %i x %i", width, height); + + CORE.Window.fullscreen = true; // Toggle fullscreen flag + CORE.Window.flags |= FLAG_FULLSCREEN_MODE; } else { //emscripten_exit_fullscreen(); - emscripten_exit_soft_fullscreen(); + //emscripten_exit_soft_fullscreen(); int width, height; emscripten_get_canvas_element_size("#canvas", &width, &height); TRACELOG(LOG_WARNING, "Emscripten: Exit fullscreen: Canvas size: %i x %i", width, height); + + CORE.Window.fullscreen = false; // Toggle fullscreen flag + CORE.Window.flags &= ~FLAG_FULLSCREEN_MODE; } */ CORE.Window.fullscreen = !CORE.Window.fullscreen; // Toggle fullscreen flag - CORE.Window.flags ^= FLAG_FULLSCREEN_MODE; #endif #if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM) TRACELOG(LOG_WARNING, "SYSTEM: Failed to toggle to windowed mode"); @@ -1341,6 +1424,13 @@ void SetWindowState(unsigned int flags) TRACELOG(LOG_WARNING, "WINDOW: High DPI can only by configured before window initialization"); } + // State change: FLAG_WINDOW_MOUSE_PASSTHROUGH + if (((CORE.Window.flags & FLAG_WINDOW_MOUSE_PASSTHROUGH) != (flags & FLAG_WINDOW_MOUSE_PASSTHROUGH)) && ((flags & FLAG_WINDOW_MOUSE_PASSTHROUGH) > 0)) + { + glfwSetWindowAttrib(CORE.Window.handle, GLFW_MOUSE_PASSTHROUGH, GLFW_TRUE); + CORE.Window.flags |= FLAG_WINDOW_MOUSE_PASSTHROUGH; + } + // State change: FLAG_MSAA_4X_HINT if (((CORE.Window.flags & FLAG_MSAA_4X_HINT) != (flags & FLAG_MSAA_4X_HINT)) && ((flags & FLAG_MSAA_4X_HINT) > 0)) { @@ -1442,6 +1532,13 @@ void ClearWindowState(unsigned int flags) TRACELOG(LOG_WARNING, "WINDOW: High DPI can only by configured before window initialization"); } + // State change: FLAG_WINDOW_MOUSE_PASSTHROUGH + if (((CORE.Window.flags & FLAG_WINDOW_MOUSE_PASSTHROUGH) > 0) && ((flags & FLAG_WINDOW_MOUSE_PASSTHROUGH) > 0)) + { + glfwSetWindowAttrib(CORE.Window.handle, GLFW_MOUSE_PASSTHROUGH, GLFW_FALSE); + CORE.Window.flags &= ~FLAG_WINDOW_MOUSE_PASSTHROUGH; + } + // State change: FLAG_MSAA_4X_HINT if (((CORE.Window.flags & FLAG_MSAA_4X_HINT) > 0) && ((flags & FLAG_MSAA_4X_HINT) > 0)) { @@ -1527,12 +1624,15 @@ void SetWindowSize(int width, int height) #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) glfwSetWindowSize(CORE.Window.handle, width, height); #endif -#if defined(PLATFORM_WEB) - //emscripten_set_canvas_size(width, height); // DEPRECATED! +} - // TODO: Below functions should be used to replace previous one but they do not seem to work properly - //emscripten_set_canvas_element_size("canvas", width, height); - //emscripten_set_element_css_size("canvas", width, height); +// Set window opacity, value opacity is between 0.0 and 1.0 +void SetWindowOpacity(float opacity) +{ +#if defined(PLATFORM_DESKTOP) + if (opacity >= 1.0f) opacity = 1.0f; + else if (opacity <= 0.0f) opacity = 0.0f; + glfwSetWindowOpacity(CORE.Window.handle, opacity); #endif } @@ -1642,7 +1742,7 @@ int GetCurrentMonitor(void) #endif } -// Get selected monitor width +// Get selected monitor position Vector2 GetMonitorPosition(int monitor) { #if defined(PLATFORM_DESKTOP) @@ -1661,7 +1761,7 @@ Vector2 GetMonitorPosition(int monitor) return (Vector2){ 0, 0 }; } -// Get selected monitor width (max available by monitor) +// Get selected monitor width (currently used by monitor) int GetMonitorWidth(int monitor) { #if defined(PLATFORM_DESKTOP) @@ -1670,11 +1770,9 @@ int GetMonitorWidth(int monitor) if ((monitor >= 0) && (monitor < monitorCount)) { - int count = 0; - const GLFWvidmode *modes = glfwGetVideoModes(monitors[monitor], &count); + const GLFWvidmode *mode = glfwGetVideoMode(monitors[monitor]); - // We return the maximum resolution available, the last one in the modes array - if (count > 0) return modes[count - 1].width; + if (mode) return mode->width; else TRACELOG(LOG_WARNING, "GLFW: Failed to find video mode for selected monitor"); } else TRACELOG(LOG_WARNING, "GLFW: Failed to find selected monitor"); @@ -1682,7 +1780,7 @@ int GetMonitorWidth(int monitor) return 0; } -// Get selected monitor width (max available by monitor) +// Get selected monitor height (currently used by monitor) int GetMonitorHeight(int monitor) { #if defined(PLATFORM_DESKTOP) @@ -1691,11 +1789,9 @@ int GetMonitorHeight(int monitor) if ((monitor >= 0) && (monitor < monitorCount)) { - int count = 0; - const GLFWvidmode *modes = glfwGetVideoModes(monitors[monitor], &count); + const GLFWvidmode *mode = glfwGetVideoMode(monitors[monitor]); - // We return the maximum resolution available, the last one in the modes array - if (count > 0) return modes[count - 1].height; + if (mode) return mode->height; else TRACELOG(LOG_WARNING, "GLFW: Failed to find video mode for selected monitor"); } else TRACELOG(LOG_WARNING, "GLFW: Failed to find selected monitor"); @@ -1822,25 +1918,44 @@ const char *GetMonitorName(int monitor) return ""; } -// Get clipboard text content -// NOTE: returned string is allocated and freed by GLFW -const char *GetClipboardText(void) -{ -#if defined(PLATFORM_DESKTOP) - return glfwGetClipboardString(CORE.Window.handle); -#else - return NULL; -#endif -} - // Set clipboard text content void SetClipboardText(const char *text) { #if defined(PLATFORM_DESKTOP) glfwSetClipboardString(CORE.Window.handle, text); #endif +#if defined(PLATFORM_WEB) + emscripten_run_script(TextFormat("navigator.clipboard.writeText('%s')", text)); +#endif } +// Enable waiting for events on EndDrawing(), no automatic event polling +void EnableEventWaiting(void) +{ + CORE.Window.eventWaiting = true; +} + +// Disable waiting for events on EndDrawing(), automatic events polling +RLAPI void DisableEventWaiting(void) +{ + CORE.Window.eventWaiting = false; +} + +// Get clipboard text content +// NOTE: returned string is allocated and freed by GLFW +const char *GetClipboardText(void) +{ +#if defined(PLATFORM_DESKTOP) + return glfwGetClipboardString(CORE.Window.handle); +#endif +#if defined(PLATFORM_WEB) + return emscripten_run_script_string("navigator.clipboard.readText()"); +#endif + return NULL; +} + + + // Show mouse cursor void ShowCursor(void) { @@ -1928,15 +2043,6 @@ void EndDrawing(void) { rlDrawRenderBatchActive(); // Update and draw internal render batch -#if defined(SUPPORT_MOUSE_CURSOR_POINT) - // Draw a small rectangle on mouse position for user reference - if (!CORE.Input.Mouse.cursorHidden) - { - DrawRectangle(CORE.Input.Mouse.currentPosition.x, CORE.Input.Mouse.currentPosition.y, 3, 3, MAROON); - rlDrawRenderBatchActive(); // Update and draw internal render batch - } -#endif - #if defined(SUPPORT_GIF_RECORDING) // Draw record indicator if (gifRecording) @@ -1949,17 +2055,20 @@ void EndDrawing(void) { // Get image data for the current frame (from backbuffer) // NOTE: This process is quite slow... :( - unsigned char *screenData = rlReadScreenPixels(CORE.Window.screen.width, CORE.Window.screen.height); - msf_gif_frame(&gifState, screenData, 10, 16, CORE.Window.screen.width*4); + Vector2 scale = GetWindowScaleDPI(); + unsigned char *screenData = rlReadScreenPixels((int)((float)CORE.Window.render.width*scale.x), (int)((float)CORE.Window.render.height*scale.y)); + msf_gif_frame(&gifState, screenData, 10, 16, (int)((float)CORE.Window.render.width*scale.x)*4); RL_FREE(screenData); // Free image data } + #if defined(SUPPORT_MODULE_RSHAPES) && defined(SUPPORT_MODULE_RTEXT) if (((gifFrameCounter/15)%2) == 1) { - DrawCircle(30, CORE.Window.screen.height - 20, 10, MAROON); - DrawText("GIF RECORDING", 50, CORE.Window.screen.height - 25, 10, RED); + DrawCircle(30, CORE.Window.screen.height - 20, 10, MAROON); // WARNING: Module required: rshapes + DrawText("GIF RECORDING", 50, CORE.Window.screen.height - 25, 10, RED); // WARNING: Module required: rtext } + #endif rlDrawRenderBatchActive(); // Update and draw internal render batch } @@ -2006,7 +2115,7 @@ void EndDrawing(void) // Wait for some milliseconds... if (CORE.Time.frame < CORE.Time.target) { - WaitTime((float)(CORE.Time.target - CORE.Time.frame)*1000.0f); + WaitTime(CORE.Time.target - CORE.Time.frame); CORE.Time.current = GetTime(); double waitTime = CORE.Time.current - CORE.Time.previous; @@ -2117,8 +2226,10 @@ void BeginTextureMode(RenderTexture2D target) rlEnableFramebuffer(target.id); // Enable render target - // Set viewport to framebuffer size + // Set viewport and RLGL internal framebuffer size rlViewport(0, 0, target.texture.width, target.texture.height); + rlSetFramebufferWidth(target.texture.width); + rlSetFramebufferHeight(target.texture.height); rlMatrixMode(RL_PROJECTION); // Switch to projection matrix rlLoadIdentity(); // Reset current matrix (projection) @@ -2186,16 +2297,20 @@ void BeginScissorMode(int x, int y, int width, int height) rlEnableScissorTest(); +#if defined(__APPLE__) + Vector2 scale = GetWindowScaleDPI(); + rlScissor((int)(x*scale.x), (int)(GetScreenHeight()*scale.y - (((y + height)*scale.y))), (int)(width*scale.x), (int)(height*scale.y)); +#else if ((CORE.Window.flags & FLAG_WINDOW_HIGHDPI) > 0) { Vector2 scale = GetWindowScaleDPI(); - rlScissor((int)(x*scale.x), (int)(CORE.Window.currentFbo.height - (y + height)*scale.y), (int)(width*scale.x), (int)(height*scale.y)); } else { rlScissor(x, CORE.Window.currentFbo.height - (y + height), width, height); } +#endif } // End scissor mode @@ -2260,8 +2375,8 @@ VrStereoConfig LoadVrStereoConfig(VrDeviceInfo device) // Fovy is normally computed with: 2*atan2f(device.vScreenSize, 2*device.eyeToScreenDistance) // ...but with lens distortion it is increased (see Oculus SDK Documentation) - //float fovy = 2.0f*atan2f(device.vScreenSize*0.5f*distortionScale, device.eyeToScreenDistance); // Really need distortionScale? - float fovy = 2.0f*(float)atan2f(device.vScreenSize*0.5f, device.eyeToScreenDistance); + float fovy = 2.0f*atan2f(device.vScreenSize*0.5f*distortionScale, device.eyeToScreenDistance); // Really need distortionScale? + // float fovy = 2.0f*(float)atan2f(device.vScreenSize*0.5f, device.eyeToScreenDistance); // Compute camera projection matrices float projOffset = 4.0f*lensShift; // Scaled to projection space coordinates [-1..1] @@ -2587,7 +2702,7 @@ void SetTargetFPS(int fps) if (fps < 1) CORE.Time.target = 0.0; else CORE.Time.target = 1.0/(double)fps; - TRACELOG(LOG_INFO, "TIMER: Target time per frame: %02.03f milliseconds", (float)CORE.Time.target*1000); + TRACELOG(LOG_INFO, "TIMER: Target time per frame: %02.03f milliseconds", (float)CORE.Time.target*1000.0f); } // Get current FPS @@ -2663,13 +2778,15 @@ void SetConfigFlags(unsigned int flags) // Takes a screenshot of current screen (saved a .png) void TakeScreenshot(const char *fileName) { - unsigned char *imgData = rlReadScreenPixels(CORE.Window.render.width, CORE.Window.render.height); - Image image = { imgData, CORE.Window.render.width, CORE.Window.render.height, 1, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8 }; +#if defined(SUPPORT_MODULE_RTEXTURES) + Vector2 scale = GetWindowScaleDPI(); + unsigned char *imgData = rlReadScreenPixels((int)((float)CORE.Window.render.width*scale.x), (int)((float)CORE.Window.render.height*scale.y)); + Image image = { imgData, (int)((float)CORE.Window.render.width*scale.x), (int)((float)CORE.Window.render.height*scale.y), 1, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8 }; - char path[512] = { 0 }; + char path[2048] = { 0 }; strcpy(path, TextFormat("%s/%s", CORE.Storage.basePath, fileName)); - ExportImage(image, path); + ExportImage(image, path); // WARNING: Module required: rtextures RL_FREE(imgData); #if defined(PLATFORM_WEB) @@ -2679,6 +2796,9 @@ void TakeScreenshot(const char *fileName) #endif TRACELOG(LOG_INFO, "SYSTEM: [%s] Screenshot taken successfully", path); +#else + TRACELOG(LOG_WARNING,"IMAGE: ExportImage() requires module: rtextures"); +#endif } // Get a random value between min and max (both included) @@ -2711,6 +2831,11 @@ bool FileExists(const char *fileName) if (access(fileName, F_OK) != -1) result = true; #endif + // NOTE: Alternatively, stat() can be used instead of access() + //#include + //struct stat statbuf; + //if (stat(filename, &statbuf) == 0) result = true; + return result; } @@ -2718,21 +2843,23 @@ bool FileExists(const char *fileName) // NOTE: Extensions checking is not case-sensitive bool IsFileExtension(const char *fileName, const char *ext) { + #define MAX_FILE_EXTENSION_SIZE 16 + bool result = false; const char *fileExt = GetFileExtension(fileName); if (fileExt != NULL) { -#if defined(SUPPORT_TEXT_MANIPULATION) +#if defined(SUPPORT_MODULE_RTEXT) && defined(SUPPORT_TEXT_MANIPULATION) int extCount = 0; - const char **checkExts = TextSplit(ext, ';', &extCount); + const char **checkExts = TextSplit(ext, ';', &extCount); // WARNING: Module required: rtext - char fileExtLower[16] = { 0 }; - strcpy(fileExtLower, TextToLower(fileExt)); + char fileExtLower[MAX_FILE_EXTENSION_SIZE + 1] = { 0 }; + strncpy(fileExtLower, TextToLower(fileExt),MAX_FILE_EXTENSION_SIZE); // WARNING: Module required: rtext for (int i = 0; i < extCount; i++) { - if (TextIsEqual(fileExtLower, TextToLower(checkExts[i]))) + if (strcmp(fileExtLower, TextToLower(checkExts[i])) == 0) { result = true; break; @@ -2761,6 +2888,24 @@ bool DirectoryExists(const char *dirPath) return result; } +// Get file length in bytes +// NOTE: GetFileSize() conflicts with windows.h +int GetFileLength(const char *fileName) +{ + int size = 0; + + FILE *file = fopen(fileName, "rb"); + + if (file != NULL) + { + fseek(file, 0L, SEEK_END); + size = (int)ftell(file); + fclose(file); + } + + return size; +} + // Get pointer to extension for a filename string (includes the dot: .png) const char *GetFileExtension(const char *fileName) { @@ -2793,7 +2938,7 @@ const char *GetFileName(const char *filePath) // Get filename string without extension (uses static string) const char *GetFileNameWithoutExt(const char *filePath) { - #define MAX_FILENAMEWITHOUTEXT_LENGTH 128 + #define MAX_FILENAMEWITHOUTEXT_LENGTH 256 static char fileName[MAX_FILENAMEWITHOUTEXT_LENGTH] = { 0 }; memset(fileName, 0, MAX_FILENAMEWITHOUTEXT_LENGTH); @@ -2896,52 +3041,148 @@ const char *GetWorkingDirectory(void) return path; } -// Get filenames in a directory path -// NOTE: Files count is returned by parameters pointer -char **GetDirectoryFiles(const char *dirPath, int *fileCount) +const char *GetApplicationDirectory(void) { - ClearDirectoryFiles(); + static char appDir[MAX_FILEPATH_LENGTH] = { 0 }; + memset(appDir, 0, MAX_FILEPATH_LENGTH); + +#if defined(_WIN32) + int len = 0; +#if defined (UNICODE) + unsigned short widePath[MAX_PATH]; + len = GetModuleFileNameW(NULL, widePath, MAX_PATH); + len = WideCharToMultiByte(0, 0, widePath, len, appDir, MAX_PATH, NULL, NULL); +#else + len = GetModuleFileNameA(NULL, appDir, MAX_PATH); +#endif + if (len > 0) + { + for (int i = len; i >= 0; --i) + { + if (appDir[i] == '\\') + { + appDir[i + 1] = '\0'; + break; + } + } + } + else + { + appDir[0] = '.'; + appDir[1] = '\\'; + } + +#elif defined(__linux__) + unsigned int size = sizeof(appDir); + ssize_t len = readlink("/proc/self/exe", appDir, size); + + if (len > 0) + { + for (int i = len; i >= 0; --i) + { + if (appDir[i] == '/') + { + appDir[i + 1] = '\0'; + break; + } + } + } + else + { + appDir[0] = '.'; + appDir[1] = '/'; + } +#elif defined(__APPLE__) + uint32_t size = sizeof(appDir); + + if (_NSGetExecutablePath(appDir, &size) == 0) + { + int len = strlen(appDir); + for (int i = len; i >= 0; --i) + { + if (appDir[i] == '/') + { + appDir[i + 1] = '\0'; + break; + } + } + } + else + { + appDir[0] = '.'; + appDir[1] = '/'; + } +#endif + + return appDir; +} + +// Load directory filepaths +// NOTE: Base path is prepended to the scanned filepaths +// WARNING: Directory is scanned twice, first time to get files count +// No recursive scanning is done! +FilePathList LoadDirectoryFiles(const char *dirPath) +{ + FilePathList files = { 0 }; + unsigned int fileCounter = 0; - int counter = 0; struct dirent *entity; DIR *dir = opendir(dirPath); if (dir != NULL) // It's a directory { - // Count files - while ((entity = readdir(dir)) != NULL) counter++; - - dirFileCount = counter; - *fileCount = dirFileCount; + // SCAN 1: Count files + while ((entity = readdir(dir)) != NULL) + { + // NOTE: We skip '.' (current dir) and '..' (parent dir) filepaths + if ((strcmp(entity->d_name, ".") != 0) && (strcmp(entity->d_name, "..") != 0)) fileCounter++; + } // Memory allocation for dirFileCount - dirFilesPath = (char **)RL_MALLOC(dirFileCount*sizeof(char *)); - for (int i = 0; i < dirFileCount; i++) dirFilesPath[i] = (char *)RL_MALLOC(MAX_FILEPATH_LENGTH*sizeof(char)); - - // Reset our position in the directory to the beginning - rewinddir(dir); - - // Read file names - for (int i = 0; (entity = readdir(dir)) != NULL; i++) strcpy(dirFilesPath[i], entity->d_name); + files.capacity = fileCounter; + files.paths = (char **)RL_MALLOC(files.capacity*sizeof(char *)); + for (unsigned int i = 0; i < files.capacity; i++) files.paths[i] = (char *)RL_MALLOC(MAX_FILEPATH_LENGTH*sizeof(char)); closedir(dir); + + // SCAN 2: Read filepaths + // NOTE: Directory paths are also registered + ScanDirectoryFiles(dirPath, &files, NULL); + + // Security check: read files.count should match fileCounter + if (files.count != files.capacity) TRACELOG(LOG_WARNING, "FILEIO: Read files count do not match capacity allocated"); } else TRACELOG(LOG_WARNING, "FILEIO: Failed to open requested directory"); // Maybe it's a file... - return dirFilesPath; + return files; } -// Clear directory files paths buffers -void ClearDirectoryFiles(void) +// Load directory filepaths with extension filtering and recursive directory scan +// NOTE: On recursive loading we do not pre-scan for file count, we use MAX_FILEPATH_CAPACITY +FilePathList LoadDirectoryFilesEx(const char *basePath, const char *filter, bool scanSubdirs) { - if (dirFileCount > 0) + FilePathList files = { 0 }; + + files.capacity = MAX_FILEPATH_CAPACITY; + files.paths = (char **)RL_CALLOC(files.capacity, sizeof(char *)); + for (unsigned int i = 0; i < files.capacity; i++) files.paths[i] = (char *)RL_CALLOC(MAX_FILEPATH_LENGTH, sizeof(char)); + + // WARNING: basePath is always prepended to scanned paths + if (scanSubdirs) ScanDirectoryFilesRecursively(basePath, &files, filter); + else ScanDirectoryFiles(basePath, &files, filter); + + return files; +} + +// Unload directory filepaths +void UnloadDirectoryFiles(FilePathList files) +{ + if (files.capacity > 0) { - for (int i = 0; i < dirFileCount; i++) RL_FREE(dirFilesPath[i]); + for (unsigned int i = 0; i < files.capacity; i++) RL_FREE(files.paths[i]); - RL_FREE(dirFilesPath); + RL_FREE(files.paths); } - - dirFileCount = 0; } // Change working directory, returns true on success @@ -2954,6 +3195,15 @@ bool ChangeDirectory(const char *dir) return (result == 0); } +// Check if a given path point to a file +bool IsPathFile(const char *path) +{ + struct stat pathStat = { 0 }; + stat(path, &pathStat); + + return S_ISREG(pathStat.st_mode); +} + // Check if a file has been dropped into window bool IsFileDropped(void) { @@ -2961,23 +3211,30 @@ bool IsFileDropped(void) else return false; } -// Get dropped files names -char **GetDroppedFiles(int *count) +// Load dropped filepaths +FilePathList LoadDroppedFiles(void) { - *count = CORE.Window.dropFileCount; - return CORE.Window.dropFilesPath; + FilePathList files = { 0 }; + + files.count = CORE.Window.dropFileCount; + files.paths = CORE.Window.dropFilepaths; + + return files; } -// Clear dropped files paths buffer -void ClearDroppedFiles(void) +// Unload dropped filepaths +void UnloadDroppedFiles(FilePathList files) { - if (CORE.Window.dropFileCount > 0) - { - for (int i = 0; i < CORE.Window.dropFileCount; i++) RL_FREE(CORE.Window.dropFilesPath[i]); + // WARNING: files pointers are the same as internal ones - RL_FREE(CORE.Window.dropFilesPath); + if (files.count > 0) + { + for (unsigned int i = 0; i < files.count; i++) RL_FREE(files.paths[i]); + + RL_FREE(files.paths); CORE.Window.dropFileCount = 0; + CORE.Window.dropFilepaths = NULL; } } @@ -2996,8 +3253,8 @@ long GetFileModTime(const char *fileName) return 0; } -// Compress data (DEFLATE algorythm) -unsigned char *CompressData(unsigned char *data, int dataLength, int *compDataLength) +// Compress data (DEFLATE algorithm) +unsigned char *CompressData(const unsigned char *data, int dataSize, int *compDataSize) { #define COMPRESSION_QUALITY_DEFLATE 8 @@ -3006,40 +3263,40 @@ unsigned char *CompressData(unsigned char *data, int dataLength, int *compDataLe #if defined(SUPPORT_COMPRESSION_API) // Compress data and generate a valid DEFLATE stream struct sdefl sdefl = { 0 }; - int bounds = sdefl_bound(dataLength); + int bounds = sdefl_bound(dataSize); compData = (unsigned char *)RL_CALLOC(bounds, 1); - *compDataLength = sdeflate(&sdefl, compData, data, dataLength, COMPRESSION_QUALITY_DEFLATE); // Compression level 8, same as stbwi + *compDataSize = sdeflate(&sdefl, compData, data, dataSize, COMPRESSION_QUALITY_DEFLATE); // Compression level 8, same as stbwi - TraceLog(LOG_INFO, "SYSTEM: Compress data: Original size: %i -> Comp. size: %i", dataLength, *compDataLength); + TraceLog(LOG_INFO, "SYSTEM: Compress data: Original size: %i -> Comp. size: %i", dataSize, *compDataSize); #endif return compData; } -// Decompress data (DEFLATE algorythm) -unsigned char *DecompressData(unsigned char *compData, int compDataLength, int *dataLength) +// Decompress data (DEFLATE algorithm) +unsigned char *DecompressData(const unsigned char *compData, int compDataSize, int *dataSize) { unsigned char *data = NULL; #if defined(SUPPORT_COMPRESSION_API) // Decompress data from a valid DEFLATE stream data = RL_CALLOC(MAX_DECOMPRESSION_SIZE*1024*1024, 1); - int length = sinflate(data, MAX_DECOMPRESSION_SIZE, compData, compDataLength); + int length = sinflate(data, MAX_DECOMPRESSION_SIZE*1024*1024, compData, compDataSize); unsigned char *temp = RL_REALLOC(data, length); if (temp != NULL) data = temp; else TRACELOG(LOG_WARNING, "SYSTEM: Failed to re-allocate required decompression memory"); - *dataLength = length; + *dataSize = length; - TraceLog(LOG_INFO, "SYSTEM: Decompress data: Comp. size: %i -> Original size: %i", compDataLength, *dataLength); + TraceLog(LOG_INFO, "SYSTEM: Decompress data: Comp. size: %i -> Original size: %i", compDataSize, *dataSize); #endif return data; } // Encode data to Base64 string -char *EncodeDataBase64(const unsigned char *data, int dataLength, int *outputLength) +char *EncodeDataBase64(const unsigned char *data, int dataSize, int *outputSize) { static const unsigned char base64encodeTable[] = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', @@ -3049,17 +3306,17 @@ char *EncodeDataBase64(const unsigned char *data, int dataLength, int *outputLen static const int modTable[] = { 0, 2, 1 }; - *outputLength = 4*((dataLength + 2)/3); + *outputSize = 4*((dataSize + 2)/3); - char *encodedData = RL_MALLOC(*outputLength); + char *encodedData = RL_MALLOC(*outputSize); if (encodedData == NULL) return NULL; - for (int i = 0, j = 0; i < dataLength;) + for (int i = 0, j = 0; i < dataSize;) { - unsigned int octetA = (i < dataLength)? (unsigned char)data[i++] : 0; - unsigned int octetB = (i < dataLength)? (unsigned char)data[i++] : 0; - unsigned int octetC = (i < dataLength)? (unsigned char)data[i++] : 0; + unsigned int octetA = (i < dataSize)? (unsigned char)data[i++] : 0; + unsigned int octetB = (i < dataSize)? (unsigned char)data[i++] : 0; + unsigned int octetC = (i < dataSize)? (unsigned char)data[i++] : 0; unsigned int triple = (octetA << 0x10) + (octetB << 0x08) + octetC; @@ -3069,13 +3326,13 @@ char *EncodeDataBase64(const unsigned char *data, int dataLength, int *outputLen encodedData[j++] = base64encodeTable[(triple >> 0*6) & 0x3F]; } - for (int i = 0; i < modTable[dataLength%3]; i++) encodedData[*outputLength - 1 - i] = '='; + for (int i = 0; i < modTable[dataSize%3]; i++) encodedData[*outputSize - 1 - i] = '='; // Padding character return encodedData; } // Decode Base64 string data -unsigned char *DecodeDataBase64(unsigned char *data, int *outputLength) +unsigned char *DecodeDataBase64(const unsigned char *data, int *outputSize) { static const unsigned char base64decodeTable[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, @@ -3085,21 +3342,21 @@ unsigned char *DecodeDataBase64(unsigned char *data, int *outputLength) }; // Get output size of Base64 input data - int outLength = 0; + int outSize = 0; for (int i = 0; data[4*i] != 0; i++) { if (data[4*i + 3] == '=') { - if (data[4*i + 2] == '=') outLength += 1; - else outLength += 2; + if (data[4*i + 2] == '=') outSize += 1; + else outSize += 2; } - else outLength += 3; + else outSize += 3; } // Allocate memory to store decoded Base64 data - unsigned char *decodedData = (unsigned char *)RL_MALLOC(outLength); + unsigned char *decodedData = (unsigned char *)RL_MALLOC(outSize); - for (int i = 0; i < outLength/3; i++) + for (int i = 0; i < outSize/3; i++) { unsigned char a = base64decodeTable[(int)data[4*i]]; unsigned char b = base64decodeTable[(int)data[4*i + 1]]; @@ -3111,131 +3368,27 @@ unsigned char *DecodeDataBase64(unsigned char *data, int *outputLength) decodedData[3*i + 2] = (c << 6) | d; } - if (outLength%3 == 1) + if (outSize%3 == 1) { - int n = outLength/3; + int n = outSize/3; unsigned char a = base64decodeTable[(int)data[4*n]]; unsigned char b = base64decodeTable[(int)data[4*n + 1]]; - decodedData[outLength - 1] = (a << 2) | (b >> 4); + decodedData[outSize - 1] = (a << 2) | (b >> 4); } - else if (outLength%3 == 2) + else if (outSize%3 == 2) { - int n = outLength/3; + int n = outSize/3; unsigned char a = base64decodeTable[(int)data[4*n]]; unsigned char b = base64decodeTable[(int)data[4*n + 1]]; unsigned char c = base64decodeTable[(int)data[4*n + 2]]; - decodedData[outLength - 2] = (a << 2) | (b >> 4); - decodedData[outLength - 1] = (b << 4) | (c >> 2); + decodedData[outSize - 2] = (a << 2) | (b >> 4); + decodedData[outSize - 1] = (b << 4) | (c >> 2); } - *outputLength = outLength; + *outputSize = outSize; return decodedData; } -// Save integer value to storage file (to defined position) -// NOTE: Storage positions is directly related to file memory layout (4 bytes each integer) -bool SaveStorageValue(unsigned int position, int value) -{ - bool success = false; - -#if defined(SUPPORT_DATA_STORAGE) - char path[512] = { 0 }; - strcpy(path, TextFormat("%s/%s", CORE.Storage.basePath, STORAGE_DATA_FILE)); - - unsigned int dataSize = 0; - unsigned int newDataSize = 0; - unsigned char *fileData = LoadFileData(path, &dataSize); - unsigned char *newFileData = NULL; - - if (fileData != NULL) - { - if (dataSize <= (position*sizeof(int))) - { - // Increase data size up to position and store value - newDataSize = (position + 1)*sizeof(int); - newFileData = (unsigned char *)RL_REALLOC(fileData, newDataSize); - - if (newFileData != NULL) - { - // RL_REALLOC succeded - int *dataPtr = (int *)newFileData; - dataPtr[position] = value; - } - else - { - // RL_REALLOC failed - TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to realloc data (%u), position in bytes (%u) bigger than actual file size", path, dataSize, position*sizeof(int)); - - // We store the old size of the file - newFileData = fileData; - newDataSize = dataSize; - } - } - else - { - // Store the old size of the file - newFileData = fileData; - newDataSize = dataSize; - - // Replace value on selected position - int *dataPtr = (int *)newFileData; - dataPtr[position] = value; - } - - success = SaveFileData(path, newFileData, newDataSize); - RL_FREE(newFileData); - - TRACELOG(LOG_INFO, "FILEIO: [%s] Saved storage value: %i", path, value); - } - else - { - TRACELOG(LOG_INFO, "FILEIO: [%s] File created successfully", path); - - dataSize = (position + 1)*sizeof(int); - fileData = (unsigned char *)RL_MALLOC(dataSize); - int *dataPtr = (int *)fileData; - dataPtr[position] = value; - - success = SaveFileData(path, fileData, dataSize); - UnloadFileData(fileData); - - TRACELOG(LOG_INFO, "FILEIO: [%s] Saved storage value: %i", path, value); - } -#endif - - return success; -} - -// Load integer value from storage file (from defined position) -// NOTE: If requested position could not be found, value 0 is returned -int LoadStorageValue(unsigned int position) -{ - int value = 0; - -#if defined(SUPPORT_DATA_STORAGE) - char path[512] = { 0 }; - strcpy(path, TextFormat("%s/%s", CORE.Storage.basePath, STORAGE_DATA_FILE)); - - unsigned int dataSize = 0; - unsigned char *fileData = LoadFileData(path, &dataSize); - - if (fileData != NULL) - { - if (dataSize < (position*4)) TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to find storage position: %i", path, position); - else - { - int *dataPtr = (int *)fileData; - value = dataPtr[position]; - } - - UnloadFileData(fileData); - - TRACELOG(LOG_INFO, "FILEIO: [%s] Loaded storage value: %i", path, value); - } -#endif - return value; -} - // Open URL with default system browser (if available) // NOTE: This function is only safe to use if you control the URL given. // A user could craft a malicious string performing another action. @@ -3254,19 +3407,43 @@ void OpenURL(const char *url) #if defined(PLATFORM_DESKTOP) char *cmd = (char *)RL_CALLOC(strlen(url) + 10, sizeof(char)); #if defined(_WIN32) - sprintf(cmd, "explorer %s", url); + sprintf(cmd, "explorer \"%s\"", url); #endif - #if defined(__linux__) || defined(__FreeBSD__) + #if defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) sprintf(cmd, "xdg-open '%s'", url); // Alternatives: firefox, x-www-browser #endif #if defined(__APPLE__) sprintf(cmd, "open '%s'", url); #endif - system(cmd); + int result = system(cmd); + if (result == -1) TRACELOG(LOG_WARNING, "OpenURL() child process could not be created"); RL_FREE(cmd); #endif #if defined(PLATFORM_WEB) emscripten_run_script(TextFormat("window.open('%s', '_blank')", url)); +#endif +#if defined(PLATFORM_ANDROID) + JNIEnv *env = NULL; + JavaVM *vm = CORE.Android.app->activity->vm; + (*vm)->AttachCurrentThread(vm, &env, NULL); + + jstring urlString = (*env)->NewStringUTF(env, url); + jclass uriClass = (*env)->FindClass(env, "android/net/Uri"); + jmethodID uriParse = (*env)->GetStaticMethodID(env, uriClass, "parse", "(Ljava/lang/String;)Landroid/net/Uri;"); + jobject uri = (*env)->CallStaticObjectMethod(env, uriClass, uriParse, urlString); + + jclass intentClass = (*env)->FindClass(env, "android/content/Intent"); + jfieldID actionViewId = (*env)->GetStaticFieldID(env, intentClass, "ACTION_VIEW", "Ljava/lang/String;"); + jobject actionView = (*env)->GetStaticObjectField(env, intentClass, actionViewId); + jmethodID newIntent = (*env)->GetMethodID(env, intentClass, "", "(Ljava/lang/String;Landroid/net/Uri;)V"); + jobject intent = (*env)->AllocObject(env, intentClass); + + (*env)->CallVoidMethod(env, intent, newIntent, actionView, uri); + jclass activityClass = (*env)->FindClass(env, "android/app/Activity"); + jmethodID startActivity = (*env)->GetMethodID(env, activityClass, "startActivity", "(Landroid/content/Intent;)V"); + (*env)->CallVoidMethod(env, CORE.Android.app->activity->clazz, startActivity, intent); + + (*vm)->DetachCurrentThread(vm); #endif } } @@ -3593,14 +3770,24 @@ void SetMouseScale(float scaleX, float scaleY) // Get mouse wheel movement Y float GetMouseWheelMove(void) { -#if defined(PLATFORM_ANDROID) - return 0.0f; -#endif -#if defined(PLATFORM_WEB) - return CORE.Input.Mouse.previousWheelMove/100.0f; + float result = 0.0f; + +#if !defined(PLATFORM_ANDROID) + if (fabsf(CORE.Input.Mouse.currentWheelMove.x) > fabsf(CORE.Input.Mouse.currentWheelMove.y)) result = (float)CORE.Input.Mouse.currentWheelMove.x; + else result = (float)CORE.Input.Mouse.currentWheelMove.y; #endif - return CORE.Input.Mouse.previousWheelMove; + return result; +} + +// Get mouse wheel movement X/Y as a vector +Vector2 GetMouseWheelMoveV(void) +{ + Vector2 result = { 0 }; + + result = CORE.Input.Mouse.currentWheelMove; + + return result; } // Set mouse cursor @@ -3803,6 +3990,10 @@ static bool InitGraphicsDevice(int width, int height) #endif } else glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_FALSE); + + // Mouse passthrough + if ((CORE.Window.flags & FLAG_WINDOW_MOUSE_PASSTHROUGH) > 0) glfwWindowHint(GLFW_MOUSE_PASSTHROUGH, GLFW_TRUE); + else glfwWindowHint(GLFW_MOUSE_PASSTHROUGH, GLFW_FALSE); #endif if (CORE.Window.flags & FLAG_MSAA_4X_HINT) @@ -3891,14 +4082,6 @@ static bool InitGraphicsDevice(int width, int height) } } } - -#if defined(PLATFORM_DESKTOP) - // If we are windowed fullscreen, ensures that window does not minimize when focus is lost - if ((CORE.Window.screen.height == CORE.Window.display.height) && (CORE.Window.screen.width == CORE.Window.display.width)) - { - glfwWindowHint(GLFW_AUTO_ICONIFY, 0); - } -#endif TRACELOG(LOG_WARNING, "SYSTEM: Closest fullscreen videomode: %i x %i", CORE.Window.display.width, CORE.Window.display.height); // NOTE: ISSUE: Closest videomode could not match monitor aspect-ratio, for example, @@ -3920,6 +4103,13 @@ static bool InitGraphicsDevice(int width, int height) } else { +#if defined(PLATFORM_DESKTOP) + // If we are windowed fullscreen, ensures that window does not minimize when focus is lost + if ((CORE.Window.screen.height == CORE.Window.display.height) && (CORE.Window.screen.width == CORE.Window.display.width)) + { + glfwWindowHint(GLFW_AUTO_ICONIFY, 0); + } +#endif // No-fullscreen window creation CORE.Window.handle = glfwCreateWindow(CORE.Window.screen.width, CORE.Window.screen.height, (CORE.Window.title != 0)? CORE.Window.title : " ", NULL, NULL); @@ -3977,7 +4167,7 @@ static bool InitGraphicsDevice(int width, int height) glfwSwapInterval(1); TRACELOG(LOG_INFO, "DISPLAY: Trying to enable VSYNC"); } - + int fbWidth = CORE.Window.screen.width; int fbHeight = CORE.Window.screen.height; @@ -4002,7 +4192,7 @@ static bool InitGraphicsDevice(int width, int height) CORE.Window.render.height = fbHeight; CORE.Window.currentFbo.width = fbWidth; CORE.Window.currentFbo.height = fbHeight; - + TRACELOG(LOG_INFO, "DISPLAY: Device initialized successfully"); TRACELOG(LOG_INFO, " > Display size: %i x %i", CORE.Window.display.width, CORE.Window.display.height); TRACELOG(LOG_INFO, " > Screen size: %i x %i", CORE.Window.screen.width, CORE.Window.screen.height); @@ -4041,11 +4231,13 @@ static bool InitGraphicsDevice(int width, int height) #else TRACELOG(LOG_INFO, "DISPLAY: No graphic card set, trying platform-gpu-card"); CORE.Window.fd = open("/dev/dri/by-path/platform-gpu-card", O_RDWR); // VideoCore VI (Raspberry Pi 4) + if ((-1 == CORE.Window.fd) || (drmModeGetResources(CORE.Window.fd) == NULL)) { TRACELOG(LOG_INFO, "DISPLAY: Failed to open platform-gpu-card, trying card1"); CORE.Window.fd = open("/dev/dri/card1", O_RDWR); // Other Embedded } + if ((-1 == CORE.Window.fd) || (drmModeGetResources(CORE.Window.fd) == NULL)) { TRACELOG(LOG_INFO, "DISPLAY: Failed to open graphic card1, trying card0"); @@ -4414,7 +4606,7 @@ static bool InitGraphicsDevice(int width, int height) CORE.Window.render.height = CORE.Window.screen.height; CORE.Window.currentFbo.width = CORE.Window.render.width; CORE.Window.currentFbo.height = CORE.Window.render.height; - + TRACELOG(LOG_INFO, "DISPLAY: Device initialized successfully"); TRACELOG(LOG_INFO, " > Display size: %i x %i", CORE.Window.display.width, CORE.Window.display.height); TRACELOG(LOG_INFO, " > Screen size: %i x %i", CORE.Window.screen.width, CORE.Window.screen.height); @@ -4439,8 +4631,6 @@ static bool InitGraphicsDevice(int width, int height) // NOTE: It updated CORE.Window.render.width and CORE.Window.render.height SetupViewport(CORE.Window.currentFbo.width, CORE.Window.currentFbo.height); - ClearBackground(RAYWHITE); // Default background color for raylib games :P - #if defined(PLATFORM_ANDROID) CORE.Window.ready = true; #endif @@ -4580,49 +4770,46 @@ static void InitTimer(void) CORE.Time.previous = GetTime(); // Get time as double } -// Wait for some milliseconds (stop program execution) +// Wait for some time (stop program execution) // NOTE: Sleep() granularity could be around 10 ms, it means, Sleep() could // take longer than expected... for that reason we use the busy wait loop // Ref: http://stackoverflow.com/questions/43057578/c-programming-win32-games-sleep-taking-longer-than-expected // Ref: http://www.geisswerks.com/ryan/FAQS/timing.html --> All about timming on Win32! -void WaitTime(float ms) +void WaitTime(double seconds) { -#if defined(SUPPORT_BUSY_WAIT_LOOP) - double previousTime = GetTime(); - double currentTime = 0.0; +#if defined(SUPPORT_BUSY_WAIT_LOOP) || defined(SUPPORT_PARTIALBUSY_WAIT_LOOP) + double destinationTime = GetTime() + seconds; +#endif - // Busy wait loop - while ((currentTime - previousTime) < ms/1000.0f) currentTime = GetTime(); +#if defined(SUPPORT_BUSY_WAIT_LOOP) + while (GetTime() < destinationTime) { } #else #if defined(SUPPORT_PARTIALBUSY_WAIT_LOOP) - double busyWait = ms*0.05; // NOTE: We are using a busy wait of 5% of the time - ms -= (float)busyWait; + double sleepSeconds = seconds - seconds*0.05; // NOTE: We reserve a percentage of the time for busy waiting + #else + double sleepSeconds = seconds; #endif // System halt functions #if defined(_WIN32) - Sleep((unsigned int)ms); + Sleep((unsigned long)(sleepSeconds*1000.0)); #endif - #if defined(__linux__) || defined(__FreeBSD__) || defined(__EMSCRIPTEN__) + #if defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__EMSCRIPTEN__) struct timespec req = { 0 }; - time_t sec = (int)(ms/1000.0f); - ms -= (sec*1000); + time_t sec = sleepSeconds; + long nsec = (sleepSeconds - sec)*1000000000L; req.tv_sec = sec; - req.tv_nsec = ms*1000000L; + req.tv_nsec = nsec; // NOTE: Use nanosleep() on Unix platforms... usleep() it's deprecated. while (nanosleep(&req, &req) == -1) continue; #endif #if defined(__APPLE__) - usleep(ms*1000.0f); + usleep(sleepSeconds*1000000.0); #endif #if defined(SUPPORT_PARTIALBUSY_WAIT_LOOP) - double previousTime = GetTime(); - double currentTime = 0.0; - - // Partial busy wait loop (only a fraction of the total wait time) - while ((currentTime - previousTime) < busyWait/1000.0f) currentTime = GetTime(); + while (GetTime() < destinationTime) { } #endif #endif } @@ -4638,53 +4825,31 @@ void SwapScreenBuffer(void) eglSwapBuffers(CORE.Window.device, CORE.Window.surface); #if defined(PLATFORM_DRM) - if (!CORE.Window.gbmSurface || (-1 == CORE.Window.fd) || !CORE.Window.connector || !CORE.Window.crtc) - { - TRACELOG(LOG_ERROR, "DISPLAY: DRM initialization failed to swap"); - abort(); - } + + if (!CORE.Window.gbmSurface || (-1 == CORE.Window.fd) || !CORE.Window.connector || !CORE.Window.crtc) TRACELOG(LOG_ERROR, "DISPLAY: DRM initialization failed to swap"); struct gbm_bo *bo = gbm_surface_lock_front_buffer(CORE.Window.gbmSurface); - if (!bo) - { - TRACELOG(LOG_ERROR, "DISPLAY: Failed GBM to lock front buffer"); - abort(); - } + if (!bo) TRACELOG(LOG_ERROR, "DISPLAY: Failed GBM to lock front buffer"); uint32_t fb = 0; - int result = drmModeAddFB(CORE.Window.fd, CORE.Window.connector->modes[CORE.Window.modeIndex].hdisplay, - CORE.Window.connector->modes[CORE.Window.modeIndex].vdisplay, 24, 32, gbm_bo_get_stride(bo), gbm_bo_get_handle(bo).u32, &fb); - if (0 != result) - { - TRACELOG(LOG_ERROR, "DISPLAY: drmModeAddFB() failed with result: %d", result); - abort(); - } + int result = drmModeAddFB(CORE.Window.fd, CORE.Window.connector->modes[CORE.Window.modeIndex].hdisplay, CORE.Window.connector->modes[CORE.Window.modeIndex].vdisplay, 24, 32, gbm_bo_get_stride(bo), gbm_bo_get_handle(bo).u32, &fb); + if (result != 0) TRACELOG(LOG_ERROR, "DISPLAY: drmModeAddFB() failed with result: %d", result); - result = drmModeSetCrtc(CORE.Window.fd, CORE.Window.crtc->crtc_id, fb, 0, 0, - &CORE.Window.connector->connector_id, 1, &CORE.Window.connector->modes[CORE.Window.modeIndex]); - if (0 != result) - { - TRACELOG(LOG_ERROR, "DISPLAY: drmModeSetCrtc() failed with result: %d", result); - abort(); - } + result = drmModeSetCrtc(CORE.Window.fd, CORE.Window.crtc->crtc_id, fb, 0, 0, &CORE.Window.connector->connector_id, 1, &CORE.Window.connector->modes[CORE.Window.modeIndex]); + if (result != 0) TRACELOG(LOG_ERROR, "DISPLAY: drmModeSetCrtc() failed with result: %d", result); if (CORE.Window.prevFB) { result = drmModeRmFB(CORE.Window.fd, CORE.Window.prevFB); - if (0 != result) - { - TRACELOG(LOG_ERROR, "DISPLAY: drmModeRmFB() failed with result: %d", result); - abort(); - } + if (result != 0) TRACELOG(LOG_ERROR, "DISPLAY: drmModeRmFB() failed with result: %d", result); } + CORE.Window.prevFB = fb; - if (CORE.Window.prevBO) - { - gbm_surface_release_buffer(CORE.Window.gbmSurface, CORE.Window.prevBO); - } + if (CORE.Window.prevBO) gbm_surface_release_buffer(CORE.Window.gbmSurface, CORE.Window.prevBO); CORE.Window.prevBO = bo; + #endif // PLATFORM_DRM #endif // PLATFORM_ANDROID || PLATFORM_RPI || PLATFORM_DRM } @@ -4716,7 +4881,7 @@ void PollInputEvents(void) // Register previous mouse states CORE.Input.Mouse.previousWheelMove = CORE.Input.Mouse.currentWheelMove; - CORE.Input.Mouse.currentWheelMove = 0.0f; + CORE.Input.Mouse.currentWheelMove = (Vector2){ 0.0f, 0.0f }; for (int i = 0; i < MAX_MOUSE_BUTTONS; i++) { CORE.Input.Mouse.previousButtonState[i] = CORE.Input.Mouse.currentButtonState[i]; @@ -4745,7 +4910,7 @@ void PollInputEvents(void) // Register previous mouse wheel state CORE.Input.Mouse.previousWheelMove = CORE.Input.Mouse.currentWheelMove; - CORE.Input.Mouse.currentWheelMove = 0.0f; + CORE.Input.Mouse.currentWheelMove = (Vector2){ 0.0f, 0.0f }; // Register previous mouse position CORE.Input.Mouse.previousPosition = CORE.Input.Mouse.currentPosition; @@ -4753,7 +4918,7 @@ void PollInputEvents(void) // Register previous touch states for (int i = 0; i < MAX_TOUCH_POINTS; i++) CORE.Input.Touch.previousTouchState[i] = CORE.Input.Touch.currentTouchState[i]; - + // Reset touch positions // TODO: It resets on PLATFORM_WEB the mouse position and not filled again until a move-event, // so, if mouse is not moved it returns a (0, 0) position... this behaviour should be reviewed! @@ -4832,8 +4997,8 @@ void PollInputEvents(void) } // Register buttons for 2nd triggers (because GLFW doesn't count these as buttons but rather axis) - CORE.Input.Gamepad.currentButtonState[i][GAMEPAD_BUTTON_LEFT_TRIGGER_2] = (char)(CORE.Input.Gamepad.axisState[i][GAMEPAD_AXIS_LEFT_TRIGGER] > 0.1); - CORE.Input.Gamepad.currentButtonState[i][GAMEPAD_BUTTON_RIGHT_TRIGGER_2] = (char)(CORE.Input.Gamepad.axisState[i][GAMEPAD_AXIS_RIGHT_TRIGGER] > 0.1); + CORE.Input.Gamepad.currentButtonState[i][GAMEPAD_BUTTON_LEFT_TRIGGER_2] = (char)(CORE.Input.Gamepad.axisState[i][GAMEPAD_AXIS_LEFT_TRIGGER] > 0.1f); + CORE.Input.Gamepad.currentButtonState[i][GAMEPAD_BUTTON_RIGHT_TRIGGER_2] = (char)(CORE.Input.Gamepad.axisState[i][GAMEPAD_AXIS_RIGHT_TRIGGER] > 0.1f); CORE.Input.Gamepad.axisCount = GLFW_GAMEPAD_AXIS_LAST + 1; } @@ -4841,11 +5006,8 @@ void PollInputEvents(void) CORE.Window.resizedLastFrame = false; -#if defined(SUPPORT_EVENTS_WAITING) - glfwWaitEvents(); -#else - glfwPollEvents(); // Register keyboard/mouse events (callbacks)... and window events! -#endif + if (CORE.Window.eventWaiting) glfwWaitEvents(); // Wait for in input events before continue (drawing is paused) + else glfwPollEvents(); // Poll input events: keyboard/mouse/window events (callbacks) #endif // PLATFORM_DESKTOP #if defined(PLATFORM_WEB) @@ -4957,6 +5119,96 @@ void PollInputEvents(void) #endif } +// Scan all files and directories in a base path +// WARNING: files.paths[] must be previously allocated and +// contain enough space to store all required paths +static void ScanDirectoryFiles(const char *basePath, FilePathList *files, const char *filter) +{ + static char path[MAX_FILEPATH_LENGTH] = { 0 }; + memset(path, 0, MAX_FILEPATH_LENGTH); + + struct dirent *dp = NULL; + DIR *dir = opendir(basePath); + + if (dir != NULL) + { + while ((dp = readdir(dir)) != NULL) + { + if ((strcmp(dp->d_name, ".") != 0) && + (strcmp(dp->d_name, "..") != 0)) + { + sprintf(path, "%s/%s", basePath, dp->d_name); + + if (filter != NULL) + { + if (IsFileExtension(path, filter)) + { + strcpy(files->paths[files->count], path); + files->count++; + } + } + else + { + strcpy(files->paths[files->count], path); + files->count++; + } + } + } + + closedir(dir); + } + else TRACELOG(LOG_WARNING, "FILEIO: Directory cannot be opened (%s)", basePath); +} + +// Scan all files and directories recursively from a base path +static void ScanDirectoryFilesRecursively(const char *basePath, FilePathList *files, const char *filter) +{ + static char path[MAX_FILEPATH_LENGTH] = { 0 }; + memset(path, 0, MAX_FILEPATH_LENGTH); + + struct dirent *dp = NULL; + DIR *dir = opendir(basePath); + + if (dir != NULL) + { + while (((dp = readdir(dir)) != NULL) && (files->count < files->capacity)) + { + if ((strcmp(dp->d_name, ".") != 0) && (strcmp(dp->d_name, "..") != 0)) + { + // Construct new path from our base path + sprintf(path, "%s/%s", basePath, dp->d_name); + + if (IsPathFile(path)) + { + if (filter != NULL) + { + if (IsFileExtension(path, filter)) + { + strcpy(files->paths[files->count], path); + files->count++; + } + } + else + { + strcpy(files->paths[files->count], path); + files->count++; + } + + if (files->count >= files->capacity) + { + TRACELOG(LOG_WARNING, "FILEIO: Maximum filepath scan capacity reached (%i files)", files->capacity); + break; + } + } + else ScanDirectoryFilesRecursively(path, files, filter); + } + } + + closedir(dir); + } + else TRACELOG(LOG_WARNING, "FILEIO: Directory cannot be opened (%s)", basePath); +} + #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) // GLFW3 Error Callback, runs on GLFW3 error static void ErrorCallback(int error, const char *description) @@ -4964,39 +5216,6 @@ static void ErrorCallback(int error, const char *description) TRACELOG(LOG_WARNING, "GLFW: Error: %i Description: %s", error, description); } -#if defined(PLATFORM_WEB) -EM_JS(int, GetCanvasWidth, (), { return canvas.clientWidth; }); -EM_JS(int, GetCanvasHeight, (), { return canvas.clientHeight; }); - -static EM_BOOL EmscriptenResizeCallback(int eventType, const EmscriptenUiEvent *e, void *userData) -{ - // Don't resize non-resizeable windows - if ((CORE.Window.flags & FLAG_WINDOW_RESIZABLE) == 0) return 1; - - // This event is called whenever the window changes sizes, - // so the size of the canvas object is explicitly retrieved below - int width = GetCanvasWidth(); - int height = GetCanvasHeight(); - emscripten_set_canvas_element_size("#canvas",width,height); - - SetupViewport(width, height); // Reset viewport and projection matrix for new size - - CORE.Window.currentFbo.width = width; - CORE.Window.currentFbo.height = height; - CORE.Window.resizedLastFrame = true; - - if (IsWindowFullscreen()) return 1; - - // Set current screen size - CORE.Window.screen.width = width; - CORE.Window.screen.height = height; - - // NOTE: Postprocessing texture is not scaled to new size - - return 0; -} -#endif - // GLFW3 WindowSize Callback, runs when window is resizedLastFrame // NOTE: Window resizing not allowed by default static void WindowSizeCallback(GLFWwindow *window, int width, int height) @@ -5058,6 +5277,8 @@ static void WindowFocusCallback(GLFWwindow *window, int focused) // GLFW3 Keyboard Callback, runs on key pressed static void KeyCallback(GLFWwindow *window, int key, int scancode, int action, int mods) { + if (key < 0) return; // Security check, macOS fn key generates -1 + // WARNING: GLFW could return GLFW_REPEAT, we need to consider it as 1 // to work properly with our implementation (IsKeyDown/IsKeyUp checks) if (action == GLFW_RELEASE) CORE.Input.Keyboard.currentKeyState[key] = 0; @@ -5070,7 +5291,7 @@ static void KeyCallback(GLFWwindow *window, int key, int scancode, int action, i CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = key; CORE.Input.Keyboard.keyPressedQueueCount++; } - + // Check the exit key to set close window if ((key == CORE.Input.Keyboard.exitKey) && (action == GLFW_PRESS)) glfwSetWindowShouldClose(CORE.Window.handle, GLFW_TRUE); @@ -5102,7 +5323,8 @@ static void KeyCallback(GLFWwindow *window, int key, int scancode, int action, i gifRecording = true; gifFrameCounter = 0; - msf_gif_begin(&gifState, CORE.Window.screen.width, CORE.Window.screen.height); + Vector2 scale = GetWindowScaleDPI(); + msf_gif_begin(&gifState, (int)((float)CORE.Window.render.width*scale.x), (int)((float)CORE.Window.render.height*scale.y)); screenshotCounter++; TRACELOG(LOG_INFO, "SYSTEM: Start animated GIF recording: %s", TextFormat("screenrec%03i.gif", screenshotCounter)); @@ -5220,11 +5442,10 @@ static void MouseCursorPosCallback(GLFWwindow *window, double x, double y) #endif } -// GLFW3 Srolling Callback, runs on mouse wheel +// GLFW3 Scrolling Callback, runs on mouse wheel static void MouseScrollCallback(GLFWwindow *window, double xoffset, double yoffset) { - if (xoffset != 0.0) CORE.Input.Mouse.currentWheelMove = (float)xoffset; - else CORE.Input.Mouse.currentWheelMove = (float)yoffset; + CORE.Input.Mouse.currentWheelMove = (Vector2){ (float)xoffset, (float)yoffset }; } // GLFW3 CursorEnter Callback, when cursor enters the window @@ -5235,21 +5456,28 @@ static void CursorEnterCallback(GLFWwindow *window, int enter) } // GLFW3 Window Drop Callback, runs when drop files into window -// NOTE: Paths are stored in dynamic memory for further retrieval -// Everytime new files are dropped, old ones are discarded static void WindowDropCallback(GLFWwindow *window, int count, const char **paths) { - ClearDroppedFiles(); - - CORE.Window.dropFilesPath = (char **)RL_MALLOC(count*sizeof(char *)); - - for (int i = 0; i < count; i++) + // In case previous dropped filepaths have not been freed, we free them + if (CORE.Window.dropFileCount > 0) { - CORE.Window.dropFilesPath[i] = (char *)RL_MALLOC(MAX_FILEPATH_LENGTH*sizeof(char)); - strcpy(CORE.Window.dropFilesPath[i], paths[i]); + for (unsigned int i = 0; i < CORE.Window.dropFileCount; i++) RL_FREE(CORE.Window.dropFilepaths[i]); + + RL_FREE(CORE.Window.dropFilepaths); + + CORE.Window.dropFileCount = 0; + CORE.Window.dropFilepaths = NULL; } + // WARNING: Paths are freed by GLFW when the callback returns, we must keep an internal copy CORE.Window.dropFileCount = count; + CORE.Window.dropFilepaths = (char **)RL_CALLOC(CORE.Window.dropFileCount, sizeof(char *)); + + for (unsigned int i = 0; i < CORE.Window.dropFileCount; i++) + { + CORE.Window.dropFilepaths[i] = (char *)RL_CALLOC(MAX_FILEPATH_LENGTH, sizeof(char)); + strcpy(CORE.Window.dropFilepaths[i], paths[i]); + } } #endif @@ -5295,13 +5523,15 @@ static void AndroidCommandCallback(struct android_app *app, int32_t cmd) // Initialize random seed srand((unsigned int)time(NULL)); - #if defined(SUPPORT_DEFAULT_FONT) + #if defined(SUPPORT_MODULE_RTEXT) && defined(SUPPORT_DEFAULT_FONT) // Load default font - // NOTE: External function (defined in module: text) + // WARNING: External function: Module required: rtext LoadFontDefault(); Rectangle rec = GetFontDefault().recs[95]; // NOTE: We setup a 1px padding on char rectangle to avoid pixel bleeding on MSAA filtering - SetShapesTexture(GetFontDefault().texture, (Rectangle){ rec.x + 1, rec.y + 1, rec.width - 2, rec.height - 2 }); + #if defined(SUPPORT_MODULE_RSHAPES) + SetShapesTexture(GetFontDefault().texture, (Rectangle){ rec.x + 1, rec.y + 1, rec.width - 2, rec.height - 2 }); // WARNING: Module required: rshapes + #endif #endif // TODO: GPU assets reload in case of lost focus (lost context) @@ -5484,11 +5714,82 @@ static int32_t AndroidInputCallback(struct android_app *app, AInputEvent *event) #endif #if defined(PLATFORM_WEB) +// Register fullscreen change events +static EM_BOOL EmscriptenFullscreenChangeCallback(int eventType, const EmscriptenFullscreenChangeEvent *event, void *userData) +{ + // TODO: Implement EmscriptenFullscreenChangeCallback()? + + return 1; // The event was consumed by the callback handler +} + +// Register window resize event +static EM_BOOL EmscriptenWindowResizedCallback(int eventType, const EmscriptenUiEvent *event, void *userData) +{ + // TODO: Implement EmscriptenWindowResizedCallback()? + + return 1; // The event was consumed by the callback handler +} + +EM_JS(int, GetCanvasWidth, (), { return canvas.clientWidth; }); +EM_JS(int, GetCanvasHeight, (), { return canvas.clientHeight; }); + +// Register DOM element resize event +static EM_BOOL EmscriptenResizeCallback(int eventType, const EmscriptenUiEvent *event, void *userData) +{ + // Don't resize non-resizeable windows + if ((CORE.Window.flags & FLAG_WINDOW_RESIZABLE) == 0) return 1; + + // This event is called whenever the window changes sizes, + // so the size of the canvas object is explicitly retrieved below + int width = GetCanvasWidth(); + int height = GetCanvasHeight(); + emscripten_set_canvas_element_size("#canvas",width,height); + + SetupViewport(width, height); // Reset viewport and projection matrix for new size + + CORE.Window.currentFbo.width = width; + CORE.Window.currentFbo.height = height; + CORE.Window.resizedLastFrame = true; + + if (IsWindowFullscreen()) return 1; + + // Set current screen size + CORE.Window.screen.width = width; + CORE.Window.screen.height = height; + + // NOTE: Postprocessing texture is not scaled to new size + + return 0; +} + // Register mouse input events static EM_BOOL EmscriptenMouseCallback(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData) { // This is only for registering mouse click events with emscripten and doesn't need to do anything - return 0; + + return 1; // The event was consumed by the callback handler +} + +// Register connected/disconnected gamepads events +static EM_BOOL EmscriptenGamepadCallback(int eventType, const EmscriptenGamepadEvent *gamepadEvent, void *userData) +{ + /* + TRACELOGD("%s: timeStamp: %g, connected: %d, index: %ld, numAxes: %d, numButtons: %d, id: \"%s\", mapping: \"%s\"", + eventType != 0? emscripten_event_type_to_string(eventType) : "Gamepad state", + gamepadEvent->timestamp, gamepadEvent->connected, gamepadEvent->index, gamepadEvent->numAxes, gamepadEvent->numButtons, gamepadEvent->id, gamepadEvent->mapping); + + for (int i = 0; i < gamepadEvent->numAxes; ++i) TRACELOGD("Axis %d: %g", i, gamepadEvent->axis[i]); + for (int i = 0; i < gamepadEvent->numButtons; ++i) TRACELOGD("Button %d: Digital: %d, Analog: %g", i, gamepadEvent->digitalButton[i], gamepadEvent->analogButton[i]); + */ + + if ((gamepadEvent->connected) && (gamepadEvent->index < MAX_GAMEPADS)) + { + CORE.Input.Gamepad.ready[gamepadEvent->index] = true; + sprintf(CORE.Input.Gamepad.name[gamepadEvent->index],"%s",gamepadEvent->id); + } + else CORE.Input.Gamepad.ready[gamepadEvent->index] = false; + + return 1; // The event was consumed by the callback handler } // Register touch input events @@ -5541,29 +5842,7 @@ static EM_BOOL EmscriptenTouchCallback(int eventType, const EmscriptenTouchEvent ProcessGestureEvent(gestureEvent); #endif - return 1; -} - -// Register connected/disconnected gamepads events -static EM_BOOL EmscriptenGamepadCallback(int eventType, const EmscriptenGamepadEvent *gamepadEvent, void *userData) -{ - /* - TRACELOGD("%s: timeStamp: %g, connected: %d, index: %ld, numAxes: %d, numButtons: %d, id: \"%s\", mapping: \"%s\"", - eventType != 0? emscripten_event_type_to_string(eventType) : "Gamepad state", - gamepadEvent->timestamp, gamepadEvent->connected, gamepadEvent->index, gamepadEvent->numAxes, gamepadEvent->numButtons, gamepadEvent->id, gamepadEvent->mapping); - - for (int i = 0; i < gamepadEvent->numAxes; ++i) TRACELOGD("Axis %d: %g", i, gamepadEvent->axis[i]); - for (int i = 0; i < gamepadEvent->numButtons; ++i) TRACELOGD("Button %d: Digital: %d, Analog: %g", i, gamepadEvent->digitalButton[i], gamepadEvent->analogButton[i]); - */ - - if ((gamepadEvent->connected) && (gamepadEvent->index < MAX_GAMEPADS)) - { - CORE.Input.Gamepad.ready[gamepadEvent->index] = true; - sprintf(CORE.Input.Gamepad.name[gamepadEvent->index],"%s",gamepadEvent->id); - } - else CORE.Input.Gamepad.ready[gamepadEvent->index] = false; - - return 0; + return 1; // The event was consumed by the callback handler } #endif @@ -5764,7 +6043,8 @@ static void InitEvdevInput(void) { while ((entity = readdir(directory)) != NULL) { - if (strncmp("event", entity->d_name, strlen("event")) == 0) // Search for devices named "event*" + if ((strncmp("event", entity->d_name, strlen("event")) == 0) || // Search for devices named "event*" + (strncmp("mouse", entity->d_name, strlen("mouse")) == 0)) // Search for devices named "mouse*" { sprintf(path, "%s%s", DEFAULT_EVDEV_PATH, entity->d_name); ConfigureEvdevDevice(path); // Configure the device if appropriate @@ -5827,7 +6107,7 @@ static void ConfigureEvdevDevice(char *device) fd = open(device, O_RDONLY | O_NONBLOCK); if (fd < 0) { - TRACELOG(LOG_WARNING, "RPI: Failed to open input device %s", device); + TRACELOG(LOG_WARNING, "RPI: Failed to open input device: %s", device); return; } worker->fd = fd; @@ -5841,6 +6121,7 @@ static void ConfigureEvdevDevice(char *device) { if (sscanf(ptrDevName, "t%d", &devNum) == 1) worker->eventNum = devNum; } + else worker->eventNum = 0; // TODO: HACK: Grab number for mouse0 device! // At this point we have a connection to the device, but we don't yet know what the device is. // It could be many things, even as simple as a power button... @@ -5924,7 +6205,7 @@ static void ConfigureEvdevDevice(char *device) // Decide what to do with the device //------------------------------------------------------------------------------------------------------- - if (worker->isKeyboard && CORE.Input.Keyboard.fd == -1) + if (worker->isKeyboard && (CORE.Input.Keyboard.fd == -1)) { // Use the first keyboard encountered. This assumes that a device that says it's a keyboard is just a // keyboard. The keyboard is polled synchronously, whereas other input devices are polled in separate @@ -6088,7 +6369,7 @@ static void *EventThread(void *arg) gestureUpdate = true; } - if (event.code == REL_WHEEL) CORE.Input.Mouse.currentWheelMove += event.value; + if (event.code == REL_WHEEL) CORE.Input.Mouse.currentWheelMove.y += event.value; } // Absolute movement parsing @@ -6219,7 +6500,7 @@ static void *EventThread(void *arg) #endif } - WaitTime(5); // Sleep for 5ms to avoid hogging CPU time + WaitTime(0.005); // Sleep for 5ms to avoid hogging CPU time } close(worker->fd); @@ -6236,7 +6517,7 @@ static void InitGamepad(void) { sprintf(gamepadDev, "%s%i", DEFAULT_GAMEPAD_DEV, i); - if ((CORE.Input.Gamepad.streamId[i] = open(gamepadDev, O_RDONLY|O_NONBLOCK)) < 0) + if ((CORE.Input.Gamepad.streamId[i] = open(gamepadDev, O_RDONLY | O_NONBLOCK)) < 0) { // NOTE: Only show message for first gamepad if (i == 0) TRACELOG(LOG_WARNING, "RPI: Failed to open Gamepad device, no gamepad available"); @@ -6307,7 +6588,7 @@ static void *GamepadThread(void *arg) } } } - else WaitTime(1); // Sleep for 1 ms to avoid hogging CPU time + else WaitTime(0.001); // Sleep for 1 ms to avoid hogging CPU time } } @@ -6375,7 +6656,7 @@ static int FindNearestConnectorMode(const drmModeConnector *connector, uint widt TRACELOG(LOG_TRACE, "DISPLAY: DRM mode: %d %ux%u@%u %s", i, mode->hdisplay, mode->vdisplay, mode->vrefresh, (mode->flags & DRM_MODE_FLAG_INTERLACE) ? "interlaced" : "progressive"); - if ((mode->hdisplay < width) || (mode->vdisplay < height) | (mode->vrefresh < fps)) + if ((mode->hdisplay < width) || (mode->vdisplay < height)) { TRACELOG(LOG_TRACE, "DISPLAY: DRM mode is too small"); continue; @@ -6387,23 +6668,22 @@ static int FindNearestConnectorMode(const drmModeConnector *connector, uint widt continue; } - if ((mode->hdisplay >= width) && (mode->vdisplay >= height) && (mode->vrefresh >= fps)) + if (nearestIndex < 0) { - const int widthDiff = mode->hdisplay - width; - const int heightDiff = mode->vdisplay - height; - const int fpsDiff = mode->vrefresh - fps; + nearestIndex = i; + continue; + } - if (nearestIndex < 0) - { - nearestIndex = i; - continue; - } + const int widthDiff = abs(mode->hdisplay - width); + const int heightDiff = abs(mode->vdisplay - height); + const int fpsDiff = abs(mode->vrefresh - fps); - const int nearestWidthDiff = CORE.Window.connector->modes[nearestIndex].hdisplay - width; - const int nearestHeightDiff = CORE.Window.connector->modes[nearestIndex].vdisplay - height; - const int nearestFpsDiff = CORE.Window.connector->modes[nearestIndex].vrefresh - fps; + const int nearestWidthDiff = abs(CORE.Window.connector->modes[nearestIndex].hdisplay - width); + const int nearestHeightDiff = abs(CORE.Window.connector->modes[nearestIndex].vdisplay - height); + const int nearestFpsDiff = abs(CORE.Window.connector->modes[nearestIndex].vrefresh - fps); - if ((widthDiff < nearestWidthDiff) || (heightDiff < nearestHeightDiff) || (fpsDiff < nearestFpsDiff)) nearestIndex = i; + if ((widthDiff < nearestWidthDiff) || (heightDiff < nearestHeightDiff) || (fpsDiff < nearestFpsDiff)) { + nearestIndex = i; } } @@ -6413,6 +6693,7 @@ static int FindNearestConnectorMode(const drmModeConnector *connector, uint widt #if defined(SUPPORT_EVENTS_AUTOMATION) // NOTE: Loading happens over AutomationEvent *events +// TODO: This system should probably be redesigned static void LoadAutomationEvents(const char *fileName) { //unsigned char fileId[4] = { 0 }; @@ -6575,12 +6856,13 @@ static void RecordAutomationEvent(unsigned int frame) } // INPUT_MOUSE_WHEEL_MOTION - if ((int)CORE.Input.Mouse.currentWheelMove != (int)CORE.Input.Mouse.previousWheelMove) + if (((int)CORE.Input.Mouse.currentWheelMove.x != (int)CORE.Input.Mouse.previousWheelMove.x) || + ((int)CORE.Input.Mouse.currentWheelMove.y != (int)CORE.Input.Mouse.previousWheelMove.y)) { events[eventCount].frame = frame; events[eventCount].type = INPUT_MOUSE_WHEEL_MOTION; - events[eventCount].params[0] = (int)CORE.Input.Mouse.currentWheelMove; - events[eventCount].params[1] = 0; + events[eventCount].params[0] = (int)CORE.Input.Mouse.currentWheelMove.x; + events[eventCount].params[1] = (int)CORE.Input.Mouse.currentWheelMove.y;; events[eventCount].params[2] = 0; TRACELOG(LOG_INFO, "[%i] INPUT_MOUSE_WHEEL_MOTION: %i, %i, %i", events[eventCount].frame, events[eventCount].params[0], events[eventCount].params[1], events[eventCount].params[2]); @@ -6732,7 +7014,11 @@ static void PlayAutomationEvent(unsigned int frame) CORE.Input.Mouse.currentPosition.x = (float)events[i].params[0]; CORE.Input.Mouse.currentPosition.y = (float)events[i].params[1]; } break; - case INPUT_MOUSE_WHEEL_MOTION: CORE.Input.Mouse.currentWheelMove = (float)events[i].params[0]; break; // param[0]: delta + case INPUT_MOUSE_WHEEL_MOTION: // param[0]: x delta, param[1]: y delta + { + CORE.Input.Mouse.currentWheelMove.x = (float)events[i].params[0]; break; + CORE.Input.Mouse.currentWheelMove.y = (float)events[i].params[1]; break; + } break; case INPUT_TOUCH_UP: CORE.Input.Touch.currentTouchState[events[i].params[0]] = false; break; // param[0]: id case INPUT_TOUCH_DOWN: CORE.Input.Touch.currentTouchState[events[i].params[0]] = true; break; // param[0]: id case INPUT_TOUCH_POSITION: // param[0]: id, param[1]: x, param[2]: y @@ -6769,3 +7055,34 @@ static void PlayAutomationEvent(unsigned int frame) } } #endif + +#if !defined(SUPPORT_MODULE_RTEXT) +// Formatting of text with variables to 'embed' +// WARNING: String returned will expire after this function is called MAX_TEXTFORMAT_BUFFERS times +const char *TextFormat(const char *text, ...) +{ +#ifndef MAX_TEXTFORMAT_BUFFERS + #define MAX_TEXTFORMAT_BUFFERS 4 // Maximum number of static buffers for text formatting +#endif +#ifndef MAX_TEXT_BUFFER_LENGTH + #define MAX_TEXT_BUFFER_LENGTH 1024 // Maximum size of static text buffer +#endif + + // We create an array of buffers so strings don't expire until MAX_TEXTFORMAT_BUFFERS invocations + static char buffers[MAX_TEXTFORMAT_BUFFERS][MAX_TEXT_BUFFER_LENGTH] = { 0 }; + static int index = 0; + + char *currentBuffer = buffers[index]; + memset(currentBuffer, 0, MAX_TEXT_BUFFER_LENGTH); // Clear buffer before using + + va_list args; + va_start(args, text); + vsnprintf(currentBuffer, MAX_TEXT_BUFFER_LENGTH, text, args); + va_end(args); + + index += 1; // Move to next buffer for next function call + if (index >= MAX_TEXTFORMAT_BUFFERS) index = 0; + + return currentBuffer; +} +#endif // !SUPPORT_MODULE_RTEXT diff --git a/raylib/rgestures.h b/raylib/rgestures.h index bb9dd16..e33e52e 100644 --- a/raylib/rgestures.h +++ b/raylib/rgestures.h @@ -24,7 +24,7 @@ * * LICENSE: zlib/libpng * -* Copyright (c) 2014-2021 Ramon Santamaria (@raysan5) +* Copyright (c) 2014-2022 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. @@ -62,10 +62,10 @@ // NOTE: Below types are required for GESTURES_STANDALONE usage //---------------------------------------------------------------------------------- // Boolean type -#if defined(__STDC__) && __STDC_VERSION__ >= 199901L +#if (defined(__STDC__) && __STDC_VERSION__ >= 199901L) || (defined(_MSC_VER) && _MSC_VER >= 1800) #include #elif !defined(__cplusplus) && !defined(bool) && !defined(RL_BOOL_TYPE) - typedef enum bool { false, true } bool; + typedef enum bool { false = 0, true = !false } bool; #endif #if !defined(RL_VECTOR2_TYPE) @@ -118,7 +118,7 @@ typedef struct { // Module Functions Declaration //---------------------------------------------------------------------------------- -#ifdef __cplusplus +#if defined(__cplusplus) extern "C" { // Prevents name mangling of functions #endif @@ -137,7 +137,7 @@ Vector2 GetGesturePinchVector(void); // Get gesture pinch del float GetGesturePinchAngle(void); // Get gesture pinch angle #endif -#ifdef __cplusplus +#if defined(__cplusplus) } #endif diff --git a/raylib/rlgl.h b/raylib/rlgl.h index d37d45f..123791a 100644 --- a/raylib/rlgl.h +++ b/raylib/rlgl.h @@ -85,7 +85,7 @@ * * LICENSE: zlib/libpng * -* Copyright (c) 2014-2021 Ramon Santamaria (@raysan5) +* Copyright (c) 2014-2022 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. @@ -358,11 +358,11 @@ typedef struct rlRenderBatch { float currentDepth; // Current depth value for next draw } rlRenderBatch; -#if defined(__STDC__) && __STDC_VERSION__ >= 199901L +#if (defined(__STDC__) && __STDC_VERSION__ >= 199901L) || (defined(_MSC_VER) && _MSC_VER >= 1800) #include #elif !defined(__cplusplus) && !defined(bool) && !defined(RL_BOOL_TYPE) // Boolean type - typedef enum bool { false, true } bool; +typedef enum bool { false = 0, true = !false } bool; #endif #if !defined(RL_MATRIX_TYPE) @@ -418,7 +418,7 @@ typedef enum { // 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 aproximation + 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 @@ -433,7 +433,8 @@ typedef enum { 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_CUSTOM // Belnd textures using custom src/dst factors (use SetBlendModeCustom()) + RL_BLEND_ALPHA_PREMULTIPLY, // Blend premultiplied textures considering alpha + RL_BLEND_CUSTOM // Blend textures using custom src/dst factors (use rlSetBlendFactors()) } rlBlendMode; // Shader location point type @@ -597,7 +598,9 @@ RLAPI void rlglInit(int width, int height); // Initialize rlgl (buffer RLAPI void rlglClose(void); // De-inititialize 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 @@ -619,25 +622,26 @@ RLAPI void rlSetTexture(unsigned int id); // Set current texture for r // Vertex buffers management RLAPI unsigned int rlLoadVertexArray(void); // Load vertex array (vao) if supported -RLAPI unsigned int rlLoadVertexBuffer(void *buffer, int size, bool dynamic); // Load a vertex buffer attribute -RLAPI unsigned int rlLoadVertexBufferElement(void *buffer, int size, bool dynamic); // Load a new attributes element buffer -RLAPI void rlUpdateVertexBuffer(unsigned int bufferId, void *data, int dataSize, int offset); // Update GPU buffer with new data +RLAPI unsigned int rlLoadVertexBuffer(const void *buffer, int size, bool dynamic); // Load a vertex buffer attribute +RLAPI unsigned int rlLoadVertexBufferElement(const void *buffer, int size, bool dynamic); // Load a new attributes element buffer +RLAPI void rlUpdateVertexBuffer(unsigned int bufferId, const void *data, int dataSize, int offset); // Update GPU buffer with new data +RLAPI void rlUpdateVertexBufferElements(unsigned int id, const void *data, int dataSize, int offset); // Update vertex buffer elements with new data RLAPI void rlUnloadVertexArray(unsigned int vaoId); RLAPI void rlUnloadVertexBuffer(unsigned int vboId); -RLAPI void rlSetVertexAttribute(unsigned int index, int compSize, int type, bool normalized, int stride, void *pointer); +RLAPI void rlSetVertexAttribute(unsigned int index, int compSize, int type, bool normalized, int stride, const void *pointer); RLAPI void rlSetVertexAttributeDivisor(unsigned int index, int divisor); RLAPI void rlSetVertexAttributeDefault(int locIndex, const void *value, int attribType, int count); // Set vertex attribute default value RLAPI void rlDrawVertexArray(int offset, int count); -RLAPI void rlDrawVertexArrayElements(int offset, int count, void *buffer); +RLAPI void rlDrawVertexArrayElements(int offset, int count, const void *buffer); RLAPI void rlDrawVertexArrayInstanced(int offset, int count, int instances); -RLAPI void rlDrawVertexArrayElementsInstanced(int offset, int count, void *buffer, int instances); +RLAPI void rlDrawVertexArrayElementsInstanced(int offset, int count, const void *buffer, int instances); // Textures management -RLAPI unsigned int rlLoadTexture(void *data, int width, int height, int format, int mipmapCount); // Load texture in GPU +RLAPI unsigned int rlLoadTexture(const void *data, int width, int height, int format, int mipmapCount); // Load texture in GPU RLAPI unsigned int rlLoadTextureDepth(int width, int height, bool useRenderBuffer); // Load depth texture/renderbuffer (to be attached to fbo) -RLAPI unsigned int rlLoadTextureCubemap(void *data, int size, int format); // Load texture cubemap +RLAPI unsigned int rlLoadTextureCubemap(const void *data, int size, int format); // Load texture cubemap RLAPI void rlUpdateTexture(unsigned int id, int offsetX, int offsetY, int width, int height, int format, const void *data); // Update GPU texture with new data -RLAPI void rlGetGlTextureFormats(int format, int *glInternalFormat, int *glFormat, int *glType); // Get OpenGL internal formats +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 @@ -713,7 +717,7 @@ RLAPI void rlLoadDrawQuad(void); // Load and draw a quad #include // OpenGL extensions library #else // APIENTRY for OpenGL function pointer declarations is required - #ifndef APIENTRY + #if !defined(APIENTRY) #if defined(_WIN32) #define APIENTRY __stdcall #else @@ -925,8 +929,8 @@ typedef struct rlglData { int glBlendDstFactor; // Blending destination factor int glBlendEquation; // Blending equation - int framebufferWidth; // Default framebuffer width - int framebufferHeight; // Default framebuffer height + int framebufferWidth; // Current framebuffer width + int framebufferHeight; // Current framebuffer height } State; // Renderer state struct { @@ -1228,6 +1232,7 @@ void rlOrtho(double left, double right, double bottom, double top, double znear, #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); @@ -1512,6 +1517,11 @@ 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: @@ -1535,7 +1545,7 @@ void rlTextureParameters(unsigned int id, int param, int value) 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, RLGL.ExtSupported.maxAnisotropyLevel); + 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"); @@ -1778,7 +1788,12 @@ void rlSetBlendMode(int mode) 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_CUSTOM: glBlendFunc(RLGL.State.glBlendSrcFactor, RLGL.State.glBlendDstFactor); glBlendEquation(RLGL.State.glBlendEquation); 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; default: break; } @@ -2212,6 +2227,22 @@ int rlGetVersion(void) 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) { @@ -2594,6 +2625,9 @@ void rlDrawRenderBatch(rlRenderBatch *batch) 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 @@ -2676,12 +2710,12 @@ bool rlCheckRenderBatchLimit(int vCount) // Textures data management //----------------------------------------------------------------------------------------- // Convert image data to OpenGL texture (returns OpenGL valid Id) -unsigned int rlLoadTexture(void *data, int width, int height, int format, int mipmapCount) +unsigned int rlLoadTexture(const void *data, int width, int height, int format, int mipmapCount) { - glBindTexture(GL_TEXTURE_2D, 0); // Free any old binding - 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) @@ -2738,7 +2772,7 @@ unsigned int rlLoadTexture(void *data, int width, int height, int format, int mi { unsigned int mipSize = rlGetPixelDataSize(mipWidth, mipHeight, format); - int glInternalFormat, glFormat, glType; + 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); @@ -2878,7 +2912,7 @@ unsigned int rlLoadTextureDepth(int width, int height, bool useRenderBuffer) // 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(void *data, int size, int format) +unsigned int rlLoadTextureCubemap(const void *data, int size, int format) { unsigned int id = 0; @@ -2888,7 +2922,7 @@ unsigned int rlLoadTextureCubemap(void *data, int size, int format) glGenTextures(1, &id); glBindTexture(GL_TEXTURE_CUBE_MAP, id); - int glInternalFormat, glFormat, glType; + unsigned int glInternalFormat, glFormat, glType; rlGetGlTextureFormats(format, &glInternalFormat, &glFormat, &glType); if (glInternalFormat != -1) @@ -2960,22 +2994,22 @@ void rlUpdateTexture(unsigned int id, int offsetX, int offsetY, int width, int h { glBindTexture(GL_TEXTURE_2D, id); - int glInternalFormat, glFormat, glType; + unsigned int glInternalFormat, glFormat, glType; rlGetGlTextureFormats(format, &glInternalFormat, &glFormat, &glType); if ((glInternalFormat != -1) && (format < RL_PIXELFORMAT_COMPRESSED_DXT1_RGB)) { - glTexSubImage2D(GL_TEXTURE_2D, 0, offsetX, offsetY, width, height, glFormat, glType, (unsigned char *)data); + 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, int *glInternalFormat, int *glFormat, int *glType) +void rlGetGlTextureFormats(int format, unsigned int *glInternalFormat, unsigned int *glFormat, unsigned int *glType) { - *glInternalFormat = -1; - *glFormat = -1; - *glType = -1; + *glInternalFormat = 0; + *glFormat = 0; + *glType = 0; switch (format) { @@ -3081,14 +3115,11 @@ void rlGenTextureMipmaps(unsigned int id, int width, int height, int format, int #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) if ((texIsPOT) || (RLGL.ExtSupported.texNPOT)) { - //glHint(GL_GENERATE_MIPMAP_HINT, GL_DONT_CARE); // Hint for mipmaps generation algorythm: GL_FASTEST, GL_NICEST, GL_DONT_CARE + //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 - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); // Activate Trilinear filtering for mipmaps - - #define MIN(a,b) (((a)<(b))?(a):(b)) - #define MAX(a,b) (((a)>(b))?(a):(b)) + #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); @@ -3121,7 +3152,7 @@ void *rlReadTexturePixels(unsigned int id, int width, int height, int format) // GL_UNPACK_ALIGNMENT affects operations that write to OpenGL memory (glTexImage, etc.) glPixelStorei(GL_PACK_ALIGNMENT, 1); - int glInternalFormat, glFormat, glType; + unsigned int glInternalFormat, glFormat, glType; rlGetGlTextureFormats(format, &glInternalFormat, &glFormat, &glType); unsigned int size = rlGetPixelDataSize(width, height, format); @@ -3164,7 +3195,6 @@ void *rlReadTexturePixels(unsigned int id, int width, int height, int format) return pixels; } - // Read screen pixel data (color buffer) unsigned char *rlReadScreenPixels(int width, int height) { @@ -3313,7 +3343,7 @@ void rlUnloadFramebuffer(unsigned int id) // Vertex data management //----------------------------------------------------------------------------------------- // Load a new attributes buffer -unsigned int rlLoadVertexBuffer(void *buffer, int size, bool dynamic) +unsigned int rlLoadVertexBuffer(const void *buffer, int size, bool dynamic) { unsigned int id = 0; @@ -3327,7 +3357,7 @@ unsigned int rlLoadVertexBuffer(void *buffer, int size, bool dynamic) } // Load a new attributes element buffer -unsigned int rlLoadVertexBufferElement(void *buffer, int size, bool dynamic) +unsigned int rlLoadVertexBufferElement(const void *buffer, int size, bool dynamic) { unsigned int id = 0; @@ -3374,7 +3404,7 @@ void rlDisableVertexBufferElement(void) // Update vertex buffer with new data // NOTE: dataSize and offset must be provided in bytes -void rlUpdateVertexBuffer(unsigned int id, void *data, int dataSize, int offset) +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); @@ -3384,7 +3414,7 @@ void rlUpdateVertexBuffer(unsigned int id, void *data, int dataSize, int offset) // Update vertex buffer elements with new data // NOTE: dataSize and offset must be provided in bytes -void rlUpdateVertexBufferElements(unsigned int id, void *data, int dataSize, int offset) +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); @@ -3437,9 +3467,9 @@ void rlDrawVertexArray(int offset, int count) } // Draw vertex array elements -void rlDrawVertexArrayElements(int offset, int count, void *buffer) +void rlDrawVertexArrayElements(int offset, int count, const void *buffer) { - glDrawElements(GL_TRIANGLES, count, GL_UNSIGNED_SHORT, (unsigned short *)buffer + offset); + glDrawElements(GL_TRIANGLES, count, GL_UNSIGNED_SHORT, (const unsigned short *)buffer + offset); } // Draw vertex array instanced @@ -3451,10 +3481,10 @@ void rlDrawVertexArrayInstanced(int offset, int count, int instances) } // Draw vertex array elements instanced -void rlDrawVertexArrayElementsInstanced(int offset, int count, void *buffer, int instances) +void rlDrawVertexArrayElementsInstanced(int offset, int count, const void *buffer, int instances) { #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - glDrawElementsInstanced(GL_TRIANGLES, count, GL_UNSIGNED_SHORT, (unsigned short *)buffer + offset, instances); + glDrawElementsInstanced(GL_TRIANGLES, count, GL_UNSIGNED_SHORT, (const unsigned short *)buffer + offset, instances); #endif } @@ -3495,7 +3525,7 @@ unsigned int rlLoadVertexArray(void) } // Set vertex attribute -void rlSetVertexAttribute(unsigned int index, int compSize, int type, bool normalized, int stride, void *pointer) +void rlSetVertexAttribute(unsigned int index, int compSize, int type, bool normalized, int stride, const void *pointer) { #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) glVertexAttribPointer(index, compSize, type, normalized, stride, pointer); @@ -3541,56 +3571,69 @@ 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 = RLGL.State.defaultVShaderId; - unsigned int fragmentShaderId = RLGL.State.defaultFShaderId; + unsigned int vertexShaderId = 0; + unsigned int fragmentShaderId = 0; + // Compile vertex shader (if provided) if (vsCode != NULL) vertexShaderId = rlCompileShader(vsCode, GL_VERTEX_SHADER); - if (fsCode != NULL) fragmentShaderId = rlCompileShader(fsCode, GL_FRAGMENT_SHADER); + // In case no vertex shader was provided or compilation failed, we use default vertex shader + if (vertexShaderId == 0) vertexShaderId = RLGL.State.defaultVShaderId; + // Compile fragment shader (if provided) + if (fsCode != NULL) fragmentShaderId = rlCompileShader(fsCode, GL_FRAGMENT_SHADER); + // In case no fragment shader was provided or compilation failed, we use default fragment shader + if (fragmentShaderId == 0) 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 { + // 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) { - // Detach shader before deletion to make sure memory is freed glDetachShader(id, vertexShaderId); glDeleteShader(vertexShaderId); } if (fragmentShaderId != RLGL.State.defaultFShaderId) { - // Detach shader before deletion to make sure memory is freed glDetachShader(id, fragmentShaderId); glDeleteShader(fragmentShaderId); } + // In case shader program loading failed, we assign default shader if (id == 0) { - TRACELOG(RL_LOG_WARNING, "SHADER: Failed to load custom shader code"); + // 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); - // Get available shader uniforms - // NOTE: This information is useful for debug... - int uniformCount = -1; + 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; - glGetProgramiv(id, GL_ACTIVE_UNIFORMS, &uniformCount); + // Get the name of the uniforms + glGetActiveUniform(id, i, sizeof(name) - 1, &namelen, &num, &type, name); - 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)); + name[namelen] = 0; + TRACELOGD("SHADER: [ID %i] Active uniform (%s) set at location: %i", id, name, glGetUniformLocation(id, name)); + } + } + */ } #endif @@ -3629,7 +3672,7 @@ unsigned int rlCompileShader(const char *shaderCode, int type) if (maxLength > 0) { int length = 0; - char *log = RL_CALLOC(maxLength, sizeof(char)); + 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); @@ -3691,7 +3734,7 @@ unsigned int rlLoadShaderProgram(unsigned int vShaderId, unsigned int fShaderId) if (maxLength > 0) { int length = 0; - char *log = RL_CALLOC(maxLength, sizeof(char)); + 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); @@ -3858,7 +3901,7 @@ unsigned int rlLoadComputeShaderProgram(unsigned int shaderId) if (maxLength > 0) { int length = 0; - char *log = RL_CALLOC(maxLength, sizeof(char)); + 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); @@ -3894,11 +3937,13 @@ void rlComputeShaderDispatch(unsigned int groupX, unsigned int groupY, unsigned unsigned int rlLoadShaderBuffer(unsigned long long 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); + glClearBufferData(GL_SHADER_STORAGE_BUFFER, GL_R8UI, GL_RED_INTEGER, GL_UNSIGNED_BYTE, 0); + glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); #endif return ssbo; @@ -3925,7 +3970,7 @@ void rlUpdateShaderBufferElements(unsigned int id, const void *data, unsigned lo unsigned long long rlGetShaderBufferSize(unsigned int id) { long long size = 0; - + #if defined(GRAPHICS_API_OPENGL_43) glBindBuffer(GL_SHADER_STORAGE_BUFFER, id); glGetInteger64v(GL_SHADER_STORAGE_BUFFER_SIZE, &size); @@ -3965,7 +4010,7 @@ void rlCopyBuffersElements(unsigned int destId, unsigned int srcId, unsigned lon void rlBindImageTexture(unsigned int id, unsigned int index, unsigned int format, int readonly) { #if defined(GRAPHICS_API_OPENGL_43) - int glInternalFormat = 0, glFormat = 0, glType = 0; + 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); @@ -4336,7 +4381,8 @@ static void rlLoadShaderDefault(void) "} \n"; #endif - // NOTE: Compiled vertex/fragment shaders are kept for re-use + // 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 diff --git a/raylib/rmodels.c b/raylib/rmodels.c index 8218a81..ec9e7ad 100644 --- a/raylib/rmodels.c +++ b/raylib/rmodels.c @@ -4,12 +4,15 @@ * * CONFIGURATION: * +* #define SUPPORT_MODULE_RMODELS +* rmodels module is included in the build +* * #define SUPPORT_FILEFORMAT_OBJ * #define SUPPORT_FILEFORMAT_MTL * #define SUPPORT_FILEFORMAT_IQM * #define SUPPORT_FILEFORMAT_GLTF * #define SUPPORT_FILEFORMAT_VOX -* +* #define SUPPORT_FILEFORMAT_M3D * Selected desired fileformats to be supported for model data loading. * * #define SUPPORT_MESH_GENERATION @@ -19,7 +22,7 @@ * * LICENSE: zlib/libpng * -* Copyright (c) 2013-2021 Ramon Santamaria (@raysan5) +* Copyright (c) 2013-2022 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. @@ -45,6 +48,8 @@ #include "config.h" // Defines module configuration flags #endif +#if defined(SUPPORT_MODULE_RMODELS) + #include "utils.h" // Required for: TRACELOG(), LoadFileData(), LoadFileText(), SaveFileText() #include "rlgl.h" // OpenGL abstraction layer to OpenGL 1.1, 2.1, 3.3+ or ES2 #include "raymath.h" // Required for: Vector3, Quaternion and Matrix functionality @@ -82,6 +87,15 @@ #include "external/vox_loader.h" // VOX file format loading (MagikaVoxel) #endif +#if defined(SUPPORT_FILEFORMAT_M3D) + #define M3D_MALLOC RL_MALLOC + #define M3D_REALLOC RL_REALLOC + #define M3D_FREE RL_FREE + + #define M3D_IMPLEMENTATION + #include "external/m3d.h" // Model3D file format loading +#endif + #if defined(SUPPORT_MESH_GENERATION) #define PAR_MALLOC(T, N) ((T*)RL_MALLOC(N*sizeof(T))) #define PAR_CALLOC(T, N) ((T*)RL_CALLOC(N*sizeof(T), 1)) @@ -137,6 +151,10 @@ static Model LoadGLTF(const char *fileName); // Load GLTF mesh data #if defined(SUPPORT_FILEFORMAT_VOX) static Model LoadVOX(const char *filename); // Load VOX mesh data #endif +#if defined(SUPPORT_FILEFORMAT_M3D) +static Model LoadM3D(const char *filename); // Load M3D mesh data +static ModelAnimation *LoadModelAnimationsM3D(const char *fileName, unsigned int *animCount); // Load M3D animation data +#endif //---------------------------------------------------------------------------------- // Module Functions Definition @@ -196,7 +214,7 @@ void DrawCircle3D(Vector3 center, float radius, Vector3 rotationAxis, float rota // Draw a color-filled triangle (vertex in counter-clockwise order!) void DrawTriangle3D(Vector3 v1, Vector3 v2, Vector3 v3, Color color) { - rlCheckRenderBatchLimit(3); + rlCheckRenderBatchLimit(8); rlBegin(RL_TRIANGLES); rlColor4ub(color.r, color.g, color.b, color.a); @@ -918,11 +936,14 @@ Model LoadModel(const char *fileName) if (IsFileExtension(fileName, ".iqm")) model = LoadIQM(fileName); #endif #if defined(SUPPORT_FILEFORMAT_GLTF) - if (IsFileExtension(fileName, ".gltf;.glb")) model = LoadGLTF(fileName); + if (IsFileExtension(fileName, ".gltf") || IsFileExtension(fileName, ".glb")) model = LoadGLTF(fileName); #endif #if defined(SUPPORT_FILEFORMAT_VOX) if (IsFileExtension(fileName, ".vox")) model = LoadVOX(fileName); #endif +#if defined(SUPPORT_FILEFORMAT_M3D) + if (IsFileExtension(fileName, ".m3d")) model = LoadM3D(fileName); +#endif // Make sure model transform is set to identity matrix! model.transform = MatrixIdentity(); @@ -1083,7 +1104,7 @@ void UploadMesh(Mesh *mesh, bool dynamic) mesh->vaoId = rlLoadVertexArray(); rlEnableVertexArray(mesh->vaoId); - // NOTE: Attributes must be uploaded considering default locations points + // NOTE: Vertex attributes must be uploaded considering default locations points and available vertex data // Enable vertex attributes: position (shader-location = 0) void *vertices = mesh->animVertices != NULL ? mesh->animVertices : mesh->vertices; @@ -1096,6 +1117,9 @@ void UploadMesh(Mesh *mesh, bool dynamic) rlSetVertexAttribute(1, 2, RL_FLOAT, 0, 0, 0); rlEnableVertexAttribute(1); + // WARNING: When setting default vertex attribute values, the values for each generic vertex attribute + // is part of current state and it is maintained even if a different program object is used + if (mesh->normals != NULL) { // Enable vertex attributes: normals (shader-location = 2) @@ -1106,7 +1130,8 @@ void UploadMesh(Mesh *mesh, bool dynamic) } else { - // Default color vertex attribute set to WHITE + // Default vertex attribute: normal + // WARNING: Default value provided to shader if location available float value[3] = { 1.0f, 1.0f, 1.0f }; rlSetVertexAttributeDefault(2, value, SHADER_ATTRIB_VEC3, 3); rlDisableVertexAttribute(2); @@ -1121,8 +1146,9 @@ void UploadMesh(Mesh *mesh, bool dynamic) } else { - // Default color vertex attribute set to WHITE - float value[4] = { 1.0f, 1.0f, 1.0f, 1.0f }; + // Default vertex attribute: color + // WARNING: Default value provided to shader if location available + float value[4] = { 1.0f, 1.0f, 1.0f, 1.0f }; // WHITE rlSetVertexAttributeDefault(3, value, SHADER_ATTRIB_VEC4, 4); rlDisableVertexAttribute(3); } @@ -1136,7 +1162,8 @@ void UploadMesh(Mesh *mesh, bool dynamic) } else { - // Default tangents vertex attribute + // Default vertex attribute: tangent + // WARNING: Default value provided to shader if location available float value[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; rlSetVertexAttributeDefault(4, value, SHADER_ATTRIB_VEC4, 4); rlDisableVertexAttribute(4); @@ -1151,7 +1178,8 @@ void UploadMesh(Mesh *mesh, bool dynamic) } else { - // Default texcoord2 vertex attribute + // Default vertex attribute: texcoord2 + // WARNING: Default value provided to shader if location available float value[2] = { 0.0f, 0.0f }; rlSetVertexAttributeDefault(5, value, SHADER_ATTRIB_VEC2, 2); rlDisableVertexAttribute(5); @@ -1170,7 +1198,7 @@ void UploadMesh(Mesh *mesh, bool dynamic) } // Update mesh vertex data in GPU for a specific buffer index -void UpdateMeshBuffer(Mesh mesh, int index, void *data, int dataSize, int offset) +void UpdateMeshBuffer(Mesh mesh, int index, const void *data, int dataSize, int offset) { rlUpdateVertexBuffer(mesh.vboId[index], data, dataSize, offset); } @@ -1289,8 +1317,10 @@ void DrawMesh(Mesh mesh, Material material, Matrix transform) } } - // Try binding vertex array objects (VAO) - // or use VBOs if not possible + // Try binding vertex array objects (VAO) or use VBOs if not possible + // WARNING: UploadMesh() enables all vertex attributes available in mesh and sets default attribute values + // for shader expected vertex attributes that are not provided by the mesh (i.e. colors) + // This could be a dangerous approach because different meshes with different shaders can enable/disable some attributes if (!rlEnableVertexArray(mesh.vaoId)) { // Bind mesh VBO data: vertex position (shader-location = 0) @@ -1322,10 +1352,10 @@ void DrawMesh(Mesh mesh, Material material, Matrix transform) } else { - // Set default value for unused attribute - // NOTE: Required when using default shader and no VAO support + // Set default value for defined vertex attribute in shader but not provided by mesh + // WARNING: It could result in GPU undefined behaviour float value[4] = { 1.0f, 1.0f, 1.0f, 1.0f }; - rlSetVertexAttributeDefault(material.shader.locs[SHADER_LOC_VERTEX_COLOR], value, SHADER_ATTRIB_VEC2, 4); + rlSetVertexAttributeDefault(material.shader.locs[SHADER_LOC_VERTEX_COLOR], value, SHADER_ATTRIB_VEC4, 4); rlDisableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_COLOR]); } } @@ -1349,6 +1379,9 @@ void DrawMesh(Mesh mesh, Material material, Matrix transform) if (mesh.indices != NULL) rlEnableVertexBufferElement(mesh.vboId[6]); } + // WARNING: Disable vertex attribute color input if mesh can not provide that data (despite location being enabled in shader) + if (mesh.vboId[3] == 0) rlDisableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_COLOR]); + int eyeCount = 1; if (rlIsStereoRenderEnabled()) eyeCount = 2; @@ -1375,14 +1408,17 @@ void DrawMesh(Mesh mesh, Material material, Matrix transform) // Unbind all binded texture maps for (int i = 0; i < MAX_MATERIAL_MAPS; i++) { - // Select current shader texture slot - rlActiveTextureSlot(i); + if (material.maps[i].texture.id > 0) + { + // Select current shader texture slot + rlActiveTextureSlot(i); - // Disable texture for active slot - if ((i == MATERIAL_MAP_IRRADIANCE) || - (i == MATERIAL_MAP_PREFILTER) || - (i == MATERIAL_MAP_CUBEMAP)) rlDisableTextureCubemap(); - else rlDisableTexture(); + // Disable texture for active slot + if ((i == MATERIAL_MAP_IRRADIANCE) || + (i == MATERIAL_MAP_PREFILTER) || + (i == MATERIAL_MAP_CUBEMAP)) rlDisableTextureCubemap(); + else rlDisableTexture(); + } } // Disable all possible vertex array objects (or VBOs) @@ -1400,7 +1436,7 @@ void DrawMesh(Mesh mesh, Material material, Matrix transform) } // Draw multiple mesh instances with material and different transforms -void DrawMeshInstanced(Mesh mesh, Material material, Matrix *transforms, int instances) +void DrawMeshInstanced(Mesh mesh, Material material, const Matrix *transforms, int instances) { #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) // Instancing required variables @@ -1540,7 +1576,7 @@ void DrawMeshInstanced(Mesh mesh, Material material, Matrix *transforms, int ins // Set default value for unused attribute // NOTE: Required when using default shader and no VAO support float value[4] = { 1.0f, 1.0f, 1.0f, 1.0f }; - rlSetVertexAttributeDefault(material.shader.locs[SHADER_LOC_VERTEX_COLOR], value, SHADER_ATTRIB_VEC2, 4); + rlSetVertexAttributeDefault(material.shader.locs[SHADER_LOC_VERTEX_COLOR], value, SHADER_ATTRIB_VEC4, 4); rlDisableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_COLOR]); } } @@ -1564,6 +1600,9 @@ void DrawMeshInstanced(Mesh mesh, Material material, Matrix *transforms, int ins if (mesh.indices != NULL) rlEnableVertexBufferElement(mesh.vboId[6]); } + // WARNING: Disable vertex attribute color input if mesh can not provide that data (despite location being enabled in shader) + if (mesh.vboId[3] == 0) rlDisableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_COLOR]); + int eyeCount = 1; if (rlIsStereoRenderEnabled()) eyeCount = 2; @@ -1590,14 +1629,17 @@ void DrawMeshInstanced(Mesh mesh, Material material, Matrix *transforms, int ins // Unbind all binded texture maps for (int i = 0; i < MAX_MATERIAL_MAPS; i++) { - // Select current shader texture slot - rlActiveTextureSlot(i); + if (material.maps[i].texture.id > 0) + { + // Select current shader texture slot + rlActiveTextureSlot(i); - // Disable texture for active slot - if ((i == MATERIAL_MAP_IRRADIANCE) || - (i == MATERIAL_MAP_PREFILTER) || - (i == MATERIAL_MAP_CUBEMAP)) rlDisableTextureCubemap(); - else rlDisableTexture(); + // Disable texture for active slot + if ((i == MATERIAL_MAP_IRRADIANCE) || + (i == MATERIAL_MAP_PREFILTER) || + (i == MATERIAL_MAP_CUBEMAP)) rlDisableTextureCubemap(); + else rlDisableTexture(); + } } // Disable all possible vertex array objects (or VBOs) @@ -1620,7 +1662,7 @@ void UnloadMesh(Mesh mesh) // Unload rlgl mesh vboId data rlUnloadVertexArray(mesh.vaoId); - for (int i = 0; i < MAX_MESH_VERTEX_BUFFERS; i++) rlUnloadVertexBuffer(mesh.vboId[i]); + if (mesh.vboId != NULL) for (int i = 0; i < MAX_MESH_VERTEX_BUFFERS; i++) rlUnloadVertexBuffer(mesh.vboId[i]); RL_FREE(mesh.vboId); RL_FREE(mesh.vertices); @@ -1645,13 +1687,13 @@ bool ExportMesh(Mesh mesh, const char *fileName) if (IsFileExtension(fileName, ".obj")) { // Estimated data size, it should be enough... - int dataSize = mesh.vertexCount/3* (int)strlen("v 0000.00f 0000.00f 0000.00f") + - mesh.vertexCount/2* (int)strlen("vt 0.000f 0.00f") + - mesh.vertexCount/3* (int)strlen("vn 0.000f 0.00f 0.00f") + - mesh.triangleCount/3* (int)strlen("f 00000/00000/00000 00000/00000/00000 00000/00000/00000"); + int dataSize = mesh.vertexCount*(int)strlen("v 0000.00f 0000.00f 0000.00f") + + mesh.vertexCount*(int)strlen("vt 0.000f 0.00f") + + mesh.vertexCount*(int)strlen("vn 0.000f 0.00f 0.00f") + + mesh.triangleCount*(int)strlen("f 00000/00000/00000 00000/00000/00000 00000/00000/00000"); // NOTE: Text data buffer size is estimated considering mesh data size - char *txtData = (char *)RL_CALLOC(dataSize + 2000, sizeof(char)); + char *txtData = (char *)RL_CALLOC(dataSize*2 + 2000, sizeof(char)); int byteCount = 0; byteCount += sprintf(txtData + byteCount, "# //////////////////////////////////////////////////////////////////////////////////\n"); @@ -1661,7 +1703,7 @@ bool ExportMesh(Mesh mesh, const char *fileName) byteCount += sprintf(txtData + byteCount, "# // more info and bugs-report: github.com/raysan5/raylib //\n"); byteCount += sprintf(txtData + byteCount, "# // feedback and support: ray[at]raylib.com //\n"); byteCount += sprintf(txtData + byteCount, "# // //\n"); - byteCount += sprintf(txtData + byteCount, "# // Copyright (c) 2018 Ramon Santamaria (@raysan5) //\n"); + byteCount += sprintf(txtData + byteCount, "# // Copyright (c) 2018-2022 Ramon Santamaria (@raysan5) //\n"); byteCount += sprintf(txtData + byteCount, "# // //\n"); byteCount += sprintf(txtData + byteCount, "# //////////////////////////////////////////////////////////////////////////////////\n\n"); byteCount += sprintf(txtData + byteCount, "# Vertex Count: %i\n", mesh.vertexCount); @@ -1684,9 +1726,22 @@ bool ExportMesh(Mesh mesh, const char *fileName) byteCount += sprintf(txtData + byteCount, "vn %.3f %.3f %.3f\n", mesh.normals[v], mesh.normals[v + 1], mesh.normals[v + 2]); } - for (int i = 0; i < mesh.triangleCount; i += 3) + if (mesh.indices != NULL) { - byteCount += sprintf(txtData + byteCount, "f %i/%i/%i %i/%i/%i %i/%i/%i\n", i, i, i, i + 1, i + 1, i + 1, i + 2, i + 2, i + 2); + for (int i = 0, v = 0; i < mesh.triangleCount; i++, v += 3) + { + byteCount += sprintf(txtData + byteCount, "f %i/%i/%i %i/%i/%i %i/%i/%i\n", + mesh.indices[v] + 1, mesh.indices[v] + 1, mesh.indices[v] + 1, + mesh.indices[v + 1] + 1, mesh.indices[v + 1] + 1, mesh.indices[v + 1] + 1, + mesh.indices[v + 2] + 1, mesh.indices[v + 2] + 1, mesh.indices[v + 2] + 1); + } + } + else + { + for (int i = 0, v = 1; i < mesh.triangleCount; i++, v += 3) + { + byteCount += sprintf(txtData + byteCount, "f %i/%i/%i %i/%i/%i %i/%i/%i\n", v, v, v, v + 1, v + 1, v + 1, v + 2, v + 2, v + 2); + } } byteCount += sprintf(txtData + byteCount, "\n"); @@ -1704,7 +1759,6 @@ bool ExportMesh(Mesh mesh, const char *fileName) return success; } - // Load materials from model file Material *LoadMaterials(const char *fileName, int *materialCount) { @@ -1771,9 +1825,12 @@ void UnloadMaterial(Material material) if (material.shader.id != rlGetShaderIdDefault()) UnloadShader(material.shader); // Unload loaded texture maps (avoid unloading default texture, managed by raylib) - for (int i = 0; i < MAX_MATERIAL_MAPS; i++) + if (material.maps != NULL) { - if (material.maps[i].texture.id != rlGetTextureIdDefault()) rlUnloadTexture(material.maps[i].texture.id); + for (int i = 0; i < MAX_MATERIAL_MAPS; i++) + { + if (material.maps[i].texture.id != rlGetTextureIdDefault()) rlUnloadTexture(material.maps[i].texture.id); + } } RL_FREE(material.maps); @@ -1802,6 +1859,9 @@ ModelAnimation *LoadModelAnimations(const char *fileName, unsigned int *animCoun #if defined(SUPPORT_FILEFORMAT_IQM) if (IsFileExtension(fileName, ".iqm")) animations = LoadModelAnimationsIQM(fileName, animCount); #endif +#if defined(SUPPORT_FILEFORMAT_M3D) + if (IsFileExtension(fileName, ".m3d")) animations = LoadModelAnimationsM3D(fileName, animCount); +#endif #if defined(SUPPORT_FILEFORMAT_GLTF) //if (IsFileExtension(fileName, ".gltf;.glb")) animations = LoadModelAnimationGLTF(fileName, animCount); #endif @@ -1911,7 +1971,7 @@ void UpdateModelAnimation(Model model, ModelAnimation anim, int frame) } // Unload animation array data -void UnloadModelAnimations(ModelAnimation* animations, unsigned int count) +void UnloadModelAnimations(ModelAnimation *animations, unsigned int count) { for (unsigned int i = 0; i < count; i++) UnloadModelAnimation(animations[i]); RL_FREE(animations); @@ -2249,14 +2309,14 @@ Mesh GenMeshCube(float width, float height, float length) int k = 0; // Indices can be initialized right now - for (int i = 0; i < 36; i+=6) + for (int i = 0; i < 36; i += 6) { mesh.indices[i] = 4*k; - mesh.indices[i+1] = 4*k+1; - mesh.indices[i+2] = 4*k+2; - mesh.indices[i+3] = 4*k; - mesh.indices[i+4] = 4*k+2; - mesh.indices[i+5] = 4*k+3; + mesh.indices[i + 1] = 4*k + 1; + mesh.indices[i + 2] = 4*k + 2; + mesh.indices[i + 3] = 4*k; + mesh.indices[i + 4] = 4*k + 2; + mesh.indices[i + 5] = 4*k + 3; k++; } @@ -2412,13 +2472,13 @@ Mesh GenMeshCylinder(float radius, float height, int slices) par_shapes_mesh *cylinder = par_shapes_create_cylinder(slices, 8); par_shapes_scale(cylinder, radius, radius, height); par_shapes_rotate(cylinder, -PI/2.0f, (float[]){ 1, 0, 0 }); - par_shapes_rotate(cylinder, PI/2.0f, (float[]){ 0, 1, 0 }); // Generate an orientable disk shape (top cap) par_shapes_mesh *capTop = par_shapes_create_disk(radius, slices, (float[]){ 0, 0, 0 }, (float[]){ 0, 0, 1 }); capTop->tcoords = PAR_MALLOC(float, 2*capTop->npoints); for (int i = 0; i < 2*capTop->npoints; i++) capTop->tcoords[i] = 0.0f; par_shapes_rotate(capTop, -PI/2.0f, (float[]){ 1, 0, 0 }); + par_shapes_rotate(capTop, 90*DEG2RAD, (float[]){ 0, 1, 0 }); par_shapes_translate(capTop, 0, height, 0); // Generate an orientable disk shape (bottom cap) @@ -2426,6 +2486,7 @@ Mesh GenMeshCylinder(float radius, float height, int slices) capBottom->tcoords = PAR_MALLOC(float, 2*capBottom->npoints); for (int i = 0; i < 2*capBottom->npoints; i++) capBottom->tcoords[i] = 0.95f; par_shapes_rotate(capBottom, PI/2.0f, (float[]){ 1, 0, 0 }); + par_shapes_rotate(capBottom, -90*DEG2RAD, (float[]){ 0, 1, 0 }); par_shapes_merge_and_free(cylinder, capTop); par_shapes_merge_and_free(cylinder, capBottom); @@ -2610,7 +2671,7 @@ Mesh GenMeshKnot(float radius, float size, int radSeg, int sides) // NOTE: Vertex data is uploaded to GPU Mesh GenMeshHeightmap(Image heightmap, Vector3 size) { - #define GRAY_VALUE(c) ((c.r+c.g+c.b)/3) + #define GRAY_VALUE(c) ((c.r+c.g+c.b)/3.0f) Mesh mesh = { 0 }; @@ -2633,8 +2694,6 @@ Mesh GenMeshHeightmap(Image heightmap, Vector3 size) int tcCounter = 0; // Used to count texcoords float by float int nCounter = 0; // Used to count normals float by float - int trisCounter = 0; - Vector3 scaleFactor = { size.x/mapX, size.y/255.0f, size.z/mapZ }; Vector3 vA = { 0 }; @@ -2651,15 +2710,15 @@ Mesh GenMeshHeightmap(Image heightmap, Vector3 size) // one triangle - 3 vertex mesh.vertices[vCounter] = (float)x*scaleFactor.x; - mesh.vertices[vCounter + 1] = (float)GRAY_VALUE(pixels[x + z*mapX])*scaleFactor.y; + mesh.vertices[vCounter + 1] = GRAY_VALUE(pixels[x + z*mapX])*scaleFactor.y; mesh.vertices[vCounter + 2] = (float)z*scaleFactor.z; mesh.vertices[vCounter + 3] = (float)x*scaleFactor.x; - mesh.vertices[vCounter + 4] = (float)GRAY_VALUE(pixels[x + (z + 1)*mapX])*scaleFactor.y; + mesh.vertices[vCounter + 4] = GRAY_VALUE(pixels[x + (z + 1)*mapX])*scaleFactor.y; mesh.vertices[vCounter + 5] = (float)(z + 1)*scaleFactor.z; mesh.vertices[vCounter + 6] = (float)(x + 1)*scaleFactor.x; - mesh.vertices[vCounter + 7] = (float)GRAY_VALUE(pixels[(x + 1) + z*mapX])*scaleFactor.y; + mesh.vertices[vCounter + 7] = GRAY_VALUE(pixels[(x + 1) + z*mapX])*scaleFactor.y; mesh.vertices[vCounter + 8] = (float)z*scaleFactor.z; // another triangle - 3 vertex @@ -2672,7 +2731,7 @@ Mesh GenMeshHeightmap(Image heightmap, Vector3 size) mesh.vertices[vCounter + 14] = mesh.vertices[vCounter + 5]; mesh.vertices[vCounter + 15] = (float)(x + 1)*scaleFactor.x; - mesh.vertices[vCounter + 16] = (float)GRAY_VALUE(pixels[(x + 1) + (z + 1)*mapX])*scaleFactor.y; + mesh.vertices[vCounter + 16] = GRAY_VALUE(pixels[(x + 1) + (z + 1)*mapX])*scaleFactor.y; mesh.vertices[vCounter + 17] = (float)(z + 1)*scaleFactor.z; vCounter += 18; // 6 vertex, 18 floats @@ -2729,7 +2788,6 @@ Mesh GenMeshHeightmap(Image heightmap, Vector3 size) } nCounter += 18; // 6 vertex, 18 floats - trisCounter += 2; } } @@ -3223,19 +3281,6 @@ void GenMeshTangents(Mesh *mesh) TRACELOG(LOG_INFO, "MESH: Tangents data computed and uploaded for provided mesh"); } -// Compute mesh binormals (aka bitangent) -void GenMeshBinormals(Mesh *mesh) -{ - for (int i = 0; i < mesh->vertexCount; i++) - { - //Vector3 normal = { mesh->normals[i*3 + 0], mesh->normals[i*3 + 1], mesh->normals[i*3 + 2] }; - //Vector3 tangent = { mesh->tangents[i*4 + 0], mesh->tangents[i*4 + 1], mesh->tangents[i*4 + 2] }; - //Vector3 binormal = Vector3Scale(Vector3CrossProduct(normal, tangent), mesh->tangents[i*4 + 3]); - - // TODO: Register computed binormal in mesh->binormal? - } -} - // Draw a model (with texture if set) void DrawModel(Model model, Vector3 position, float scale, Color tint) { @@ -3264,10 +3309,10 @@ void DrawModelEx(Model model, Vector3 position, Vector3 rotationAxis, float rota Color color = model.materials[model.meshMaterial[i]].maps[MATERIAL_MAP_DIFFUSE].color; Color colorTint = WHITE; - colorTint.r = (unsigned char)((((float)color.r/255.0)*((float)tint.r/255.0))*255.0f); - colorTint.g = (unsigned char)((((float)color.g/255.0)*((float)tint.g/255.0))*255.0f); - colorTint.b = (unsigned char)((((float)color.b/255.0)*((float)tint.b/255.0))*255.0f); - colorTint.a = (unsigned char)((((float)color.a/255.0)*((float)tint.a/255.0))*255.0f); + colorTint.r = (unsigned char)((((float)color.r/255.0f)*((float)tint.r/255.0f))*255.0f); + colorTint.g = (unsigned char)((((float)color.g/255.0f)*((float)tint.g/255.0f))*255.0f); + colorTint.b = (unsigned char)((((float)color.b/255.0f)*((float)tint.b/255.0f))*255.0f); + colorTint.a = (unsigned char)((((float)color.a/255.0f)*((float)tint.a/255.0f))*255.0f); model.materials[model.meshMaterial[i]].maps[MATERIAL_MAP_DIFFUSE].color = colorTint; DrawMesh(model.meshes[i], model.materials[model.meshMaterial[i]], model.transform); @@ -3315,7 +3360,7 @@ void DrawBillboardRec(Camera camera, Texture2D texture, Rectangle source, Vector void DrawBillboardPro(Camera camera, Texture2D texture, Rectangle source, Vector3 position, Vector3 up, Vector2 size, Vector2 origin, float rotation, Color tint) { // NOTE: Billboard size will maintain source rectangle aspect ratio, size will represent billboard width - Vector2 sizeRatio = { size.y, size.x*(float)source.height/source.width }; + Vector2 sizeRatio = { size.x*(float)source.width/source.height, size.y }; Matrix matView = MatrixLookAt(camera.position, camera.target, camera.up); @@ -3376,7 +3421,7 @@ void DrawBillboardPro(Camera camera, Texture2D texture, Rectangle source, Vector bottomRight = Vector3Add(bottomRight, position); bottomLeft = Vector3Add(bottomLeft, position); - rlCheckRenderBatchLimit(4); + rlCheckRenderBatchLimit(8); rlSetTexture(texture.id); @@ -3620,31 +3665,12 @@ RayCollision GetRayCollisionMesh(Ray ray, Mesh mesh, Matrix transform) return collision; } -// Get collision info between ray and model -RayCollision GetRayCollisionModel(Ray ray, Model model) -{ - RayCollision collision = { 0 }; - - for (int m = 0; m < model.meshCount; m++) - { - RayCollision meshHitInfo = GetRayCollisionMesh(ray, model.meshes[m], model.transform); - - if (meshHitInfo.hit) - { - // Save the closest hit mesh - if ((!collision.hit) || (collision.distance > meshHitInfo.distance)) collision = meshHitInfo; - } - } - - return collision; -} - // Get collision info between ray and triangle // NOTE: The points are expected to be in counter-clockwise winding // NOTE: Based on https://en.wikipedia.org/wiki/M%C3%B6ller%E2%80%93Trumbore_intersection_algorithm RayCollision GetRayCollisionTriangle(Ray ray, Vector3 p1, Vector3 p2, Vector3 p3) { - #define EPSILON 0.000001 // A small number + #define EPSILON 0.000001f // A small number RayCollision collision = { 0 }; Vector3 edge1 = { 0 }; @@ -4281,7 +4307,7 @@ static Model LoadIQM(const char *fileName) } // Load IQM animation data -static ModelAnimation* LoadModelAnimationsIQM(const char *fileName, unsigned int *animCount) +static ModelAnimation *LoadModelAnimationsIQM(const char *fileName, unsigned int *animCount) { #define IQM_MAGIC "INTERQUAKEMODEL" // IQM file magic number #define IQM_VERSION 2 // only IQM version 2 supported @@ -4523,7 +4549,7 @@ static Image LoadImageFromCgltfImage(cgltf_image *cgltfImage, const char *texPat cgltf_options options = { 0 }; cgltf_result result = cgltf_load_buffer_base64(&options, outSize, cgltfImage->uri + i + 1, &data); - + if (result == cgltf_result_success) { image = LoadImageFromMemory(".png", (unsigned char *)data, outSize); @@ -4551,12 +4577,12 @@ static Image LoadImageFromCgltfImage(cgltf_image *cgltfImage, const char *texPat // Check mime_type for image: (cgltfImage->mime_type == "image/png") // NOTE: Detected that some models define mime_type as "image\\/png" - if ((strcmp(cgltfImage->mime_type, "image\\/png") == 0) || + if ((strcmp(cgltfImage->mime_type, "image\\/png") == 0) || (strcmp(cgltfImage->mime_type, "image/png") == 0)) image = LoadImageFromMemory(".png", data, (int)cgltfImage->buffer_view->size); else if ((strcmp(cgltfImage->mime_type, "image\\/jpeg") == 0) || (strcmp(cgltfImage->mime_type, "image/jpeg") == 0)) image = LoadImageFromMemory(".jpg", data, (int)cgltfImage->buffer_view->size); else TRACELOG(LOG_WARNING, "MODEL: glTF image data MIME type not recognized", TextFormat("%s/%s", texPath, cgltfImage->uri)); - + RL_FREE(data); } @@ -4617,7 +4643,7 @@ static Model LoadGLTF(const char *fileName) cgltf_options options = { 0 }; cgltf_data *data = NULL; cgltf_result result = cgltf_parse(&options, fileData, dataSize, &data); - + if (result == cgltf_result_success) { if (data->file_type == cgltf_file_type_glb) TRACELOG(LOG_INFO, "MODEL: [%s] Model basic data (glb) loaded successfully", fileName); @@ -4642,10 +4668,9 @@ static Model LoadGLTF(const char *fileName) // Load our model data: meshes and materials model.meshCount = primitivesCount; model.meshes = RL_CALLOC(model.meshCount, sizeof(Mesh)); - for (int i = 0; i < model.meshCount; i++) model.meshes[i].vboId = (unsigned int*)RL_CALLOC(MAX_MESH_VERTEX_BUFFERS, sizeof(unsigned int)); // NOTE: We keep an extra slot for default material, in case some mesh requires it - model.materialCount = (int)data->materials_count + 1; + model.materialCount = (int)data->materials_count + 1; model.materials = RL_CALLOC(model.materialCount, sizeof(Material)); model.materials[0] = LoadMaterialDefault(); // Load default material (index: 0) @@ -4672,13 +4697,12 @@ static Model LoadGLTF(const char *fileName) model.materials[j].maps[MATERIAL_MAP_ALBEDO].texture = LoadTextureFromImage(imAlbedo); UnloadImage(imAlbedo); } - - // Load base color factor (tint) - model.materials[j].maps[MATERIAL_MAP_ALBEDO].color.r = (unsigned char)(data->materials[i].pbr_metallic_roughness.base_color_factor[0]*255); - model.materials[j].maps[MATERIAL_MAP_ALBEDO].color.g = (unsigned char)(data->materials[i].pbr_metallic_roughness.base_color_factor[1]*255); - model.materials[j].maps[MATERIAL_MAP_ALBEDO].color.b = (unsigned char)(data->materials[i].pbr_metallic_roughness.base_color_factor[2]*255); - model.materials[j].maps[MATERIAL_MAP_ALBEDO].color.a = (unsigned char)(data->materials[i].pbr_metallic_roughness.base_color_factor[3]*255); } + // Load base color factor (tint) + model.materials[j].maps[MATERIAL_MAP_ALBEDO].color.r = (unsigned char)(data->materials[i].pbr_metallic_roughness.base_color_factor[0]*255); + model.materials[j].maps[MATERIAL_MAP_ALBEDO].color.g = (unsigned char)(data->materials[i].pbr_metallic_roughness.base_color_factor[1]*255); + model.materials[j].maps[MATERIAL_MAP_ALBEDO].color.b = (unsigned char)(data->materials[i].pbr_metallic_roughness.base_color_factor[2]*255); + model.materials[j].maps[MATERIAL_MAP_ALBEDO].color.a = (unsigned char)(data->materials[i].pbr_metallic_roughness.base_color_factor[3]*255); // Load metallic/roughness texture if (data->materials[i].pbr_metallic_roughness.metallic_roughness_texture.texture) @@ -4689,8 +4713,8 @@ static Model LoadGLTF(const char *fileName) model.materials[j].maps[MATERIAL_MAP_ROUGHNESS].texture = LoadTextureFromImage(imMetallicRoughness); UnloadImage(imMetallicRoughness); } - - // Load metallic/roughness material properties + + // Load metallic/roughness material properties float roughness = data->materials[i].pbr_metallic_roughness.roughness_factor; model.materials[j].maps[MATERIAL_MAP_ROUGHNESS].value = roughness; @@ -4738,7 +4762,7 @@ static Model LoadGLTF(const char *fileName) } } - // Other possible materials not supported by raylib pipeline: + // Other possible materials not supported by raylib pipeline: // has_clearcoat, has_transmission, has_volume, has_ior, has specular, has_sheen } @@ -4768,7 +4792,7 @@ static Model LoadGLTF(const char *fileName) if ((attribute->component_type == cgltf_component_type_r_32f) && (attribute->type == cgltf_type_vec3)) { - // Init raylib mesh vertices to copy glTF attribute data + // Init raylib mesh vertices to copy glTF attribute data model.meshes[meshIndex].vertexCount = (int)attribute->count; model.meshes[meshIndex].vertices = RL_MALLOC(attribute->count*3*sizeof(float)); @@ -4845,7 +4869,7 @@ static Model LoadGLTF(const char *fileName) LOAD_ATTRIBUTE(attribute, 4, unsigned short, temp); // Convert data to raylib color data type (4 bytes) - for (int c = 0; c < attribute->count*4; c++) model.meshes[meshIndex].colors[c] = (unsigned char)(((float)temp[c]/65535.0f)*255.0f); + for (unsigned int c = 0; c < attribute->count*4; c++) model.meshes[meshIndex].colors[c] = (unsigned char)(((float)temp[c]/65535.0f)*255.0f); RL_FREE(temp); } @@ -4859,7 +4883,7 @@ static Model LoadGLTF(const char *fileName) LOAD_ATTRIBUTE(attribute, 4, float, temp); // Convert data to raylib color data type (4 bytes), we expect the color data normalized - for (int c = 0; c < attribute->count*4; c++) model.meshes[meshIndex].colors[c] = (unsigned char)(temp[c]*255.0f); + for (unsigned int c = 0; c < attribute->count*4; c++) model.meshes[meshIndex].colors[c] = (unsigned char)(temp[c]*255.0f); RL_FREE(temp); } @@ -4894,7 +4918,7 @@ static Model LoadGLTF(const char *fileName) LOAD_ATTRIBUTE(attribute, 1, unsigned int, temp); // Convert data to raylib indices data type (unsigned short) - for (int d = 0; d < attribute->count; d++) model.meshes[meshIndex].indices[d] = (unsigned short)temp[d]; + for (unsigned int d = 0; d < attribute->count; d++) model.meshes[meshIndex].indices[d] = (unsigned short)temp[d]; TRACELOG(LOG_WARNING, "MODEL: [%s] Indices data converted from u32 to u16, possible loss of data", fileName); @@ -4906,7 +4930,7 @@ static Model LoadGLTF(const char *fileName) // Assign to the primitive mesh the corresponding material index // NOTE: If no material defined, mesh uses the already assigned default material (index: 0) - for (int m = 0; m < data->materials_count; m++) + for (unsigned int m = 0; m < data->materials_count; m++) { // The primitive actually keeps the pointer to the corresponding material, // raylib instead assigns to the mesh the by its index, as loaded in model.materials array @@ -4923,6 +4947,63 @@ static Model LoadGLTF(const char *fileName) } } +/* + // TODO: Load glTF meshes animation data + // REF: https://www.khronos.org/registry/glTF/specs/2.0/glTF-2.0.html#skins + // REF: https://www.khronos.org/registry/glTF/specs/2.0/glTF-2.0.html#skinned-mesh-attributes + //---------------------------------------------------------------------------------------------------- + for (unsigned int i = 0, meshIndex = 0; i < data->meshes_count; i++) + { + for (unsigned int p = 0; p < data->meshes[i].primitives_count; p++) + { + // NOTE: We only support primitives defined by triangles + if (data->meshes[i].primitives[p].type != cgltf_primitive_type_triangles) continue; + + for (unsigned int j = 0; j < data->meshes[i].primitives[p].attributes_count; j++) + { + // NOTE: JOINTS_1 + WEIGHT_1 will be used for +4 joints influencing a vertex -> Not supported by raylib + + if (data->meshes[i].primitives[p].attributes[j].type == cgltf_attribute_type_joints) // JOINTS_n (vec4: 4 bones max per vertex / u8, u16) + { + cgltf_accessor *attribute = data->meshes[i].primitives[p].attributes[j].data; + + if ((attribute->component_type == cgltf_component_type_r_8u) && (attribute->type == cgltf_type_vec4)) + { + // Init raylib mesh bone ids to copy glTF attribute data + model.meshes[meshIndex].boneIds = RL_CALLOC(model.meshes[meshIndex].vertexCount*4, sizeof(unsigned char)); + + // Load 4 components of unsigned char data type into mesh.boneIds + // TODO: It seems LOAD_ATTRIBUTE() macro does not work as expected in some cases, + // for cgltf_attribute_type_joints we have: + // - data.meshes[0] (256 vertices) + // - 256 values, provided as cgltf_type_vec4 of bytes (4 byte per joint, stride 4) + LOAD_ATTRIBUTE(attribute, 4, unsigned char, model.meshes[meshIndex].boneIds) + } + else TRACELOG(LOG_WARNING, "MODEL: [%s] Joint attribute data format not supported, use vec4 u8", fileName); + } + else if (data->meshes[i].primitives[p].attributes[j].type == cgltf_attribute_type_weights) // WEIGHTS_n (vec4 / u8, u16, f32) + { + cgltf_accessor *attribute = data->meshes[i].primitives[p].attributes[j].data; + + if ((attribute->component_type == cgltf_component_type_r_32f) && (attribute->type == cgltf_type_vec4)) + { + // Init raylib mesh bone weight to copy glTF attribute data + model.meshes[meshIndex].boneWeights = RL_CALLOC(model.meshes[meshIndex].vertexCount*4, sizeof(float)); + + // Load 4 components of float data type into mesh.boneWeights + // for cgltf_attribute_type_weights we have: + // - data.meshes[0] (256 vertices) + // - 256 values, provided as cgltf_type_vec4 of float (4 byte per joint, stride 16) + LOAD_ATTRIBUTE(attribute, 4, float, model.meshes[meshIndex].boneWeights) + } + else TRACELOG(LOG_WARNING, "MODEL: [%s] Joint weight attribute data format not supported, use vec4 float", fileName); + } + } + + meshIndex++; // Move to next mesh + } + } +*/ // Free all cgltf loaded data cgltf_free(data); } @@ -5039,3 +5120,360 @@ static Model LoadVOX(const char *fileName) return model; } #endif + +#if defined(SUPPORT_FILEFORMAT_M3D) +// Hook LoadFileData()/UnloadFileData() calls to M3D loaders +unsigned char *m3d_loaderhook(char *fn, unsigned int *len) { return LoadFileData((const char *)fn, len); } +void m3d_freehook(void *data) { UnloadFileData((unsigned char *)data); } + +// Load M3D mesh data +static Model LoadM3D(const char *fileName) +{ + Model model = { 0 }; + + m3d_t *m3d = NULL; + m3dp_t *prop = NULL; + unsigned int bytesRead = 0; + unsigned char *fileData = LoadFileData(fileName, &bytesRead); + int i, j, k, l, n, mi = -2; + + if (fileData != NULL) + { + m3d = m3d_load(fileData, m3d_loaderhook, m3d_freehook, NULL); + + if (!m3d || M3D_ERR_ISFATAL(m3d->errcode)) + { + TRACELOG(LOG_WARNING, "MODEL: [%s] Failed to load M3D data, error code %d", fileName, m3d ? m3d->errcode : -2); + if (m3d) m3d_free(m3d); + UnloadFileData(fileData); + return model; + } + else TRACELOG(LOG_INFO, "MODEL: [%s] M3D data loaded successfully: %i faces/%i materials", fileName, m3d->numface, m3d->nummaterial); + + // no face? this is probably just a material library + if (!m3d->numface) + { + m3d_free(m3d); + UnloadFileData(fileData); + return model; + } + + if (m3d->nummaterial > 0) + { + model.meshCount = model.materialCount = m3d->nummaterial; + TRACELOG(LOG_INFO, "MODEL: model has %i material meshes", model.materialCount); + } + else + { + model.meshCount = model.materialCount = 1; + TRACELOG(LOG_INFO, "MODEL: No materials, putting all meshes in a default material"); + } + + model.meshes = (Mesh *)RL_CALLOC(model.meshCount, sizeof(Mesh)); + model.meshMaterial = (int *)RL_CALLOC(model.meshCount, sizeof(int)); + model.materials = (Material *)RL_CALLOC(model.materialCount + 1, sizeof(Material)); + + // Map no material to index 0 with default shader, everything else materialid + 1 + model.materials[0] = LoadMaterialDefault(); + + for (i = l = 0, k = -1; i < m3d->numface; i++, l++) + { + // Materials are grouped together + if (mi != m3d->face[i].materialid) + { + // there should be only one material switch per material kind, but be bulletproof for unoptimal model files + if (k + 1 >= model.meshCount) + { + model.meshCount++; + model.meshes = (Mesh *)RL_REALLOC(model.meshes, model.meshCount*sizeof(Mesh)); + memset(&model.meshes[model.meshCount - 1], 0, sizeof(Mesh)); + model.meshMaterial = (int *)RL_REALLOC(model.meshMaterial, model.meshCount*sizeof(int)); + } + + k++; + mi = m3d->face[i].materialid; + + for (j = i, l = 0; (j < m3d->numface) && (mi == m3d->face[j].materialid); j++, l++); + + model.meshes[k].vertexCount = l*3; + model.meshes[k].triangleCount = l; + model.meshes[k].vertices = (float *)RL_CALLOC(model.meshes[k].vertexCount*3, sizeof(float)); + model.meshes[k].texcoords = (float *)RL_CALLOC(model.meshes[k].vertexCount*2, sizeof(float)); + model.meshes[k].normals = (float *)RL_CALLOC(model.meshes[k].vertexCount*3, sizeof(float)); + // without material, we rely on vertex colors + if (mi == M3D_UNDEF && model.meshes[k].colors == NULL) + { + model.meshes[k].colors = RL_CALLOC(model.meshes[k].vertexCount*4, sizeof(unsigned char)); + for (j = 0; j < model.meshes[k].vertexCount*4; j += 4) memcpy(&model.meshes[k].colors[j], &WHITE, 4); + } + if (m3d->numbone && m3d->numskin) + { + model.meshes[k].boneIds = (unsigned char *)RL_CALLOC(model.meshes[k].vertexCount*4, sizeof(unsigned char)); + model.meshes[k].boneWeights = (float *)RL_CALLOC(model.meshes[k].vertexCount*4, sizeof(float)); + model.meshes[k].animVertices = (float *)RL_CALLOC(model.meshes[k].vertexCount*3, sizeof(float)); + model.meshes[k].animNormals = (float *)RL_CALLOC(model.meshes[k].vertexCount*3, sizeof(float)); + } + model.meshMaterial[k] = mi + 1; + l = 0; + } + + // Process meshes per material, add triangles + model.meshes[k].vertices[l * 9 + 0] = m3d->vertex[m3d->face[i].vertex[0]].x*m3d->scale; + model.meshes[k].vertices[l * 9 + 1] = m3d->vertex[m3d->face[i].vertex[0]].y*m3d->scale; + model.meshes[k].vertices[l * 9 + 2] = m3d->vertex[m3d->face[i].vertex[0]].z*m3d->scale; + model.meshes[k].vertices[l * 9 + 3] = m3d->vertex[m3d->face[i].vertex[1]].x*m3d->scale; + model.meshes[k].vertices[l * 9 + 4] = m3d->vertex[m3d->face[i].vertex[1]].y*m3d->scale; + model.meshes[k].vertices[l * 9 + 5] = m3d->vertex[m3d->face[i].vertex[1]].z*m3d->scale; + model.meshes[k].vertices[l * 9 + 6] = m3d->vertex[m3d->face[i].vertex[2]].x*m3d->scale; + model.meshes[k].vertices[l * 9 + 7] = m3d->vertex[m3d->face[i].vertex[2]].y*m3d->scale; + model.meshes[k].vertices[l * 9 + 8] = m3d->vertex[m3d->face[i].vertex[2]].z*m3d->scale; + + if (mi == M3D_UNDEF) + { + // without vertex color (full transparency), we use the default color + if (m3d->vertex[m3d->face[i].vertex[0]].color & 0xFF000000) + memcpy(&model.meshes[k].colors[l * 12 + 0], &m3d->vertex[m3d->face[i].vertex[0]].color, 4); + if (m3d->vertex[m3d->face[i].vertex[1]].color & 0xFF000000) + memcpy(&model.meshes[k].colors[l * 12 + 4], &m3d->vertex[m3d->face[i].vertex[1]].color, 4); + if (m3d->vertex[m3d->face[i].vertex[2]].color & 0xFF000000) + memcpy(&model.meshes[k].colors[l * 12 + 8], &m3d->vertex[m3d->face[i].vertex[2]].color, 4); + } + + if (m3d->face[i].texcoord[0] != M3D_UNDEF) + { + model.meshes[k].texcoords[l * 6 + 0] = m3d->tmap[m3d->face[i].texcoord[0]].u; + model.meshes[k].texcoords[l * 6 + 1] = 1.0 - m3d->tmap[m3d->face[i].texcoord[0]].v; + model.meshes[k].texcoords[l * 6 + 2] = m3d->tmap[m3d->face[i].texcoord[1]].u; + model.meshes[k].texcoords[l * 6 + 3] = 1.0 - m3d->tmap[m3d->face[i].texcoord[1]].v; + model.meshes[k].texcoords[l * 6 + 4] = m3d->tmap[m3d->face[i].texcoord[2]].u; + model.meshes[k].texcoords[l * 6 + 5] = 1.0 - m3d->tmap[m3d->face[i].texcoord[2]].v; + } + + if (m3d->face[i].normal[0] != M3D_UNDEF) + { + model.meshes[k].normals[l * 9 + 0] = m3d->vertex[m3d->face[i].normal[0]].x; + model.meshes[k].normals[l * 9 + 1] = m3d->vertex[m3d->face[i].normal[0]].y; + model.meshes[k].normals[l * 9 + 2] = m3d->vertex[m3d->face[i].normal[0]].z; + model.meshes[k].normals[l * 9 + 3] = m3d->vertex[m3d->face[i].normal[1]].x; + model.meshes[k].normals[l * 9 + 4] = m3d->vertex[m3d->face[i].normal[1]].y; + model.meshes[k].normals[l * 9 + 5] = m3d->vertex[m3d->face[i].normal[1]].z; + model.meshes[k].normals[l * 9 + 6] = m3d->vertex[m3d->face[i].normal[2]].x; + model.meshes[k].normals[l * 9 + 7] = m3d->vertex[m3d->face[i].normal[2]].y; + model.meshes[k].normals[l * 9 + 8] = m3d->vertex[m3d->face[i].normal[2]].z; + } + + // Add skin (vertex / bone weight pairs) + if (m3d->numbone && m3d->numskin) { + for (n = 0; n < 3; n++) { + int skinid = m3d->vertex[m3d->face[i].vertex[n]].skinid; + // check if there's a skin for this mesh, should be, just failsafe + if (skinid != M3D_UNDEF && skinid < m3d->numskin) + { + for (j = 0; j < 4; j++) + { + model.meshes[k].boneIds[l * 12 + n * 4 + j] = m3d->skin[skinid].boneid[j]; + model.meshes[k].boneWeights[l * 12 + n * 4 + j] = m3d->skin[skinid].weight[j]; + } + } + } + } + } + + // Load materials + for (i = 0; i < m3d->nummaterial; i++) + { + model.materials[i + 1] = LoadMaterialDefault(); + + for (j = 0; j < m3d->material[i].numprop; j++) + { + prop = &m3d->material[i].prop[j]; + + switch (prop->type) + { + case m3dp_Kd: + { + memcpy(&model.materials[i + 1].maps[MATERIAL_MAP_DIFFUSE].color, &prop->value.color, 4); + model.materials[i + 1].maps[MATERIAL_MAP_DIFFUSE].value = 0.0f; + } break; + case m3dp_Ks: + { + memcpy(&model.materials[i + 1].maps[MATERIAL_MAP_SPECULAR].color, &prop->value.color, 4); + } break; + case m3dp_Ns: + { + model.materials[i + 1].maps[MATERIAL_MAP_SPECULAR].value = prop->value.fnum; + } break; + case m3dp_Ke: + { + memcpy(&model.materials[i + 1].maps[MATERIAL_MAP_EMISSION].color, &prop->value.color, 4); + model.materials[i + 1].maps[MATERIAL_MAP_EMISSION].value = 0.0f; + } break; + case m3dp_Pm: + { + model.materials[i + 1].maps[MATERIAL_MAP_METALNESS].value = prop->value.fnum; + } break; + case m3dp_Pr: + { + model.materials[i + 1].maps[MATERIAL_MAP_ROUGHNESS].value = prop->value.fnum; + } break; + case m3dp_Ps: + { + model.materials[i + 1].maps[MATERIAL_MAP_NORMAL].color = WHITE; + model.materials[i + 1].maps[MATERIAL_MAP_NORMAL].value = prop->value.fnum; + } break; + default: + { + if (prop->type >= 128) + { + Image image = { 0 }; + image.data = m3d->texture[prop->value.textureid].d; + image.width = m3d->texture[prop->value.textureid].w; + image.height = m3d->texture[prop->value.textureid].h; + image.mipmaps = 1; + image.format = (m3d->texture[prop->value.textureid].f == 4)? PIXELFORMAT_UNCOMPRESSED_R8G8B8A8 : + ((m3d->texture[prop->value.textureid].f == 3)? PIXELFORMAT_UNCOMPRESSED_R8G8B8 : + ((m3d->texture[prop->value.textureid].f == 2)? PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA : PIXELFORMAT_UNCOMPRESSED_GRAYSCALE)); + + switch (prop->type) + { + case m3dp_map_Kd: model.materials[i + 1].maps[MATERIAL_MAP_DIFFUSE].texture = LoadTextureFromImage(image); break; + case m3dp_map_Ks: model.materials[i + 1].maps[MATERIAL_MAP_SPECULAR].texture = LoadTextureFromImage(image); break; + case m3dp_map_Ke: model.materials[i + 1].maps[MATERIAL_MAP_EMISSION].texture = LoadTextureFromImage(image); break; + case m3dp_map_Km: model.materials[i + 1].maps[MATERIAL_MAP_NORMAL].texture = LoadTextureFromImage(image); break; + case m3dp_map_Ka: model.materials[i + 1].maps[MATERIAL_MAP_OCCLUSION].texture = LoadTextureFromImage(image); break; + case m3dp_map_Pm: model.materials[i + 1].maps[MATERIAL_MAP_ROUGHNESS].texture = LoadTextureFromImage(image); break; + default: break; + } + } + } break; + } + } + } + + // Load bones + if(m3d->numbone) + { + model.boneCount = m3d->numbone; + model.bones = RL_MALLOC(m3d->numbone*sizeof(BoneInfo)); + model.bindPose = RL_MALLOC(m3d->numbone*sizeof(Transform)); + for (i = 0; i < m3d->numbone; i++) + { + model.bones[i].parent = m3d->bone[i].parent; + strncpy(model.bones[i].name, m3d->bone[i].name, sizeof(model.bones[i].name)); + model.bindPose[i].translation.x = m3d->vertex[m3d->bone[i].pos].x; + model.bindPose[i].translation.y = m3d->vertex[m3d->bone[i].pos].y; + model.bindPose[i].translation.z = m3d->vertex[m3d->bone[i].pos].z; + model.bindPose[i].rotation.x = m3d->vertex[m3d->bone[i].ori].x; + model.bindPose[i].rotation.y = m3d->vertex[m3d->bone[i].ori].y; + model.bindPose[i].rotation.z = m3d->vertex[m3d->bone[i].ori].z; + model.bindPose[i].rotation.w = m3d->vertex[m3d->bone[i].ori].w; + // TODO: if the orientation quaternion not normalized, then that's encoding scaling + model.bindPose[i].rotation = QuaternionNormalize(model.bindPose[i].rotation); + model.bindPose[i].scale.x = model.bindPose[i].scale.y = model.bindPose[i].scale.z = 1.0f; + } + } + + // Load bone-pose default mesh into animation vertices. These will be updated when UpdateModelAnimation gets + // called, but not before, however DrawMesh uses these if they exists (so not good if they are left empty). + if (m3d->numbone && m3d->numskin) + { + for(i = 0; i < model.meshCount; i++) + { + memcpy(model.meshes[i].animVertices, model.meshes[i].vertices, model.meshes[i].vertexCount*3*sizeof(float)); + memcpy(model.meshes[i].animNormals, model.meshes[i].normals, model.meshes[i].vertexCount*3*sizeof(float)); + } + } + + m3d_free(m3d); + UnloadFileData(fileData); + } + + return model; +} + +// Load M3D animation data +#define M3D_ANIMDELAY 17 // that's roughly ~1000 msec / 60 FPS (16.666666* msec) +static ModelAnimation *LoadModelAnimationsM3D(const char *fileName, unsigned int *animCount) +{ + m3d_t *m3d = NULL; + unsigned int bytesRead = 0; + unsigned char *fileData = LoadFileData(fileName, &bytesRead); + ModelAnimation *animations = NULL; + int i, j; + + *animCount = 0; + + if (fileData != NULL) + { + m3d = m3d_load(fileData, m3d_loaderhook, m3d_freehook, NULL); + + if (!m3d || M3D_ERR_ISFATAL(m3d->errcode)) + { + TRACELOG(LOG_WARNING, "MODEL: [%s] Failed to load M3D data, error code %d", fileName, m3d ? m3d->errcode : -2); + UnloadFileData(fileData); + return NULL; + } + else TRACELOG(LOG_INFO, "MODEL: [%s] M3D data loaded successfully: %i animations, %i bones, %i skins", fileName, + m3d->numaction, m3d->numbone, m3d->numskin); + + // no animation or bone+skin? + if (!m3d->numaction || !m3d->numbone || !m3d->numskin) + { + m3d_free(m3d); + UnloadFileData(fileData); + return NULL; + } + + animations = RL_MALLOC(m3d->numaction*sizeof(ModelAnimation)); + *animCount = m3d->numaction; + + for (unsigned int a = 0; a < m3d->numaction; a++) + { + animations[a].frameCount = m3d->action[a].durationmsec / M3D_ANIMDELAY; + animations[a].boneCount = m3d->numbone; + animations[a].bones = RL_MALLOC(m3d->numbone*sizeof(BoneInfo)); + animations[a].framePoses = RL_MALLOC(animations[a].frameCount*sizeof(Transform *)); + // strncpy(animations[a].name, m3d->action[a].name, sizeof(animations[a].name)); + TRACELOG(LOG_INFO, "MODEL: [%s] animation #%i: %i msec, %i frames", fileName, a, m3d->action[a].durationmsec, animations[a].frameCount); + + for (i = 0; i < m3d->numbone; i++) + { + animations[a].bones[i].parent = m3d->bone[i].parent; + strncpy(animations[a].bones[i].name, m3d->bone[i].name, sizeof(animations[a].bones[i].name)); + } + + // M3D stores frames at arbitrary intervals with sparse skeletons. We need full skeletons at + // regular intervals, so let the M3D SDK do the heavy lifting and calculate interpolated bones + for (i = 0; i < animations[a].frameCount; i++) + { + animations[a].framePoses[i] = RL_MALLOC(m3d->numbone*sizeof(Transform)); + + m3db_t *pose = m3d_pose(m3d, a, i * M3D_ANIMDELAY); + if (pose != NULL) + { + for (j = 0; j < m3d->numbone; j++) + { + animations[a].framePoses[i][j].translation.x = m3d->vertex[pose[j].pos].x; + animations[a].framePoses[i][j].translation.y = m3d->vertex[pose[j].pos].y; + animations[a].framePoses[i][j].translation.z = m3d->vertex[pose[j].pos].z; + animations[a].framePoses[i][j].rotation.x = m3d->vertex[pose[j].ori].x; + animations[a].framePoses[i][j].rotation.y = m3d->vertex[pose[j].ori].y; + animations[a].framePoses[i][j].rotation.z = m3d->vertex[pose[j].ori].z; + animations[a].framePoses[i][j].rotation.w = m3d->vertex[pose[j].ori].w; + animations[a].framePoses[i][j].rotation = QuaternionNormalize(animations[a].framePoses[i][j].rotation); + animations[a].framePoses[i][j].scale.x = animations[a].framePoses[i][j].scale.y = animations[a].framePoses[i][j].scale.z = 1.0f; + } + RL_FREE(pose); + } + } + } + + m3d_free(m3d); + UnloadFileData(fileData); + } + + return animations; +} +#endif + +#endif // SUPPORT_MODULE_RMODELS diff --git a/raylib/rshapes.c b/raylib/rshapes.c index a60d52b..955f056 100644 --- a/raylib/rshapes.c +++ b/raylib/rshapes.c @@ -6,24 +6,27 @@ * Shapes can be draw using 3 types of primitives: LINES, TRIANGLES and QUADS. * Some functions implement two drawing options: TRIANGLES and QUADS, by default TRIANGLES * are used but QUADS implementation can be selected with SUPPORT_QUADS_DRAW_MODE define -* -* Some functions define texture coordinates (rlTexCoord2f()) for the shapes and use a +* +* Some functions define texture coordinates (rlTexCoord2f()) for the shapes and use a * user-provided texture with SetShapesTexture(), the pourpouse of this implementation * is allowing to reduce draw calls when combined with a texture-atlas. * -* By default, raylib sets the default texture and rectangle at InitWindow()[rcore] to one +* By default, raylib sets the default texture and rectangle at InitWindow()[rcore] to one * white character of default font [rtext], this way, raylib text and shapes can be draw with * a single draw call and it also allows users to configure it the same way with their own fonts. * * CONFIGURATION: * +* #define SUPPORT_MODULE_RSHAPES +* rshapes module is included in the build +* * #define SUPPORT_QUADS_DRAW_MODE * Use QUADS instead of TRIANGLES for drawing when possible. Lines-based shapes still use LINES * * * LICENSE: zlib/libpng * -* Copyright (c) 2013-2021 Ramon Santamaria (@raysan5) +* Copyright (c) 2013-2022 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. @@ -49,6 +52,8 @@ #include "config.h" // Defines module configuration flags #endif +#if defined(SUPPORT_MODULE_RSHAPES) + #include "rlgl.h" // OpenGL abstraction layer to OpenGL 1.1, 2.1, 3.3+ or ES2 #include // Required for: sinf(), asinf(), cosf(), acosf(), sqrtf(), fabsf() @@ -75,8 +80,8 @@ //---------------------------------------------------------------------------------- // Global Variables Definition //---------------------------------------------------------------------------------- -Texture2D texShapes = { 1, 1, 1, 1, 7 }; // Texture used on shapes drawing (usually a white pixel) -Rectangle texShapesRec = { 0, 0, 1, 1 }; // Texture source rectangle used on shapes drawing +Texture2D texShapes = { 1, 1, 1, 1, 7 }; // Texture used on shapes drawing (usually a white pixel) +Rectangle texShapesRec = { 0.0f, 0.0f, 1.0f, 1.0f }; // Texture source rectangle used on shapes drawing //---------------------------------------------------------------------------------- // Module specific Functions Declaration @@ -718,7 +723,7 @@ void DrawRectanglePro(Rectangle rec, Vector2 origin, float rotation, Color color rlCheckRenderBatchLimit(4); rlSetTexture(texShapes.id); - + rlBegin(RL_QUADS); rlNormal3f(0.0f, 0.0f, 1.0f); @@ -737,13 +742,13 @@ void DrawRectanglePro(Rectangle rec, Vector2 origin, float rotation, Color color rlVertex2f(topRight.x, topRight.y); rlEnd(); - + rlSetTexture(0); #else rlCheckRenderBatchLimit(6); rlBegin(RL_TRIANGLES); - + rlColor4ub(color.r, color.g, color.b, color.a); rlVertex2f(topLeft.x, topLeft.y); @@ -780,28 +785,26 @@ void DrawRectangleGradientEx(Rectangle rec, Color col1, Color col2, Color col3, rlSetTexture(texShapes.id); - rlPushMatrix(); - rlBegin(RL_QUADS); - rlNormal3f(0.0f, 0.0f, 1.0f); + rlBegin(RL_QUADS); + rlNormal3f(0.0f, 0.0f, 1.0f); - // NOTE: Default raylib font character 95 is a white square - rlColor4ub(col1.r, col1.g, col1.b, col1.a); - rlTexCoord2f(texShapesRec.x/texShapes.width, texShapesRec.y/texShapes.height); - rlVertex2f(rec.x, rec.y); + // NOTE: Default raylib font character 95 is a white square + rlColor4ub(col1.r, col1.g, col1.b, col1.a); + rlTexCoord2f(texShapesRec.x/texShapes.width, texShapesRec.y/texShapes.height); + rlVertex2f(rec.x, rec.y); - rlColor4ub(col2.r, col2.g, col2.b, col2.a); - rlTexCoord2f(texShapesRec.x/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); - rlVertex2f(rec.x, rec.y + rec.height); + rlColor4ub(col2.r, col2.g, col2.b, col2.a); + rlTexCoord2f(texShapesRec.x/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); + rlVertex2f(rec.x, rec.y + rec.height); - rlColor4ub(col3.r, col3.g, col3.b, col3.a); - rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); - rlVertex2f(rec.x + rec.width, rec.y + rec.height); + rlColor4ub(col3.r, col3.g, col3.b, col3.a); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); + rlVertex2f(rec.x + rec.width, rec.y + rec.height); - rlColor4ub(col4.r, col4.g, col4.b, col4.a); - rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, texShapesRec.y/texShapes.height); - rlVertex2f(rec.x + rec.width, rec.y); - rlEnd(); - rlPopMatrix(); + rlColor4ub(col4.r, col4.g, col4.b, col4.a); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, texShapesRec.y/texShapes.height); + rlVertex2f(rec.x + rec.width, rec.y); + rlEnd(); rlSetTexture(0); } @@ -1604,7 +1607,7 @@ bool CheckCollisionPointRec(Vector2 point, Rectangle rec) bool CheckCollisionPointCircle(Vector2 point, Vector2 center, float radius) { bool collision = false; - + collision = CheckCollisionCircles(point, 0, center, radius); return collision; @@ -1676,7 +1679,7 @@ bool CheckCollisionCircleRec(Vector2 center, float radius, Rectangle rec) (dy - rec.height/2.0f)*(dy - rec.height/2.0f); collision = (cornerDistanceSq <= (radius*radius)); - + return collision; } @@ -1690,10 +1693,10 @@ bool CheckCollisionLines(Vector2 startPos1, Vector2 endPos1, Vector2 startPos2, if (fabsf(div) >= FLT_EPSILON) { collision = true; - + float xi = ((startPos2.x - endPos2.x)*(startPos1.x*endPos1.y - startPos1.y*endPos1.x) - (startPos1.x - endPos1.x)*(startPos2.x*endPos2.y - startPos2.y*endPos2.x))/div; float yi = ((startPos2.y - endPos2.y)*(startPos1.x*endPos1.y - startPos1.y*endPos1.x) - (startPos1.y - endPos1.y)*(startPos2.x*endPos2.y - startPos2.y*endPos2.x))/div; - + if (((fabsf(startPos1.x - endPos1.x) > FLT_EPSILON) && (xi < fminf(startPos1.x, endPos1.x) || (xi > fmaxf(startPos1.x, endPos1.x)))) || ((fabsf(startPos2.x - endPos2.x) > FLT_EPSILON) && (xi < fminf(startPos2.x, endPos2.x) || (xi > fmaxf(startPos2.x, endPos2.x)))) || ((fabsf(startPos1.y - endPos1.y) > FLT_EPSILON) && (yi < fminf(startPos1.y, endPos1.y) || (yi > fmaxf(startPos1.y, endPos1.y)))) || @@ -1810,3 +1813,5 @@ static float EaseCubicInOut(float t, float b, float c, float d) return 0.5f*c*(t*t*t + 2.0f) + b; } + +#endif // SUPPORT_MODULE_RSHAPES diff --git a/raylib/rtext.c b/raylib/rtext.c index 137ad18..ef5debd 100644 --- a/raylib/rtext.c +++ b/raylib/rtext.c @@ -4,6 +4,9 @@ * * CONFIGURATION: * +* #define SUPPORT_MODULE_RTEXT +* rtext module is included in the build +* * #define SUPPORT_FILEFORMAT_FNT * #define SUPPORT_FILEFORMAT_TTF * Selected desired fileformats to be supported for loading. Some of those formats are @@ -22,12 +25,12 @@ * * DEPENDENCIES: * stb_truetype - Load TTF file and rasterize characters data -* stb_rect_pack - Rectangles packing algorythms, required for font atlas generation +* stb_rect_pack - Rectangles packing algorithms, required for font atlas generation * * * LICENSE: zlib/libpng * -* Copyright (c) 2013-2021 Ramon Santamaria (@raysan5) +* Copyright (c) 2013-2022 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. @@ -53,6 +56,8 @@ #include "config.h" // Defines module configuration flags #endif +#if defined(SUPPORT_MODULE_RTEXT) + #include "utils.h" // Required for: LoadFileText() #include "rlgl.h" // OpenGL abstraction layer to OpenGL 1.1, 2.1, 3.3+ or ES2 -> Only DrawTextPro() @@ -224,7 +229,7 @@ extern void LoadFontDefault(void) //------------------------------------------------------------------------------ // Allocate space for our characters info data - // NOTE: This memory should be freed at end! --> CloseWindow() + // NOTE: This memory must be freed at end! --> Done by CloseWindow() defaultFont.glyphs = (GlyphInfo *)RL_MALLOC(defaultFont.glyphCount*sizeof(GlyphInfo)); defaultFont.recs = (Rectangle *)RL_MALLOC(defaultFont.glyphCount*sizeof(Rectangle)); @@ -311,7 +316,7 @@ Font LoadFont(const char *fileName) Font font = { 0 }; #if defined(SUPPORT_FILEFORMAT_TTF) - if (IsFileExtension(fileName, ".ttf;.otf")) font = LoadFontEx(fileName, FONT_TTF_DEFAULT_SIZE, NULL, FONT_TTF_DEFAULT_NUMCHARS); + if (IsFileExtension(fileName, ".ttf") || IsFileExtension(fileName, ".otf")) font = LoadFontEx(fileName, FONT_TTF_DEFAULT_SIZE, NULL, FONT_TTF_DEFAULT_NUMCHARS); else #endif #if defined(SUPPORT_FILEFORMAT_FNT) @@ -329,7 +334,11 @@ Font LoadFont(const char *fileName) TRACELOG(LOG_WARNING, "FONT: [%s] Failed to load font texture -> Using default font", fileName); font = GetFontDefault(); } - else SetTextureFilter(font.texture, TEXTURE_FILTER_POINT); // By default we set point filter (best performance) + else + { + SetTextureFilter(font.texture, TEXTURE_FILTER_POINT); // By default we set point filter (best performance) + TRACELOG(LOG_INFO, "FONT: Data loaded successfully (%i pixel size | %i glyphs)", FONT_TTF_DEFAULT_SIZE, FONT_TTF_DEFAULT_NUMCHARS); + } return font; } @@ -515,7 +524,7 @@ Font LoadFontFromMemory(const char *fileType, const unsigned char *fileData, int UnloadImage(atlas); - // TRACELOG(LOG_INFO, "FONT: Font loaded successfully (%i glyphs)", font.glyphCount); + TRACELOG(LOG_INFO, "FONT: Data loaded successfully (%i pixel size | %i glyphs)", font.baseSize, font.glyphCount); } else font = GetFontDefault(); } @@ -677,20 +686,20 @@ Image GenImageFontAtlas(const GlyphInfo *chars, Rectangle **charRecs, int glyphC // NOTE 2: SDF font characters already contain an internal padding, // so image size would result bigger than default font type float requiredArea = 0; - for (int i = 0; i < glyphCount; i++) requiredArea += ((chars[i].image.width + 2*padding)*(chars[i].image.height + 2*padding)); - float guessSize = sqrtf(requiredArea)*1.3f; - int imageSize = (int)powf(2, ceilf(logf((float)guessSize)/logf(2))); // Calculate next POT + for (int i = 0; i < glyphCount; i++) requiredArea += ((chars[i].image.width + 2*padding)*(fontSize + 2*padding)); + float guessSize = sqrtf(requiredArea)*1.4f; + int imageSize = (int)powf(2, ceilf(logf((float)guessSize)/logf(2))); // Calculate next POT atlas.width = imageSize; // Atlas bitmap width atlas.height = imageSize; // Atlas bitmap height - atlas.data = (unsigned char *)RL_CALLOC(1, atlas.width*atlas.height); // Create a bitmap to store characters (8 bpp) + atlas.data = (unsigned char *)RL_CALLOC(1, atlas.width*atlas.height); // Create a bitmap to store characters (8 bpp) atlas.format = PIXELFORMAT_UNCOMPRESSED_GRAYSCALE; atlas.mipmaps = 1; // DEBUG: We can see padding in the generated image setting a gray background... //for (int i = 0; i < atlas.width*atlas.height; i++) ((unsigned char *)atlas.data)[i] = 100; - if (packMethod == 0) // Use basic packing algorythm + if (packMethod == 0) // Use basic packing algorithm { int offsetX = padding; int offsetY = padding; @@ -725,11 +734,23 @@ Image GenImageFontAtlas(const GlyphInfo *chars, Rectangle **charRecs, int glyphC // height is bigger than fontSize, it could be up to (fontSize + 8) offsetY += (fontSize + 2*padding); - if (offsetY > (atlas.height - fontSize - padding)) break; + if (offsetY > (atlas.height - fontSize - padding)) + { + for(int j = i + 1; j < glyphCount; j++) + { + TRACELOG(LOG_WARNING, "FONT: Failed to package character (%i)", j); + // make sure remaining recs contain valid data + recs[j].x = 0; + recs[j].y = 0; + recs[j].width = 0; + recs[j].height = 0; + } + break; + } } } } - else if (packMethod == 1) // Use Skyline rect packing algorythm (stb_pack_rect) + else if (packMethod == 1) // Use Skyline rect packing algorithm (stb_pack_rect) { stbrp_context *context = (stbrp_context *)RL_MALLOC(sizeof(*context)); stbrp_node *nodes = (stbrp_node *)RL_MALLOC(glyphCount*sizeof(*nodes)); @@ -797,9 +818,12 @@ Image GenImageFontAtlas(const GlyphInfo *chars, Rectangle **charRecs, int glyphC // Unload font glyphs info data (RAM) void UnloadFontData(GlyphInfo *glyphs, int glyphCount) { - for (int i = 0; i < glyphCount; i++) UnloadImage(glyphs[i].image); + if (glyphs != NULL) + { + for (int i = 0; i < glyphCount; i++) UnloadImage(glyphs[i].image); - RL_FREE(glyphs); + RL_FREE(glyphs); + } } // Unload Font from GPU memory (VRAM) @@ -816,15 +840,170 @@ void UnloadFont(Font font) } } +// Export font as code file, returns true on success +bool ExportFontAsCode(Font font, const char *fileName) +{ + bool success = false; + +#ifndef TEXT_BYTES_PER_LINE + #define TEXT_BYTES_PER_LINE 20 +#endif + + #define MAX_FONT_DATA_SIZE 1024*1024 // 1 MB + + // Get file name from path + char fileNamePascal[256] = { 0 }; + strcpy(fileNamePascal, TextToPascal(GetFileNameWithoutExt(fileName))); + + // NOTE: Text data buffer size is estimated considering image data size in bytes + // and requiring 6 char bytes for every byte: "0x00, " + char *txtData = (char *)RL_CALLOC(MAX_FONT_DATA_SIZE, sizeof(char)); + + int byteCount = 0; + byteCount += sprintf(txtData + byteCount, "////////////////////////////////////////////////////////////////////////////////////////\n"); + byteCount += sprintf(txtData + byteCount, "// //\n"); + byteCount += sprintf(txtData + byteCount, "// FontAsCode exporter v1.0 - Font data exported as an array of bytes //\n"); + byteCount += sprintf(txtData + byteCount, "// //\n"); + byteCount += sprintf(txtData + byteCount, "// more info and bugs-report: github.com/raysan5/raylib //\n"); + byteCount += sprintf(txtData + byteCount, "// feedback and support: ray[at]raylib.com //\n"); + byteCount += sprintf(txtData + byteCount, "// //\n"); + byteCount += sprintf(txtData + byteCount, "// Copyright (c) 2018-2022 Ramon Santamaria (@raysan5) //\n"); + byteCount += sprintf(txtData + byteCount, "// //\n"); + byteCount += sprintf(txtData + byteCount, "// ---------------------------------------------------------------------------------- //\n"); + byteCount += sprintf(txtData + byteCount, "// //\n"); + byteCount += sprintf(txtData + byteCount, "// TODO: Fill the information and license of the exported font here: //\n"); + byteCount += sprintf(txtData + byteCount, "// //\n"); + byteCount += sprintf(txtData + byteCount, "// Font name: .... //\n"); + byteCount += sprintf(txtData + byteCount, "// Font creator: .... //\n"); + byteCount += sprintf(txtData + byteCount, "// Font LICENSE: .... //\n"); + byteCount += sprintf(txtData + byteCount, "// //\n"); + byteCount += sprintf(txtData + byteCount, "////////////////////////////////////////////////////////////////////////////////////////\n\n"); + + // Support font export and initialization + // NOTE: This mechanism is highly coupled to raylib + Image image = LoadImageFromTexture(font.texture); + if (image.format != PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA) TRACELOG(LOG_WARNING, "Font export as code: Font image format is not GRAY+ALPHA!"); + int imageDataSize = GetPixelDataSize(image.width, image.height, image.format); + + // Image data is usually GRAYSCALE + ALPHA and can be reduced to GRAYSCALE + //ImageFormat(&image, PIXELFORMAT_UNCOMPRESSED_GRAYSCALE); + +#define SUPPORT_COMPRESSED_FONT_ATLAS +#if defined(SUPPORT_COMPRESSED_FONT_ATLAS) + // WARNING: Data is compressed using raylib CompressData() DEFLATE, + // it requires to be decompressed with raylib DecompressData(), that requires + // compiling raylib with SUPPORT_COMPRESSION_API config flag enabled + + // Compress font image data + int compDataSize = 0; + unsigned char *compData = CompressData(image.data, imageDataSize, &compDataSize); + + // Save font image data (compressed) + byteCount += sprintf(txtData + byteCount, "#define COMPRESSED_DATA_SIZE_FONT_%s %i\n\n", TextToUpper(fileNamePascal), compDataSize); + byteCount += sprintf(txtData + byteCount, "// Font image pixels data compressed (DEFLATE)\n"); + byteCount += sprintf(txtData + byteCount, "// NOTE: Original pixel data simplified to GRAYSCALE\n"); + byteCount += sprintf(txtData + byteCount, "static unsigned char fontData_%s[COMPRESSED_DATA_SIZE_FONT_%s] = { ", fileNamePascal, TextToUpper(fileNamePascal)); + for (int i = 0; i < compDataSize - 1; i++) byteCount += sprintf(txtData + byteCount, ((i%TEXT_BYTES_PER_LINE == 0)? "0x%02x,\n " : "0x%02x, "), compData[i]); + byteCount += sprintf(txtData + byteCount, "0x%02x };\n\n", compData[compDataSize - 1]); + MemFree(compData); +#else + // Save font image data (uncompressed) + byteCount += sprintf(txtData + byteCount, "// Font image pixels data\n"); + byteCount += sprintf(txtData + byteCount, "// NOTE: 2 bytes per pixel, GRAY + ALPHA channels\n"); + byteCount += sprintf(txtData + byteCount, "static unsigned char fontImageData_%s[%i] = { ", fileNamePascal, imageDataSize); + for (int i = 0; i < imageDataSize - 1; i++) byteCount += sprintf(txtData + byteCount, ((i%TEXT_BYTES_PER_LINE == 0)? "0x%02x,\n " : "0x%02x, "), ((unsigned char *)imFont.data)[i]); + byteCount += sprintf(txtData + byteCount, "0x%02x };\n\n", ((unsigned char *)imFont.data)[imageDataSize - 1]); +#endif + + // Save font recs data + byteCount += sprintf(txtData + byteCount, "// Font characters rectangles data\n"); + byteCount += sprintf(txtData + byteCount, "static const Rectangle fontRecs_%s[%i] = {\n", fileNamePascal, font.glyphCount); + for (int i = 0; i < font.glyphCount; i++) + { + byteCount += sprintf(txtData + byteCount, " { %1.0f, %1.0f, %1.0f , %1.0f },\n", font.recs[i].x, font.recs[i].y, font.recs[i].width, font.recs[i].height); + } + byteCount += sprintf(txtData + byteCount, "};\n\n"); + + // Save font glyphs data + // NOTE: Glyphs image data not saved (grayscale pixels), + // it could be generated from image and recs + byteCount += sprintf(txtData + byteCount, "// Font glyphs info data\n"); + byteCount += sprintf(txtData + byteCount, "// NOTE: No glyphs.image data provided\n"); + byteCount += sprintf(txtData + byteCount, "static const GlyphInfo fontGlyphs_%s[%i] = {\n", fileNamePascal, font.glyphCount); + for (int i = 0; i < font.glyphCount; i++) + { + byteCount += sprintf(txtData + byteCount, " { %i, %i, %i, %i, { 0 }},\n", font.glyphs[i].value, font.glyphs[i].offsetX, font.glyphs[i].offsetY, font.glyphs[i].advanceX); + } + byteCount += sprintf(txtData + byteCount, "};\n\n"); + + // Custom font loading function + byteCount += sprintf(txtData + byteCount, "// Font loading function: %s\n", fileNamePascal); + byteCount += sprintf(txtData + byteCount, "static Font LoadFont_%s(void)\n{\n", fileNamePascal); + byteCount += sprintf(txtData + byteCount, " Font font = { 0 };\n\n"); + byteCount += sprintf(txtData + byteCount, " font.baseSize = %i;\n", font.baseSize); + byteCount += sprintf(txtData + byteCount, " font.glyphCount = %i;\n", font.glyphCount); + byteCount += sprintf(txtData + byteCount, " font.glyphPadding = %i;\n\n", font.glyphPadding); + byteCount += sprintf(txtData + byteCount, " // Custom font loading\n"); +#if defined(SUPPORT_COMPRESSED_FONT_ATLAS) + byteCount += sprintf(txtData + byteCount, " // NOTE: Compressed font image data (DEFLATE), it requires DecompressData() function\n"); + byteCount += sprintf(txtData + byteCount, " int fontDataSize_%s = 0;\n", fileNamePascal); + byteCount += sprintf(txtData + byteCount, " unsigned char *data = DecompressData(fontData_%s, COMPRESSED_DATA_SIZE_FONT_%s, &fontDataSize_%s);\n", fileNamePascal, TextToUpper(fileNamePascal), fileNamePascal); + byteCount += sprintf(txtData + byteCount, " Image imFont = { data, %i, %i, 1, %i };\n\n", image.width, image.height, image.format); +#else + byteCount += sprintf(txtData + byteCount, " Image imFont = { fontImageData_%s, %i, %i, 1, %i };\n\n", styleName, image.width, image.height, image.format); +#endif + byteCount += sprintf(txtData + byteCount, " // Load texture from image\n"); + byteCount += sprintf(txtData + byteCount, " font.texture = LoadTextureFromImage(imFont);\n"); +#if defined(SUPPORT_COMPRESSED_FONT_ATLAS) + byteCount += sprintf(txtData + byteCount, " UnloadImage(imFont); // Uncompressed data can be unloaded from memory\n\n"); +#endif + // We have two possible mechanisms to assign font.recs and font.glyphs data, + // that data is already available as global arrays, we two options to assign that data: + // - 1. Data copy. This option consumes more memory and Font MUST be unloaded by user, requiring additional code. + // - 2. Data assignment. This option consumes less memory and Font MUST NOT be unloaded by user because data is on protected DATA segment +//#define SUPPORT_FONT_DATA_COPY +#if defined(SUPPORT_FONT_DATA_COPY) + byteCount += sprintf(txtData + byteCount, " // Copy glyph recs data from global fontRecs\n"); + byteCount += sprintf(txtData + byteCount, " // NOTE: Required to avoid issues if trying to free font\n"); + byteCount += sprintf(txtData + byteCount, " font.recs = (Rectangle *)malloc(font.glyphCount*sizeof(Rectangle));\n"); + byteCount += sprintf(txtData + byteCount, " memcpy(font.recs, fontRecs_%s, font.glyphCount*sizeof(Rectangle));\n\n", fileNamePascal); + + byteCount += sprintf(txtData + byteCount, " // Copy font glyph info data from global fontChars\n"); + byteCount += sprintf(txtData + byteCount, " // NOTE: Required to avoid issues if trying to free font\n"); + byteCount += sprintf(txtData + byteCount, " font.glyphs = (GlyphInfo *)malloc(font.glyphCount*sizeof(GlyphInfo));\n"); + byteCount += sprintf(txtData + byteCount, " memcpy(font.glyphs, fontGlyphs_%s, font.glyphCount*sizeof(GlyphInfo));\n\n", fileNamePascal); +#else + byteCount += sprintf(txtData + byteCount, " // Assign glyph recs and info data directly\n"); + byteCount += sprintf(txtData + byteCount, " // WARNING: This font data must not be unloaded\n"); + byteCount += sprintf(txtData + byteCount, " font.recs = fontRecs_%s;\n", fileNamePascal); + byteCount += sprintf(txtData + byteCount, " font.glyphs = fontGlyphs_%s;\n\n", fileNamePascal); +#endif + byteCount += sprintf(txtData + byteCount, " return font;\n"); + byteCount += sprintf(txtData + byteCount, "}\n"); + + UnloadImage(image); + + // NOTE: Text data size exported is determined by '\0' (NULL) character + success = SaveFileText(fileName, txtData); + + RL_FREE(txtData); + + if (success != 0) TRACELOG(LOG_INFO, "FILEIO: [%s] Font as code exported successfully", fileName); + else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to export font as code", fileName); + + return success; +} + + // Draw current FPS // NOTE: Uses default font void DrawFPS(int posX, int posY) { - Color color = LIME; // good fps + Color color = LIME; // Good FPS int fps = GetFPS(); - if (fps < 30 && fps >= 15) color = ORANGE; // warning FPS - else if (fps < 15) color = RED; // bad FPS + if ((fps < 30) && (fps >= 15)) color = ORANGE; // Warning FPS + else if (fps < 15) color = RED; // Low FPS DrawText(TextFormat("%2i FPS", GetFPS()), posX, posY, 20, color); } @@ -875,7 +1054,7 @@ void DrawTextEx(Font font, const char *text, Vector2 position, float fontSize, f { // NOTE: Fixed line spacing of 1.5 line-height // TODO: Support custom line spacing defined by user - textOffsetY += (int)((font.baseSize + font.baseSize/2)*scaleFactor); + textOffsetY += (int)((font.baseSize + font.baseSize/2.0f)*scaleFactor); textOffsetX = 0.0f; } else @@ -931,10 +1110,42 @@ void DrawTextCodepoint(Font font, int codepoint, Vector2 position, float fontSiz DrawTexturePro(font.texture, srcRec, dstRec, (Vector2){ 0, 0 }, 0.0f, tint); } +// Draw multiple character (codepoints) +void DrawTextCodepoints(Font font, const int *codepoints, int count, Vector2 position, float fontSize, float spacing, Color tint) +{ + int textOffsetY = 0; // Offset between lines (on line break '\n') + float textOffsetX = 0.0f; // Offset X to next character to draw + + float scaleFactor = fontSize/font.baseSize; // Character quad scaling factor + + for (int i = 0; i < count; i++) + { + int index = GetGlyphIndex(font, codepoints[i]); + + if (codepoints[i] == '\n') + { + // NOTE: Fixed line spacing of 1.5 line-height + // TODO: Support custom line spacing defined by user + textOffsetY += (int)((font.baseSize + font.baseSize/2.0f)*scaleFactor); + textOffsetX = 0.0f; + } + else + { + if ((codepoints[i] != ' ') && (codepoints[i] != '\t')) + { + DrawTextCodepoint(font, codepoints[i], (Vector2){ position.x + textOffsetX, position.y + textOffsetY }, fontSize, tint); + } + + if (font.glyphs[index].advanceX == 0) textOffsetX += ((float)font.recs[index].width*scaleFactor + spacing); + else textOffsetX += ((float)font.glyphs[index].advanceX*scaleFactor + spacing); + } + } +} + // Measure string width for default font int MeasureText(const char *text, int fontSize) { - Vector2 vec = { 0.0f, 0.0f }; + Vector2 textSize = { 0.0f, 0.0f }; // Check if default font has been loaded if (GetFontDefault().texture.id != 0) @@ -943,15 +1154,19 @@ int MeasureText(const char *text, int fontSize) if (fontSize < defaultFontSize) fontSize = defaultFontSize; int spacing = fontSize/defaultFontSize; - vec = MeasureTextEx(GetFontDefault(), text, (float)fontSize, (float)spacing); + textSize = MeasureTextEx(GetFontDefault(), text, (float)fontSize, (float)spacing); } - return (int)vec.x; + return (int)textSize.x; } // Measure string size for Font Vector2 MeasureTextEx(Font font, const char *text, float fontSize, float spacing) { + Vector2 textSize = { 0 }; + + if ((font.texture.id == 0) || (text == NULL)) return textSize; + int size = TextLength(text); // Get size in bytes of text int tempByteCounter = 0; // Used to count longer text line num chars int byteCounter = 0; @@ -996,11 +1211,10 @@ Vector2 MeasureTextEx(Font font, const char *text, float fontSize, float spacing if (tempTextWidth < textWidth) tempTextWidth = textWidth; - Vector2 vec = { 0 }; - vec.x = tempTextWidth*scaleFactor + (float)((tempByteCounter - 1)*spacing); // Adds chars spacing to measure - vec.y = textHeight*scaleFactor; + textSize.x = tempTextWidth*scaleFactor + (float)((tempByteCounter - 1)*spacing); // Adds chars spacing to measure + textSize.y = textHeight*scaleFactor; - return vec; + return textSize; } // Get index position for a unicode character on font @@ -1179,7 +1393,7 @@ const char *TextSubtext(const char *text, int position, int length) // Replace text string // REQUIRES: strlen(), strstr(), strncpy(), strcpy() -// WARNING: Returned buffer must be freed by the user (if return != NULL) +// WARNING: Allocated memory must be manually freed char *TextReplace(char *text, const char *replace, const char *by) { // Sanity checks and initialization @@ -1228,7 +1442,7 @@ char *TextReplace(char *text, const char *replace, const char *by) } // Insert text in a specific position, moves all text forward -// WARNING: Allocated memory should be manually freed +// WARNING: Allocated memory must be manually freed char *TextInsert(const char *text, const char *insert, int position) { int textLen = TextLength(text); @@ -1412,8 +1626,8 @@ const char *TextToPascal(const char *text) // Encode text codepoint into UTF-8 text // REQUIRES: memcpy() -// WARNING: Allocated memory should be manually freed -char *TextCodepointsToUTF8(int *codepoints, int length) +// WARNING: Allocated memory must be manually freed +char *TextCodepointsToUTF8(const int *codepoints, int length) { // We allocate enough memory fo fit all possible codepoints // NOTE: 5 bytes for every codepoint should be enough @@ -1593,7 +1807,7 @@ int GetCodepoint(const char *text, int *bytesProcessed) if (((octet == 0xe0) && !((octet1 >= 0xa0) && (octet1 <= 0xbf))) || ((octet == 0xed) && !((octet1 >= 0x80) && (octet1 <= 0x9f)))) { *bytesProcessed = 2; return code; } - if ((octet >= 0xe0) && (0 <= 0xef)) + if ((octet >= 0xe0) && (octet <= 0xef)) { code = ((octet & 0xf) << 12) | ((octet1 & 0x3f) << 6) | (octet2 & 0x3f); *bytesProcessed = 3; @@ -1795,3 +2009,5 @@ static Font LoadBMFont(const char *fileName) return font; } #endif + +#endif // SUPPORT_MODULE_RTEXT diff --git a/raylib/rtextures.c b/raylib/rtextures.c index 34c4685..2490d1d 100644 --- a/raylib/rtextures.c +++ b/raylib/rtextures.c @@ -4,11 +4,15 @@ * * CONFIGURATION: * +* #define SUPPORT_MODULE_RTEXTURES +* rtextures module is included in the build +* * #define SUPPORT_FILEFORMAT_BMP * #define SUPPORT_FILEFORMAT_PNG * #define SUPPORT_FILEFORMAT_TGA * #define SUPPORT_FILEFORMAT_JPG * #define SUPPORT_FILEFORMAT_GIF +* #define SUPPORT_FILEFORMAT_QOI * #define SUPPORT_FILEFORMAT_PSD * #define SUPPORT_FILEFORMAT_PIC * #define SUPPORT_FILEFORMAT_HDR @@ -33,12 +37,12 @@ * DEPENDENCIES: * stb_image - Multiple image formats loading (JPEG, PNG, BMP, TGA, PSD, GIF, PIC) * NOTE: stb_image has been slightly modified to support Android platform. -* stb_image_resize - Multiple image resize algorythms +* stb_image_resize - Multiple image resize algorithms * * * LICENSE: zlib/libpng * -* Copyright (c) 2013-2021 Ramon Santamaria (@raysan5) +* Copyright (c) 2013-2022 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. @@ -64,11 +68,13 @@ #include "config.h" // Defines module configuration flags #endif +#if defined(SUPPORT_MODULE_RTEXTURES) + #include "utils.h" // Required for: TRACELOG() and fopen() Android mapping #include "rlgl.h" // OpenGL abstraction layer to OpenGL 1.1, 3.3 or ES2 #include // Required for: malloc(), free() -#include // Required for: strlen() [Used in ImageTextEx()] +#include // Required for: strlen() [Used in ImageTextEx()], strcmp() [Used in LoadImageFromMemory()] #include // Required for: fabsf() #include // Required for: sprintf() [Used in ExportImageAsCode()] @@ -124,6 +130,14 @@ // NOTE: Used to read image data (multiple formats support) #endif +#if defined(SUPPORT_FILEFORMAT_QOI) + #define QOI_MALLOC RL_MALLOC + #define QOI_FREE RL_FREE + + #define QOI_IMPLEMENTATION + #include "external/qoi.h" +#endif + #if defined(SUPPORT_IMAGE_EXPORT) #define STBIW_MALLOC RL_MALLOC #define STBIW_FREE RL_FREE @@ -202,7 +216,8 @@ Image LoadImage(const char *fileName) defined(SUPPORT_FILEFORMAT_PIC) || \ defined(SUPPORT_FILEFORMAT_HDR) || \ defined(SUPPORT_FILEFORMAT_PSD) -#define STBI_REQUIRED + + #define STBI_REQUIRED #endif // Loading file to memory @@ -286,36 +301,32 @@ Image LoadImageAnim(const char *fileName, int *frames) } // Load image from memory buffer, fileType refers to extension: i.e. ".png" +// WARNING: File extension must be provided in lower-case Image LoadImageFromMemory(const char *fileType, const unsigned char *fileData, int dataSize) { Image image = { 0 }; - char fileExtLower[16] = { 0 }; - strcpy(fileExtLower, TextToLower(fileType)); - -#if defined(SUPPORT_FILEFORMAT_PNG) - if ((TextIsEqual(fileExtLower, ".png")) -#else if ((false) +#if defined(SUPPORT_FILEFORMAT_PNG) + || (strcmp(fileType, ".png") == 0) #endif #if defined(SUPPORT_FILEFORMAT_BMP) - || (TextIsEqual(fileExtLower, ".bmp")) + || (strcmp(fileType, ".bmp") == 0) #endif #if defined(SUPPORT_FILEFORMAT_TGA) - || (TextIsEqual(fileExtLower, ".tga")) + || (strcmp(fileType, ".tga") == 0) #endif #if defined(SUPPORT_FILEFORMAT_JPG) - || (TextIsEqual(fileExtLower, ".jpg") || - TextIsEqual(fileExtLower, ".jpeg")) + || ((strcmp(fileType, ".jpg") == 0) || (strcmp(fileType, ".jpeg") == 0)) #endif #if defined(SUPPORT_FILEFORMAT_GIF) - || (TextIsEqual(fileExtLower, ".gif")) + || (strcmp(fileType, ".gif") == 0) #endif #if defined(SUPPORT_FILEFORMAT_PIC) - || (TextIsEqual(fileExtLower, ".pic")) + || (strcmp(fileType, ".pic") == 0) #endif #if defined(SUPPORT_FILEFORMAT_PSD) - || (TextIsEqual(fileExtLower, ".psd")) + || (strcmp(fileType, ".psd") == 0) #endif ) { @@ -340,7 +351,7 @@ Image LoadImageFromMemory(const char *fileType, const unsigned char *fileData, i #endif } #if defined(SUPPORT_FILEFORMAT_HDR) - else if (TextIsEqual(fileExtLower, ".hdr")) + else if (strcmp(fileType, ".hdr") == 0) { #if defined(STBI_REQUIRED) if (fileData != NULL) @@ -362,20 +373,31 @@ Image LoadImageFromMemory(const char *fileType, const unsigned char *fileData, i #endif } #endif +#if defined(SUPPORT_FILEFORMAT_QOI) + else if (strcmp(fileType, ".qoi") == 0) + { + qoi_desc desc = { 0 }; + image.data = qoi_decode(fileData, dataSize, &desc, 4); + image.width = desc.width; + image.height = desc.height; + image.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; + image.mipmaps = 1; + } +#endif #if defined(SUPPORT_FILEFORMAT_DDS) - else if (TextIsEqual(fileExtLower, ".dds")) image = LoadDDS(fileData, dataSize); + else if (strcmp(fileType, ".dds") == 0) image = LoadDDS(fileData, dataSize); #endif #if defined(SUPPORT_FILEFORMAT_PKM) - else if (TextIsEqual(fileExtLower, ".pkm")) image = LoadPKM(fileData, dataSize); + else if (strcmp(fileType, ".pkm") == 0) image = LoadPKM(fileData, dataSize); #endif #if defined(SUPPORT_FILEFORMAT_KTX) - else if (TextIsEqual(fileExtLower, ".ktx")) image = LoadKTX(fileData, dataSize); + else if (strcmp(fileType, ".ktx") == 0) image = LoadKTX(fileData, dataSize); #endif #if defined(SUPPORT_FILEFORMAT_PVR) - else if (TextIsEqual(fileExtLower, ".pvr")) image = LoadPVR(fileData, dataSize); + else if (strcmp(fileType, ".pvr") == 0) image = LoadPVR(fileData, dataSize); #endif #if defined(SUPPORT_FILEFORMAT_ASTC) - else if (TextIsEqual(fileExtLower, ".astc")) image = LoadASTC(fileData, dataSize); + else if (strcmp(fileType, ".astc") == 0) image = LoadASTC(fileData, dataSize); #endif else TRACELOG(LOG_WARNING, "IMAGE: Data format not supported"); @@ -477,7 +499,28 @@ bool ExportImage(Image image, const char *fileName) else if (IsFileExtension(fileName, ".tga")) success = stbi_write_tga(fileName, image.width, image.height, channels, imgData); #endif #if defined(SUPPORT_FILEFORMAT_JPG) - else if (IsFileExtension(fileName, ".jpg")) success = stbi_write_jpg(fileName, image.width, image.height, channels, imgData, 90); // JPG quality: between 1 and 100 + else if (IsFileExtension(fileName, ".jpg") || + IsFileExtension(fileName, ".jpeg")) success = stbi_write_jpg(fileName, image.width, image.height, channels, imgData, 90); // JPG quality: between 1 and 100 +#endif +#if defined(SUPPORT_FILEFORMAT_QOI) + else if (IsFileExtension(fileName, ".qoi")) + { + channels = 0; + if (image.format == PIXELFORMAT_UNCOMPRESSED_R8G8B8) channels = 3; + else if (image.format == PIXELFORMAT_UNCOMPRESSED_R8G8B8A8) channels = 4; + else TRACELOG(LOG_WARNING, "IMAGE: Image pixel format must be R8G8B8 or R8G8B8A8"); + + if ((channels == 3) || (channels == 4)) + { + qoi_desc desc = { 0 }; + desc.width = image.width; + desc.height = image.height; + desc.channels = channels; + desc.colorspace = QOI_SRGB; + + success = qoi_write(fileName, imgData, &desc); + } + } #endif #if defined(SUPPORT_FILEFORMAT_KTX) else if (IsFileExtension(fileName, ".ktx")) success = SaveKTX(image, fileName); @@ -523,7 +566,7 @@ bool ExportImageAsCode(Image image, const char *fileName) byteCount += sprintf(txtData + byteCount, "// more info and bugs-report: github.com/raysan5/raylib //\n"); byteCount += sprintf(txtData + byteCount, "// feedback and support: ray[at]raylib.com //\n"); byteCount += sprintf(txtData + byteCount, "// //\n"); - byteCount += sprintf(txtData + byteCount, "// Copyright (c) 2018-2021 Ramon Santamaria (@raysan5) //\n"); + byteCount += sprintf(txtData + byteCount, "// Copyright (c) 2018-2022 Ramon Santamaria (@raysan5) //\n"); byteCount += sprintf(txtData + byteCount, "// //\n"); byteCount += sprintf(txtData + byteCount, "////////////////////////////////////////////////////////////////////////////////////////\n\n"); @@ -549,8 +592,8 @@ bool ExportImageAsCode(Image image, const char *fileName) #endif // SUPPORT_IMAGE_EXPORT - if (success != 0) TRACELOG(LOG_INFO, "FILEIO: [%s] Image exported successfully", fileName); - else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to export image", fileName); + if (success != 0) TRACELOG(LOG_INFO, "FILEIO: [%s] Image as code exported successfully", fileName); + else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to export image as code", fileName); return success; } @@ -732,7 +775,7 @@ Image GenImageCellular(int width, int height, int tileSize) { int y = (i/seedsPerRow)*tileSize + GetRandomValue(0, tileSize - 1); int x = (i%seedsPerRow)*tileSize + GetRandomValue(0, tileSize - 1); - seeds[i] = (Vector2){ (float)x, (float)y}; + seeds[i] = (Vector2){ (float)x, (float)y }; } for (int y = 0; y < height; y++) @@ -743,7 +786,7 @@ Image GenImageCellular(int width, int height, int tileSize) { int tileX = x/tileSize; - float minDistance = (float)strtod("Inf", NULL); + float minDistance = 65536.0f; //(float)strtod("Inf", NULL); // Check all adjacent tiles for (int i = -1; i < 2; i++) @@ -832,11 +875,11 @@ Image ImageFromImage(Image image, Rectangle rec) result.width = (int)rec.width; result.height = (int)rec.height; - result.data = RL_CALLOC((int)(rec.width*rec.height)*bytesPerPixel, 1); + result.data = RL_CALLOC((int)rec.width*(int)rec.height*bytesPerPixel, 1); result.format = image.format; result.mipmaps = 1; - for (int y = 0; y < rec.height; y++) + for (int y = 0; y < (int)rec.height; y++) { memcpy(((unsigned char *)result.data) + y*(int)rec.width*bytesPerPixel, ((unsigned char *)image.data) + ((y + (int)rec.y)*image.width + (int)rec.x)*bytesPerPixel, (int)rec.width*bytesPerPixel); } @@ -1092,35 +1135,41 @@ void ImageToPOT(Image *image, Color fill) // Create an image from text (default font) Image ImageText(const char *text, int fontSize, Color color) { + Image imText = { 0 }; +#if defined(SUPPORT_MODULE_RTEXT) int defaultFontSize = 10; // Default Font chars height in pixel if (fontSize < defaultFontSize) fontSize = defaultFontSize; int spacing = fontSize/defaultFontSize; - - Image imText = ImageTextEx(GetFontDefault(), text, (float)fontSize, (float)spacing, color); - + imText = ImageTextEx(GetFontDefault(), text, (float)fontSize, (float)spacing, color); // WARNING: Module required: rtext +#else + imText = GenImageColor(200, 60, BLACK); // Generating placeholder black image rectangle + TRACELOG(LOG_WARNING, "IMAGE: ImageTextEx() requires module: rtext"); +#endif return imText; } // Create an image from text (custom sprite font) Image ImageTextEx(Font font, const char *text, float fontSize, float spacing, Color tint) { + Image imText = { 0 }; +#if defined(SUPPORT_MODULE_RTEXT) int size = (int)strlen(text); // Get size in bytes of text int textOffsetX = 0; // Image drawing position X int textOffsetY = 0; // Offset between lines (on line break '\n') // NOTE: Text image is generated at font base size, later scaled to desired font size - Vector2 imSize = MeasureTextEx(font, text, (float)font.baseSize, spacing); + Vector2 imSize = MeasureTextEx(font, text, (float)font.baseSize, spacing); // WARNING: Module required: rtext // Create image to store text - Image imText = GenImageColor((int)imSize.x, (int)imSize.y, BLANK); + imText = GenImageColor((int)imSize.x, (int)imSize.y, BLANK); for (int i = 0; i < size; i++) { // Get next codepoint from byte string and glyph index in font int codepointByteCount = 0; - int codepoint = GetCodepoint(&text[i], &codepointByteCount); - int index = GetGlyphIndex(font, codepoint); + int codepoint = GetCodepoint(&text[i], &codepointByteCount); // WARNING: Module required: rtext + int index = GetGlyphIndex(font, codepoint); // WARNING: Module required: rtext // NOTE: Normally we exit the decoding sequence as soon as a bad byte is found (and return 0x3f) // but we need to draw all of the bad bytes using the '?' symbol moving one byte @@ -1155,10 +1204,14 @@ Image ImageTextEx(Font font, const char *text, float fontSize, float spacing, Co TRACELOG(LOG_INFO, "IMAGE: Text scaled by factor: %f", scaleFactor); // Using nearest-neighbor scaling algorithm for default font + // WARNING: Module required: rtext if (font.texture.id == GetFontDefault().texture.id) ImageResizeNN(&imText, (int)(imSize.x*scaleFactor), (int)(imSize.y*scaleFactor)); else ImageResize(&imText, (int)(imSize.x*scaleFactor), (int)(imSize.y*scaleFactor)); } - +#else + imText = GenImageColor(200, 60, BLACK); // Generating placeholder black image rectangle + TRACELOG(LOG_WARNING, "IMAGE: ImageTextEx() requires module: rtext"); +#endif return imText; } @@ -1361,10 +1414,12 @@ void ImageResize(Image *image, int newWidth, int newHeight) // Security check to avoid program crash if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; - bool fastPath = true; - if ((image->format != PIXELFORMAT_UNCOMPRESSED_GRAYSCALE) && (image->format != PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA) && (image->format != PIXELFORMAT_UNCOMPRESSED_R8G8B8) && (image->format != PIXELFORMAT_UNCOMPRESSED_R8G8B8A8)) fastPath = true; - - if (fastPath) + // Check if we can use a fast path on image scaling + // It can be for 8 bit per channel images with 1 to 4 channels per pixel + if ((image->format == PIXELFORMAT_UNCOMPRESSED_GRAYSCALE) || + (image->format == PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA) || + (image->format == PIXELFORMAT_UNCOMPRESSED_R8G8B8) || + (image->format == PIXELFORMAT_UNCOMPRESSED_R8G8B8A8)) { int bytesPerPixel = GetPixelDataSize(1, 1, image->format); unsigned char *output = (unsigned char *)RL_MALLOC(newWidth*newHeight*bytesPerPixel); @@ -1836,10 +1891,10 @@ void ImageColorTint(Image *image, Color color) unsigned char b = (unsigned char)(((float)pixels[index].b/255*cB)*255.0f); unsigned char a = (unsigned char)(((float)pixels[index].a/255*cA)*255.0f); - pixels[y*image->width + x].r = r; - pixels[y*image->width + x].g = g; - pixels[y*image->width + x].b = b; - pixels[y*image->width + x].a = a; + pixels[index].r = r; + pixels[index].g = g; + pixels[index].b = b; + pixels[index].a = a; } } @@ -1905,25 +1960,25 @@ void ImageColorContrast(Image *image, float contrast) for (int x = 0; x < image->width; x++) { float pR = (float)pixels[y*image->width + x].r/255.0f; - pR -= 0.5; + pR -= 0.5f; pR *= contrast; - pR += 0.5; + pR += 0.5f; pR *= 255; if (pR < 0) pR = 0; if (pR > 255) pR = 255; float pG = (float)pixels[y*image->width + x].g/255.0f; - pG -= 0.5; + pG -= 0.5f; pG *= contrast; - pG += 0.5; + pG += 0.5f; pG *= 255; if (pG < 0) pG = 0; if (pG > 255) pG = 255; float pB = (float)pixels[y*image->width + x].b/255.0f; - pB -= 0.5; + pB -= 0.5f; pB *= contrast; - pB += 0.5; + pB += 0.5f; pB *= 255; if (pB < 0) pB = 0; if (pB > 255) pB = 255; @@ -2358,7 +2413,20 @@ Color GetImageColor(Image image, int x, int y) // Clear image background with given color void ImageClearBackground(Image *dst, Color color) { - for (int i = 0; i < dst->width*dst->height; ++i) ImageDrawPixel(dst, i%dst->width, i/dst->width, color); + // Security check to avoid program crash + if ((dst->data == NULL) || (dst->width == 0) || (dst->height == 0)) return; + + // Fill in first pixel based on image format + ImageDrawPixel(dst, 0, 0, color); + + unsigned char *pSrcPixel = (unsigned char *)dst->data; + int bytesPerPixel = GetPixelDataSize(1, 1, dst->format); + + // Repeat the first pixel data throughout the image + for (int i = 1; i < dst->width * dst->height; i++) + { + memcpy(pSrcPixel + i * bytesPerPixel, pSrcPixel, bytesPerPixel); + } } // Draw pixel within an image @@ -2632,13 +2700,21 @@ void ImageDrawRectangleRec(Image *dst, Rectangle rec, Color color) int ey = sy + (int)rec.height; int sx = (int)rec.x; - int ex = sx + (int)rec.width; + + int bytesPerPixel = GetPixelDataSize(1, 1, dst->format); for (int y = sy; y < ey; y++) { - for (int x = sx; x < ex; x++) + // Fill in the first pixel of the row based on image format + ImageDrawPixel(dst, sx, y, color); + + int bytesOffset = ((y * dst->width) + sx) * bytesPerPixel; + unsigned char *pSrcPixel = (unsigned char *)dst->data + bytesOffset; + + // Repeat the first pixel data throughout the row + for (int x = 1; x < (int)rec.width; x++) { - ImageDrawPixel(dst, x, y, color); + memcpy(pSrcPixel + x * bytesPerPixel, pSrcPixel, bytesPerPixel); } } } @@ -2716,6 +2792,9 @@ void ImageDraw(Image *dst, Image src, Rectangle srcRec, Rectangle dstRec, Color // [x] Consider fast path: no alpha blending required cases (src has no alpha) // [x] Consider fast path: same src/dst format with no alpha -> direct line copy // [-] GetPixelColor(): Get Vector4 instead of Color, easier for ColorAlphaBlend() + // [ ] Support f32bit channels drawing + + // TODO: Support PIXELFORMAT_UNCOMPRESSED_R32, PIXELFORMAT_UNCOMPRESSED_R32G32B32, PIXELFORMAT_UNCOMPRESSED_R32G32B32A32 Color colSrc, colDst, blend; bool blendRequired = true; @@ -2768,10 +2847,13 @@ void ImageDraw(Image *dst, Image src, Rectangle srcRec, Rectangle dstRec, Color // Draw text (default font) within an image (destination) void ImageDrawText(Image *dst, const char *text, int posX, int posY, int fontSize, Color color) { +#if defined(SUPPORT_MODULE_RTEXT) Vector2 position = { (float)posX, (float)posY }; - - // NOTE: For default font, sapcing is set to desired font size / default font size (10) - ImageDrawTextEx(dst, GetFontDefault(), text, position, (float)fontSize, (float)fontSize/10, color); + // NOTE: For default font, spacing is set to desired font size / default font size (10) + ImageDrawTextEx(dst, GetFontDefault(), text, position, (float)fontSize, (float)fontSize/10, color); // WARNING: Module required: rtext +#else + TRACELOG(LOG_WARNING, "IMAGE: ImageDrawText() requires module: rtext"); +#endif } // Draw text (custom sprite font) within an image (destination) @@ -2812,7 +2894,7 @@ Texture2D LoadTextureFromImage(Image image) { Texture2D texture = { 0 }; - if ((image.data != NULL) && (image.width != 0) && (image.height != 0)) + if ((image.width != 0) && (image.height != 0)) { texture.id = rlLoadTexture(image.data, image.width, image.height, image.format, image.mipmaps); } @@ -2849,6 +2931,7 @@ TextureCubemap LoadTextureCubemap(Image image, int layout) cubemap.height = cubemap.width; } + // Layout provided or already auto-detected if (layout != CUBEMAP_LAYOUT_AUTO_DETECT) { int size = cubemap.width; @@ -2859,8 +2942,7 @@ TextureCubemap LoadTextureCubemap(Image image, int layout) if (layout == CUBEMAP_LAYOUT_LINE_VERTICAL) { - faces = image; - for (int i = 0; i < 6; i++) faceRecs[i].y = (float)size*i; + faces = ImageCopy(image); // Image data already follows expected convention } else if (layout == CUBEMAP_LAYOUT_PANORAMA) { @@ -2894,10 +2976,12 @@ TextureCubemap LoadTextureCubemap(Image image, int layout) ImageFormat(&faces, image.format); // NOTE: Image formating does not work with compressed textures + + for (int i = 0; i < 6; i++) ImageDraw(&faces, image, faceRecs[i], (Rectangle){ 0, (float)size*i, (float)size, (float)size }, WHITE); } - for (int i = 0; i < 6; i++) ImageDraw(&faces, image, faceRecs[i], (Rectangle){ 0, (float)size*i, (float)size, (float)size }, WHITE); - + // NOTE: Cubemap data is expected to be provided as 6 images in a single data array, + // one after the other (that's a vertical image), following convention: +X, -X, +Y, -Y, +Z, -Z cubemap.id = rlLoadTextureCubemap(faces.data, size, faces.format); if (cubemap.id == 0) TRACELOG(LOG_WARNING, "IMAGE: Failed to load cubemap image"); @@ -3071,6 +3155,7 @@ void SetTextureWrap(Texture2D texture, int wrap) { case TEXTURE_WRAP_REPEAT: { + // NOTE: It only works if NPOT textures are supported, i.e. OpenGL ES 2.0 could not support it rlTextureParameters(texture.id, RL_TEXTURE_WRAP_S, RL_TEXTURE_WRAP_REPEAT); rlTextureParameters(texture.id, RL_TEXTURE_WRAP_T, RL_TEXTURE_WRAP_REPEAT); } break; @@ -3132,6 +3217,8 @@ void DrawTextureRec(Texture2D texture, Rectangle source, Vector2 position, Color // i.e tiling = { 1.0f, 1.0f } refers to all texture, offset = { 0.5f, 0.5f } moves texture origin to center void DrawTextureQuad(Texture2D texture, Vector2 tiling, Vector2 offset, Rectangle quad, Color tint) { + // WARNING: This solution only works if TEXTURE_WRAP_REPEAT is supported, + // NPOT textures supported is required and OpenGL ES 2.0 could not support it Rectangle source = { offset.x*texture.width, offset.y*texture.height, tiling.x*texture.width, tiling.y*texture.height }; Vector2 origin = { 0.0f, 0.0f }; @@ -3143,6 +3230,7 @@ void DrawTextureQuad(Texture2D texture, Vector2 tiling, Vector2 offset, Rectangl void DrawTextureTiled(Texture2D texture, Rectangle source, Rectangle dest, Vector2 origin, float rotation, float scale, Color tint) { if ((texture.id <= 0) || (scale <= 0.0f)) return; // Wanna see a infinite loop?!...just delete this line! + if ((source.width == 0) || (source.height == 0)) return; int tileWidth = (int)(source.width*scale), tileHeight = (int)(source.height*scale); if ((dest.width < tileWidth) && (dest.height < tileHeight)) @@ -3406,6 +3494,8 @@ void DrawTextureNPatch(Texture2D texture, NPatchInfo nPatchInfo, Rectangle dest, coordD.x = (nPatchInfo.source.x + nPatchInfo.source.width)/width; coordD.y = (nPatchInfo.source.y + nPatchInfo.source.height)/height; + rlCheckRenderBatchLimit(9 * 3 * 2); // Maxium number of verts that could happen + rlSetTexture(texture.id); rlPushMatrix(); @@ -3553,7 +3643,7 @@ void DrawTexturePoly(Texture2D texture, Vector2 center, Vector2 *points, Vector2 rlSetTexture(texture.id); - // Texturing is only supported on QUADs + // Texturing is only supported on RL_QUADS rlBegin(RL_QUADS); rlColor4ub(tint.r, tint.g, tint.b, tint.a); @@ -3721,10 +3811,10 @@ Color ColorAlphaBlend(Color dst, Color src, Color tint) Color out = WHITE; // Apply color tint to source color - src.r = (unsigned char)(((unsigned int)src.r*(unsigned int)tint.r) >> 8); - src.g = (unsigned char)(((unsigned int)src.g*(unsigned int)tint.g) >> 8); - src.b = (unsigned char)(((unsigned int)src.b*(unsigned int)tint.b) >> 8); - src.a = (unsigned char)(((unsigned int)src.a*(unsigned int)tint.a) >> 8); + src.r = (unsigned char)(((unsigned int)src.r*((unsigned int)tint.r+1)) >> 8); + src.g = (unsigned char)(((unsigned int)src.g*((unsigned int)tint.g+1)) >> 8); + src.b = (unsigned char)(((unsigned int)src.b*((unsigned int)tint.b+1)) >> 8); + src.a = (unsigned char)(((unsigned int)src.a*((unsigned int)tint.a+1)) >> 8); //#define COLORALPHABLEND_FLOAT #define COLORALPHABLEND_INTEGERS @@ -4259,6 +4349,7 @@ static Image LoadPKM(const unsigned char *fileData, unsigned int fileSize) #if defined(SUPPORT_FILEFORMAT_KTX) // Load KTX compressed image data (ETC1/ETC2 compression) +// TODO: Review KTX loading, many things changed! static Image LoadKTX(const unsigned char *fileData, unsigned int fileSize) { unsigned char *fileDataPtr = (unsigned char *)fileData; @@ -4333,6 +4424,8 @@ static Image LoadKTX(const unsigned char *fileData, unsigned int fileSize) if (ktxHeader->glInternalFormat == 0x8D64) image.format = PIXELFORMAT_COMPRESSED_ETC1_RGB; else if (ktxHeader->glInternalFormat == 0x9274) image.format = PIXELFORMAT_COMPRESSED_ETC2_RGB; else if (ktxHeader->glInternalFormat == 0x9278) image.format = PIXELFORMAT_COMPRESSED_ETC2_EAC_RGBA; + + // TODO: Support uncompressed data formats? Right now it returns format = 0! } } @@ -4341,11 +4434,12 @@ static Image LoadKTX(const unsigned char *fileData, unsigned int fileSize) // Save image data as KTX file // NOTE: By default KTX 1.1 spec is used, 2.0 is still on draft (01Oct2018) +// TODO: Review KTX saving, many things changed! static int SaveKTX(Image image, const char *fileName) { // KTX file Header (64 bytes) // v1.1 - https://www.khronos.org/opengles/sdk/tools/KTX/file_format_spec/ - // v2.0 - http://github.khronos.org/KTX-Specification/ - still on draft, not ready for implementation + // v2.0 - http://github.khronos.org/KTX-Specification/ - Final specs by 2021-04-18 typedef struct { char id[12]; // Identifier: "«KTX 11»\r\n\x1A\n" // KTX 2.0: "«KTX 22»\r\n\x1A\n" unsigned int endianness; // Little endian: 0x01 0x02 0x03 0x04 @@ -4480,7 +4574,7 @@ static Image LoadPVR(const unsigned char *fileData, unsigned int fileSize) unsigned int flags; unsigned char channels[4]; // pixelFormat high part unsigned char channelDepth[4]; // pixelFormat low part - unsigned int colourSpace; + unsigned int colorSpace; unsigned int channelType; unsigned int height; unsigned int width; @@ -4759,3 +4853,5 @@ static Vector4 *LoadImageDataNormalized(Image image) return pixels; } + +#endif // SUPPORT_MODULE_RTEXTURES diff --git a/raylib/utils.c b/raylib/utils.c index 77d0ffb..9809e90 100644 --- a/raylib/utils.c +++ b/raylib/utils.c @@ -11,7 +11,7 @@ * * LICENSE: zlib/libpng * -* Copyright (c) 2014-2021 Ramon Santamaria (@raysan5) +* Copyright (c) 2014-2022 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. @@ -148,6 +148,7 @@ void TraceLog(int logType, const char *text, ...) strcat(buffer, text); strcat(buffer, "\n"); vprintf(buffer, args); + fflush(stdout); #endif va_end(args); @@ -268,6 +269,51 @@ bool SaveFileData(const char *fileName, void *data, unsigned int bytesToWrite) return success; } +// Export data to code (.h), returns true on success +bool ExportDataAsCode(const char *data, unsigned int size, const char *fileName) +{ + bool success = false; + +#ifndef TEXT_BYTES_PER_LINE + #define TEXT_BYTES_PER_LINE 20 +#endif + + // NOTE: Text data buffer size is estimated considering raw data size in bytes + // and requiring 6 char bytes for every byte: "0x00, " + char *txtData = (char *)RL_CALLOC(size*6 + 2000, sizeof(char)); + + int byteCount = 0; + byteCount += sprintf(txtData + byteCount, "////////////////////////////////////////////////////////////////////////////////////////\n"); + byteCount += sprintf(txtData + byteCount, "// //\n"); + byteCount += sprintf(txtData + byteCount, "// DataAsCode exporter v1.0 - Raw data exported as an array of bytes //\n"); + byteCount += sprintf(txtData + byteCount, "// //\n"); + byteCount += sprintf(txtData + byteCount, "// more info and bugs-report: github.com/raysan5/raylib //\n"); + byteCount += sprintf(txtData + byteCount, "// feedback and support: ray[at]raylib.com //\n"); + byteCount += sprintf(txtData + byteCount, "// //\n"); + byteCount += sprintf(txtData + byteCount, "// Copyright (c) 2022 Ramon Santamaria (@raysan5) //\n"); + byteCount += sprintf(txtData + byteCount, "// //\n"); + byteCount += sprintf(txtData + byteCount, "////////////////////////////////////////////////////////////////////////////////////////\n\n"); + + // Get file name from path and convert variable name to uppercase + char varFileName[256] = { 0 }; + strcpy(varFileName, GetFileNameWithoutExt(fileName)); + for (int i = 0; varFileName[i] != '\0'; i++) if ((varFileName[i] >= 'a') && (varFileName[i] <= 'z')) { varFileName[i] = varFileName[i] - 32; } + + byteCount += sprintf(txtData + byteCount, "static unsigned char %s_DATA[%i] = { ", varFileName, size); + for (unsigned int i = 0; i < size - 1; i++) byteCount += sprintf(txtData + byteCount, ((i%TEXT_BYTES_PER_LINE == 0)? "0x%x,\n" : "0x%x, "), data[i]); + byteCount += sprintf(txtData + byteCount, "0x%x };\n", data[size - 1]); + + // NOTE: Text data size exported is determined by '\0' (NULL) character + success = SaveFileText(fileName, txtData); + + RL_FREE(txtData); + + if (success != 0) TRACELOG(LOG_INFO, "FILEIO: [%s] Data as code exported successfully", fileName); + else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to export data as code", fileName); + + return success; +} + // Load text data from file, returns a '\0' terminated string // NOTE: text chars array should be freed manually char *LoadFileText(const char *fileName) diff --git a/raylib/utils.h b/raylib/utils.h index c9b3318..a2b3e03 100644 --- a/raylib/utils.h +++ b/raylib/utils.h @@ -5,7 +5,7 @@ * * LICENSE: zlib/libpng * -* Copyright (c) 2014-2021 Ramon Santamaria (@raysan5) +* Copyright (c) 2014-2022 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. @@ -65,7 +65,7 @@ //---------------------------------------------------------------------------------- // Module Functions Declaration //---------------------------------------------------------------------------------- -#ifdef __cplusplus +#if defined(__cplusplus) extern "C" { // Prevents name mangling of functions #endif @@ -74,7 +74,7 @@ void InitAssetManager(AAssetManager *manager, const char *dataPath); // Initia FILE *android_fopen(const char *fileName, const char *mode); // Replacement for fopen() -> Read-only! #endif -#ifdef __cplusplus +#if defined(__cplusplus) } #endif