aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorvictorfisac <victorfisac@gmail.com>2016-05-20 14:03:23 +0200
committervictorfisac <victorfisac@gmail.com>2016-05-20 14:03:23 +0200
commit4f1bee31654dec5f5cea2ac9d291d202df504745 (patch)
tree42a5028ebab3277a8d46bdcf3fea2990e1aa590d /src
parentea7afc8ec835040d84d79ae318f7aebb9f1e189c (diff)
parentdcf5f45f687f2a534286aecd5e6471a0440b0c21 (diff)
downloadraylib-4f1bee31654dec5f5cea2ac9d291d202df504745.tar.gz
raylib-4f1bee31654dec5f5cea2ac9d291d202df504745.zip
Merge remote-tracking branch 'refs/remotes/raysan5/develop' into develop
Diffstat (limited to 'src')
-rw-r--r--src/Makefile30
-rw-r--r--src/audio.c676
-rw-r--r--src/audio.h36
-rw-r--r--src/core.c975
-rw-r--r--src/easings.h17
-rw-r--r--src/gestures.c6
-rw-r--r--src/gestures.h4
-rw-r--r--src/jar_xm.h2666
-rw-r--r--src/models.c444
-rw-r--r--src/physac.c12
-rw-r--r--src/physac.h12
-rw-r--r--src/raylib.h232
-rw-r--r--src/raymath.h18
-rw-r--r--src/rlgl.c2307
-rw-r--r--src/rlgl.h123
-rw-r--r--src/shapes.c1
-rw-r--r--src/stb_image.h263
-rw-r--r--src/stb_image_resize.h110
-rw-r--r--src/stb_image_write.h14
-rw-r--r--src/stb_rect_pack.h26
-rw-r--r--src/stb_truetype.h52
-rw-r--r--src/stb_vorbis.c112
-rw-r--r--src/stb_vorbis.h20
-rw-r--r--src/text.c98
-rw-r--r--src/textures.c42
-rw-r--r--src/windows_compile.bat2
26 files changed, 6000 insertions, 2298 deletions
diff --git a/src/Makefile b/src/Makefile
index cab2ced0..12f4609b 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -21,6 +21,8 @@
#
#**************************************************************************************************
+.PHONY: all clean
+
# define raylib platform to compile for
# possible platforms: PLATFORM_DESKTOP PLATFORM_RPI PLATFORM_WEB
PLATFORM ?= PLATFORM_DESKTOP
@@ -84,8 +86,6 @@ else
# external libraries headers
# GLFW3
INCLUDES += -I../external/glfw3/include
-# GLEW
- INCLUDES += -I../external/glew/include
# OpenAL Soft
INCLUDES += -I../external/openal_soft/include
endif
@@ -99,9 +99,9 @@ else
endif
-# typing 'make' will invoke the first target entry in the file,
-# in this case, the 'default' target entry is raylib
-default: raylib
+# typing 'make' will invoke the default target entry called 'all',
+# in this case, the 'default' target entry is basic_game
+all: raylib
# compile raylib library
raylib: $(OBJS)
@@ -143,7 +143,7 @@ models.o: models.c
# compile audio module
audio.o: audio.c
$(CC) -c audio.c $(CFLAGS) $(INCLUDES) -D$(PLATFORM)
-
+
# compile stb_vorbis library
stb_vorbis.o: stb_vorbis.c
$(CC) -c stb_vorbis.c -O1 $(INCLUDES) -D$(PLATFORM)
@@ -163,22 +163,22 @@ gestures.o: gestures.c
# clean everything
clean:
ifeq ($(PLATFORM),PLATFORM_DESKTOP)
- ifeq ($(PLATFORM_OS),OSX)
- rm -f *.o libraylib.a
- else
- ifeq ($(PLATFORM_OS),LINUX)
- find -type f -executable | xargs file -i | grep -E 'x-object|x-archive|x-sharedlib|x-executable' | rev | cut -d ':' -f 2- | rev | xargs rm -f
- else
+ ifeq ($(PLATFORM_OS),WINDOWS)
del *.o libraylib.a
+ else
+ rm -f *.o libraylib.a
endif
+endif
+ifeq ($(PLATFORM),PLATFORM_WEB)
+ ifeq ($(PLATFORM_OS),WINDOWS)
+ del *.o libraylib.bc
+ else
+ rm -f *.o libraylib.bc
endif
endif
ifeq ($(PLATFORM),PLATFORM_RPI)
rm -f *.o libraylib.a
endif
-ifeq ($(PLATFORM),PLATFORM_WEB)
- del *.o libraylib.bc
-endif
@echo Cleaning done
# instead of defining every module one by one, we can define a pattern
diff --git a/src/audio.c b/src/audio.c
index 260f6778..43e8be14 100644
--- a/src/audio.c
+++ b/src/audio.c
@@ -37,6 +37,7 @@
#include "AL/al.h" // OpenAL basic header
#include "AL/alc.h" // OpenAL context header (like OpenGL, OpenAL requires a context to work)
+#include "AL/alext.h" // extensions for other format types
#include <stdlib.h> // Declares malloc() and free() for memory management
#include <string.h> // Required for strcmp()
@@ -50,39 +51,57 @@
#endif
//#define STB_VORBIS_HEADER_ONLY
-#include "stb_vorbis.h" // OGG loading functions
+#include "stb_vorbis.h" // OGG loading functions
+
+#define JAR_XM_IMPLEMENTATION
+#include "jar_xm.h" // For playing .xm files
//----------------------------------------------------------------------------------
// Defines and Macros
//----------------------------------------------------------------------------------
-#define MUSIC_STREAM_BUFFERS 2
-
-#if defined(PLATFORM_RPI)
- // NOTE: On RPI should be lower to avoid frame-stalls
- #define MUSIC_BUFFER_SIZE 4096*2 // PCM data buffer (short) - 16Kb (RPI)
+#define MAX_STREAM_BUFFERS 2 // Number of buffers for each alSource
+#define MAX_MIX_CHANNELS 4 // Number of open AL sources
+#define MAX_MUSIC_STREAMS 2 // Number of simultanious music sources
+
+#if defined(PLATFORM_RPI) || defined(PLATFORM_ANDROID)
+ // NOTE: On RPI and Android should be lower to avoid frame-stalls
+ #define MUSIC_BUFFER_SIZE_SHORT 4096*2 // PCM data buffer (short) - 16Kb (RPI)
+ #define MUSIC_BUFFER_SIZE_FLOAT 4096 // PCM data buffer (float) - 16Kb (RPI)
#else
// NOTE: On HTML5 (emscripten) this is allocated on heap, by default it's only 16MB!...just take care...
- #define MUSIC_BUFFER_SIZE 4096*8 // PCM data buffer (short) - 64Kb
+ #define MUSIC_BUFFER_SIZE_SHORT 4096*8 // PCM data buffer (short) - 64Kb
+ #define MUSIC_BUFFER_SIZE_FLOAT 4096*4 // PCM data buffer (float) - 64Kb
#endif
//----------------------------------------------------------------------------------
// Types and Structures Definition
//----------------------------------------------------------------------------------
+// Used to create custom audio streams that are not bound to a specific file. There can be
+// no more than 4 concurrent mixchannels in use. This is due to each active mixc being tied to
+// a dedicated mix channel.
+typedef struct MixChannel_t {
+ unsigned short sampleRate; // default is 48000
+ unsigned char channels; // 1=mono,2=stereo
+ unsigned char mixChannel; // 0-3 or mixA-mixD, each mix channel can receive up to one dedicated audio stream
+ bool floatingPoint; // if false then the short datatype is used instead
+ bool playing; // false if paused
+ ALenum alFormat; // openAL format specifier
+ ALuint alSource; // openAL source
+ ALuint alBuffer[MAX_STREAM_BUFFERS]; // openAL sample buffer
+} MixChannel_t;
+
// Music type (file streaming from memory)
-// NOTE: Anything longer than ~10 seconds should be streamed...
+// NOTE: Anything longer than ~10 seconds should be streamed into a mix channel...
typedef struct Music {
stb_vorbis *stream;
-
- ALuint buffers[MUSIC_STREAM_BUFFERS];
- ALuint source;
- ALenum format;
-
- int channels;
- int sampleRate;
+ jar_xm_context_t *chipctx; // Stores jar_xm mixc
+ MixChannel_t *mixc; // mix channel
+
int totalSamplesLeft;
+ float totalLengthSeconds;
bool loop;
-
+ bool chipTune; // True if chiptune is loaded
} Music;
#if defined(AUDIO_STANDALONE)
@@ -92,19 +111,28 @@ typedef enum { INFO = 0, ERROR, WARNING, DEBUG, OTHER } TraceLogType;
//----------------------------------------------------------------------------------
// Global Variables Definition
//----------------------------------------------------------------------------------
-static bool musicEnabled = false;
-static Music currentMusic; // Current music loaded
- // NOTE: Only one music file playing at a time
+static MixChannel_t* mixChannelsActive_g[MAX_MIX_CHANNELS]; // What mix channels are currently active
+static bool musicEnabled_g = false;
+static Music currentMusic[MAX_MUSIC_STREAMS]; // Current music loaded, up to two can play at the same time
//----------------------------------------------------------------------------------
// Module specific Functions Declaration
//----------------------------------------------------------------------------------
-static Wave LoadWAV(const char *fileName); // Load WAV file
-static Wave LoadOGG(char *fileName); // Load OGG file
-static void UnloadWave(Wave wave); // Unload wave data
+static Wave LoadWAV(const char *fileName); // Load WAV file
+static Wave LoadOGG(char *fileName); // Load OGG file
+static void UnloadWave(Wave wave); // Unload wave data
-static bool BufferMusicStream(ALuint buffer); // Fill music buffers with data
-static void EmptyMusicStream(void); // Empty music buffers
+static bool BufferMusicStream(int index, int numBuffers); // Fill music buffers with data
+static void EmptyMusicStream(int index); // Empty music buffers
+
+
+static MixChannel_t* InitMixChannel(unsigned short sampleRate, unsigned char mixChannel, unsigned char channels, bool floatingPoint); // For streaming into mix channels.
+static void CloseMixChannel(MixChannel_t* mixc); // Frees mix channel
+static int BufferMixChannel(MixChannel_t* mixc, void *data, int numberElements); // Pushes more audio data into mixc mix channel, if NULL is passed it pauses
+static int FillAlBufferWithSilence(MixChannel_t *mixc, ALuint buffer); // Fill buffer with zeros, returns number processed
+static void ResampleShortToFloat(short *shorts, float *floats, unsigned short len); // Pass two arrays of the same legnth in
+static void ResampleByteToFloat(char *chars, float *floats, unsigned short len); // Pass two arrays of same length in
+static int IsMusicStreamReadyForBuffering(int index); // Checks if music buffer is ready to be refilled
#if defined(AUDIO_STANDALONE)
const char *GetExtension(const char *fileName); // Get the extension for a filename
@@ -115,7 +143,7 @@ void TraceLog(int msgType, const char *text, ...); // Outputs a trace log messa
// Module Functions Definition - Audio Device initialization and Closing
//----------------------------------------------------------------------------------
-// Initialize audio device and context
+// Initialize audio device and mixc
void InitAudioDevice(void)
{
// Open and initialize a device with default settings
@@ -131,7 +159,7 @@ void InitAudioDevice(void)
alcCloseDevice(device);
- TraceLog(ERROR, "Could not setup audio context");
+ TraceLog(ERROR, "Could not setup mix channel");
}
TraceLog(INFO, "Audio device and context initialized successfully: %s", alcGetString(device, ALC_DEVICE_SPECIFIER));
@@ -142,15 +170,19 @@ void InitAudioDevice(void)
alListener3f(AL_ORIENTATION, 0, 0, -1);
}
-// Close the audio device for the current context, and destroys the context
+// Close the audio device for all contexts
void CloseAudioDevice(void)
{
- StopMusicStream(); // Stop music streaming and close current stream
+ for(int index=0; index<MAX_MUSIC_STREAMS; index++)
+ {
+ if(currentMusic[index].mixc) StopMusicStream(index); // Stop music streaming and close current stream
+ }
+
ALCdevice *device;
ALCcontext *context = alcGetCurrentContext();
- if (context == NULL) TraceLog(WARNING, "Could not get current audio context for closing");
+ if (context == NULL) TraceLog(WARNING, "Could not get current mix channel for closing");
device = alcGetContextsDevice(context);
@@ -159,6 +191,229 @@ void CloseAudioDevice(void)
alcCloseDevice(device);
}
+// True if call to InitAudioDevice() was successful and CloseAudioDevice() has not been called yet
+bool IsAudioDeviceReady(void)
+{
+ ALCcontext *context = alcGetCurrentContext();
+ if (context == NULL) return false;
+ else{
+ ALCdevice *device = alcGetContextsDevice(context);
+ if (device == NULL) return false;
+ else return true;
+ }
+}
+
+//----------------------------------------------------------------------------------
+// Module Functions Definition - Custom audio output
+//----------------------------------------------------------------------------------
+
+// For streaming into mix channels.
+// The mixChannel is what audio muxing channel you want to operate on, 0-3 are the ones available. Each mix channel can only be used one at a time.
+// exmple usage is InitMixChannel(48000, 0, 2, true); // mixchannel 1, 48khz, stereo, floating point
+static MixChannel_t* InitMixChannel(unsigned short sampleRate, unsigned char mixChannel, unsigned char channels, bool floatingPoint)
+{
+ if(mixChannel >= MAX_MIX_CHANNELS) return NULL;
+ if(!IsAudioDeviceReady()) InitAudioDevice();
+
+ if(!mixChannelsActive_g[mixChannel]){
+ MixChannel_t *mixc = (MixChannel_t*)malloc(sizeof(MixChannel_t));
+ mixc->sampleRate = sampleRate;
+ mixc->channels = channels;
+ mixc->mixChannel = mixChannel;
+ mixc->floatingPoint = floatingPoint;
+ mixChannelsActive_g[mixChannel] = mixc;
+
+ // setup openAL format
+ if(channels == 1)
+ {
+ if(floatingPoint)
+ mixc->alFormat = AL_FORMAT_MONO_FLOAT32;
+ else
+ mixc->alFormat = AL_FORMAT_MONO16;
+ }
+ else if(channels == 2)
+ {
+ if(floatingPoint)
+ mixc->alFormat = AL_FORMAT_STEREO_FLOAT32;
+ else
+ mixc->alFormat = AL_FORMAT_STEREO16;
+ }
+
+ // Create an audio source
+ alGenSources(1, &mixc->alSource);
+ alSourcef(mixc->alSource, AL_PITCH, 1);
+ alSourcef(mixc->alSource, AL_GAIN, 1);
+ alSource3f(mixc->alSource, AL_POSITION, 0, 0, 0);
+ alSource3f(mixc->alSource, AL_VELOCITY, 0, 0, 0);
+
+ // Create Buffer
+ alGenBuffers(MAX_STREAM_BUFFERS, mixc->alBuffer);
+
+ //fill buffers
+ int x;
+ for(x=0;x<MAX_STREAM_BUFFERS;x++)
+ FillAlBufferWithSilence(mixc, mixc->alBuffer[x]);
+
+ alSourceQueueBuffers(mixc->alSource, MAX_STREAM_BUFFERS, mixc->alBuffer);
+ mixc->playing = true;
+ alSourcePlay(mixc->alSource);
+
+ return mixc;
+ }
+ return NULL;
+}
+
+// Frees buffer in mix channel
+static void CloseMixChannel(MixChannel_t* mixc)
+{
+ if(mixc){
+ alSourceStop(mixc->alSource);
+ mixc->playing = false;
+
+ //flush out all queued buffers
+ ALuint buffer = 0;
+ int queued = 0;
+ alGetSourcei(mixc->alSource, AL_BUFFERS_QUEUED, &queued);
+ while (queued > 0)
+ {
+ alSourceUnqueueBuffers(mixc->alSource, 1, &buffer);
+ queued--;
+ }
+
+ //delete source and buffers
+ alDeleteSources(1, &mixc->alSource);
+ alDeleteBuffers(MAX_STREAM_BUFFERS, mixc->alBuffer);
+ mixChannelsActive_g[mixc->mixChannel] = NULL;
+ free(mixc);
+ mixc = NULL;
+ }
+}
+
+// Pushes more audio data into mixc mix channel, only one buffer per call
+// Call "BufferMixChannel(mixc, NULL, 0)" if you want to pause the audio.
+// @Returns number of samples that where processed.
+static int BufferMixChannel(MixChannel_t* mixc, void *data, int numberElements)
+{
+ if(!mixc || mixChannelsActive_g[mixc->mixChannel] != mixc) return 0; // when there is two channels there must be an even number of samples
+
+ if (!data || !numberElements)
+ { // pauses audio until data is given
+ if(mixc->playing){
+ alSourcePause(mixc->alSource);
+ mixc->playing = false;
+ }
+ return 0;
+ }
+ else if(!mixc->playing)
+ { // restart audio otherwise
+ alSourcePlay(mixc->alSource);
+ mixc->playing = true;
+ }
+
+
+ ALuint buffer = 0;
+
+ alSourceUnqueueBuffers(mixc->alSource, 1, &buffer);
+ if(!buffer) return 0;
+ if(mixc->floatingPoint) // process float buffers
+ {
+ float *ptr = (float*)data;
+ alBufferData(buffer, mixc->alFormat, ptr, numberElements*sizeof(float), mixc->sampleRate);
+ }
+ else // process short buffers
+ {
+ short *ptr = (short*)data;
+ alBufferData(buffer, mixc->alFormat, ptr, numberElements*sizeof(short), mixc->sampleRate);
+ }
+ alSourceQueueBuffers(mixc->alSource, 1, &buffer);
+
+ return numberElements;
+}
+
+// fill buffer with zeros, returns number processed
+static int FillAlBufferWithSilence(MixChannel_t *mixc, ALuint buffer)
+{
+ if(mixc->floatingPoint){
+ float pcm[MUSIC_BUFFER_SIZE_FLOAT] = {0.f};
+ alBufferData(buffer, mixc->alFormat, pcm, MUSIC_BUFFER_SIZE_FLOAT*sizeof(float), mixc->sampleRate);
+ return MUSIC_BUFFER_SIZE_FLOAT;
+ }
+ else
+ {
+ short pcm[MUSIC_BUFFER_SIZE_SHORT] = {0};
+ alBufferData(buffer, mixc->alFormat, pcm, MUSIC_BUFFER_SIZE_SHORT*sizeof(short), mixc->sampleRate);
+ return MUSIC_BUFFER_SIZE_SHORT;
+ }
+}
+
+// example usage:
+// short sh[3] = {1,2,3};float fl[3];
+// ResampleShortToFloat(sh,fl,3);
+static void ResampleShortToFloat(short *shorts, float *floats, unsigned short len)
+{
+ int x;
+ for(x=0;x<len;x++)
+ {
+ if(shorts[x] < 0)
+ floats[x] = (float)shorts[x] / 32766.f;
+ else
+ floats[x] = (float)shorts[x] / 32767.f;
+ }
+}
+
+// example usage:
+// char ch[3] = {1,2,3};float fl[3];
+// ResampleByteToFloat(ch,fl,3);
+static void ResampleByteToFloat(char *chars, float *floats, unsigned short len)
+{
+ int x;
+ for(x=0;x<len;x++)
+ {
+ if(chars[x] < 0)
+ floats[x] = (float)chars[x] / 127.f;
+ else
+ floats[x] = (float)chars[x] / 128.f;
+ }
+}
+
+// used to output raw audio streams, returns negative numbers on error
+// if floating point is false the data size is 16bit short, otherwise it is float 32bit
+RawAudioContext InitRawAudioContext(int sampleRate, int channels, bool floatingPoint)
+{
+ int mixIndex;
+ for(mixIndex = 0; mixIndex < MAX_MIX_CHANNELS; mixIndex++) // find empty mix channel slot
+ {
+ if(mixChannelsActive_g[mixIndex] == NULL) break;
+ else if(mixIndex = MAX_MIX_CHANNELS - 1) return -1; // error
+ }
+
+ if(InitMixChannel(sampleRate, mixIndex, channels, floatingPoint))
+ return mixIndex;
+ else
+ return -2; // error
+}
+
+void CloseRawAudioContext(RawAudioContext ctx)
+{
+ if(mixChannelsActive_g[ctx])
+ CloseMixChannel(mixChannelsActive_g[ctx]);
+}
+
+int BufferRawAudioContext(RawAudioContext ctx, void *data, int numberElements)
+{
+ int numBuffered = 0;
+ if(ctx >= 0)
+ {
+ MixChannel_t* mixc = mixChannelsActive_g[ctx];
+ numBuffered = BufferMixChannel(mixc, data, numberElements);
+ }
+ return numBuffered;
+}
+
+
+
+
+
//----------------------------------------------------------------------------------
// Module Functions Definition - Sounds loading and playing (.WAV)
//----------------------------------------------------------------------------------
@@ -479,7 +734,7 @@ void StopSound(Sound sound)
}
// Check if a sound is playing
-bool SoundIsPlaying(Sound sound)
+bool IsSoundPlaying(Sound sound)
{
bool playing = false;
ALint state;
@@ -507,145 +762,217 @@ void SetSoundPitch(Sound sound, float pitch)
//----------------------------------------------------------------------------------
// Start music playing (open stream)
-void PlayMusicStream(char *fileName)
+// returns 0 on success
+int PlayMusicStream(int musicIndex, char *fileName)
{
+ int mixIndex;
+
+ if(currentMusic[musicIndex].stream || currentMusic[musicIndex].chipctx) return 1; // error
+
+ for(mixIndex = 0; mixIndex < MAX_MIX_CHANNELS; mixIndex++) // find empty mix channel slot
+ {
+ if(mixChannelsActive_g[mixIndex] == NULL) break;
+ else if(mixIndex = MAX_MIX_CHANNELS - 1) return 2; // error
+ }
+
if (strcmp(GetExtension(fileName),"ogg") == 0)
{
- // Stop current music, clean buffers, unload current stream
- StopMusicStream();
-
// Open audio stream
- currentMusic.stream = stb_vorbis_open_filename(fileName, NULL, NULL);
+ currentMusic[musicIndex].stream = stb_vorbis_open_filename(fileName, NULL, NULL);
- if (currentMusic.stream == NULL)
+ if (currentMusic[musicIndex].stream == NULL)
{
TraceLog(WARNING, "[%s] OGG audio file could not be opened", fileName);
+ return 3; // error
}
else
{
// Get file info
- stb_vorbis_info info = stb_vorbis_get_info(currentMusic.stream);
-
- currentMusic.channels = info.channels;
- currentMusic.sampleRate = info.sample_rate;
+ stb_vorbis_info info = stb_vorbis_get_info(currentMusic[musicIndex].stream);
TraceLog(INFO, "[%s] Ogg sample rate: %i", fileName, info.sample_rate);
TraceLog(INFO, "[%s] Ogg channels: %i", fileName, info.channels);
TraceLog(DEBUG, "[%s] Temp memory required: %i", fileName, info.temp_memory_required);
- if (info.channels == 2) currentMusic.format = AL_FORMAT_STEREO16;
- else currentMusic.format = AL_FORMAT_MONO16;
-
- currentMusic.loop = true; // We loop by default
- musicEnabled = true;
+ currentMusic[musicIndex].loop = true; // We loop by default
+ musicEnabled_g = true;
+
- // Create an audio source
- alGenSources(1, &currentMusic.source); // Generate pointer to audio source
-
- alSourcef(currentMusic.source, AL_PITCH, 1);
- alSourcef(currentMusic.source, AL_GAIN, 1);
- alSource3f(currentMusic.source, AL_POSITION, 0, 0, 0);
- alSource3f(currentMusic.source, AL_VELOCITY, 0, 0, 0);
- //alSourcei(currentMusic.source, AL_LOOPING, AL_TRUE); // ERROR: Buffers do not queue!
-
- // Generate two OpenAL buffers
- alGenBuffers(2, currentMusic.buffers);
-
- // Fill buffers with music...
- BufferMusicStream(currentMusic.buffers[0]);
- BufferMusicStream(currentMusic.buffers[1]);
-
- // Queue buffers and start playing
- alSourceQueueBuffers(currentMusic.source, 2, currentMusic.buffers);
- alSourcePlay(currentMusic.source);
-
- // NOTE: Regularly, we must check if a buffer has been processed and refill it: UpdateMusicStream()
-
- currentMusic.totalSamplesLeft = stb_vorbis_stream_length_in_samples(currentMusic.stream) * currentMusic.channels;
+ currentMusic[musicIndex].totalSamplesLeft = stb_vorbis_stream_length_in_samples(currentMusic[musicIndex].stream) * info.channels;
+ currentMusic[musicIndex].totalLengthSeconds = stb_vorbis_stream_length_in_seconds(currentMusic[musicIndex].stream);
+
+ if (info.channels == 2){
+ currentMusic[musicIndex].mixc = InitMixChannel(info.sample_rate, mixIndex, 2, false);
+ currentMusic[musicIndex].mixc->playing = true;
+ }
+ else{
+ currentMusic[musicIndex].mixc = InitMixChannel(info.sample_rate, mixIndex, 1, false);
+ currentMusic[musicIndex].mixc->playing = true;
+ }
+ if(!currentMusic[musicIndex].mixc) return 4; // error
}
}
- else TraceLog(WARNING, "[%s] Music extension not recognized, it can't be loaded", fileName);
+ else if (strcmp(GetExtension(fileName),"xm") == 0)
+ {
+ // only stereo is supported for xm
+ if(!jar_xm_create_context_from_file(&currentMusic[musicIndex].chipctx, 48000, fileName))
+ {
+ currentMusic[musicIndex].chipTune = true;
+ currentMusic[musicIndex].loop = true;
+ jar_xm_set_max_loop_count(currentMusic[musicIndex].chipctx, 0); // infinite number of loops
+ currentMusic[musicIndex].totalSamplesLeft = jar_xm_get_remaining_samples(currentMusic[musicIndex].chipctx);
+ currentMusic[musicIndex].totalLengthSeconds = ((float)currentMusic[musicIndex].totalSamplesLeft) / 48000.f;
+ musicEnabled_g = true;
+
+ TraceLog(INFO, "[%s] XM number of samples: %i", fileName, currentMusic[musicIndex].totalSamplesLeft);
+ TraceLog(INFO, "[%s] XM track length: %11.6f sec", fileName, currentMusic[musicIndex].totalLengthSeconds);
+
+ currentMusic[musicIndex].mixc = InitMixChannel(48000, mixIndex, 2, false);
+ if(!currentMusic[musicIndex].mixc) return 5; // error
+ currentMusic[musicIndex].mixc->playing = true;
+ }
+ else
+ {
+ TraceLog(WARNING, "[%s] XM file could not be opened", fileName);
+ return 6; // error
+ }
+ }
+ else
+ {
+ TraceLog(WARNING, "[%s] Music extension not recognized, it can't be loaded", fileName);
+ return 7; // error
+ }
+ return 0; // normal return
}
-// Stop music playing (close stream)
-void StopMusicStream(void)
+// Stop music playing for individual music index of currentMusic array (close stream)
+void StopMusicStream(int index)
{
- if (musicEnabled)
+ if (index < MAX_MUSIC_STREAMS && currentMusic[index].mixc)
{
- alSourceStop(currentMusic.source);
-
- EmptyMusicStream(); // Empty music buffers
-
- alDeleteSources(1, &currentMusic.source);
- alDeleteBuffers(2, currentMusic.buffers);
-
- stb_vorbis_close(currentMusic.stream);
+ CloseMixChannel(currentMusic[index].mixc);
+
+ if (currentMusic[index].chipTune)
+ {
+ jar_xm_free_context(currentMusic[index].chipctx);
+ }
+ else
+ {
+ stb_vorbis_close(currentMusic[index].stream);
+ }
+
+ if(!getMusicStreamCount()) musicEnabled_g = false;
+ if(currentMusic[index].stream || currentMusic[index].chipctx)
+ {
+ currentMusic[index].stream = NULL;
+ currentMusic[index].chipctx = NULL;
+ }
}
+}
- musicEnabled = false;
+//get number of music channels active at this time, this does not mean they are playing
+int getMusicStreamCount(void)
+{
+ int musicCount = 0;
+ for(int musicIndex = 0; musicIndex < MAX_MUSIC_STREAMS; musicIndex++) // find empty music slot
+ if(currentMusic[musicIndex].stream != NULL || currentMusic[musicIndex].chipTune) musicCount++;
+
+ return musicCount;
}
// Pause music playing
-void PauseMusicStream(void)
+void PauseMusicStream(int index)
{
// Pause music stream if music available!
- if (musicEnabled)
+ if (index < MAX_MUSIC_STREAMS && currentMusic[index].mixc && musicEnabled_g)
{
TraceLog(INFO, "Pausing music stream");
- alSourcePause(currentMusic.source);
- musicEnabled = false;
+ alSourcePause(currentMusic[index].mixc->alSource);
+ currentMusic[index].mixc->playing = false;
}
}
// Resume music playing
-void ResumeMusicStream(void)
+void ResumeMusicStream(int index)
{
// Resume music playing... if music available!
ALenum state;
- alGetSourcei(currentMusic.source, AL_SOURCE_STATE, &state);
-
- if (state == AL_PAUSED)
- {
- TraceLog(INFO, "Resuming music stream");
- alSourcePlay(currentMusic.source);
- musicEnabled = true;
+ if(index < MAX_MUSIC_STREAMS && currentMusic[index].mixc){
+ alGetSourcei(currentMusic[index].mixc->alSource, AL_SOURCE_STATE, &state);
+ if (state == AL_PAUSED)
+ {
+ TraceLog(INFO, "Resuming music stream");
+ alSourcePlay(currentMusic[index].mixc->alSource);
+ currentMusic[index].mixc->playing = true;
+ }
}
}
-// Check if music is playing
-bool MusicIsPlaying(void)
+// Check if any music is playing
+bool IsMusicPlaying(int index)
{
bool playing = false;
ALint state;
-
- alGetSourcei(currentMusic.source, AL_SOURCE_STATE, &state);
- if (state == AL_PLAYING) playing = true;
+
+ if(index < MAX_MUSIC_STREAMS && currentMusic[index].mixc){
+ alGetSourcei(currentMusic[index].mixc->alSource, AL_SOURCE_STATE, &state);
+ if (state == AL_PLAYING) playing = true;
+ }
return playing;
}
// Set volume for music
-void SetMusicVolume(float volume)
+void SetMusicVolume(int index, float volume)
+{
+ if(index < MAX_MUSIC_STREAMS && currentMusic[index].mixc){
+ alSourcef(currentMusic[index].mixc->alSource, AL_GAIN, volume);
+ }
+}
+
+void SetMusicPitch(int index, float pitch)
{
- alSourcef(currentMusic.source, AL_GAIN, volume);
+ if(index < MAX_MUSIC_STREAMS && currentMusic[index].mixc){
+ alSourcef(currentMusic[index].mixc->alSource, AL_PITCH, pitch);
+ }
}
// Get current music time length (in seconds)
-float GetMusicTimeLength(void)
+float GetMusicTimeLength(int index)
{
- float totalSeconds = stb_vorbis_stream_length_in_seconds(currentMusic.stream);
+ float totalSeconds;
+ if (currentMusic[index].chipTune)
+ {
+ totalSeconds = currentMusic[index].totalLengthSeconds;
+ }
+ else
+ {
+ totalSeconds = stb_vorbis_stream_length_in_seconds(currentMusic[index].stream);
+ }
return totalSeconds;
}
// Get current music time played (in seconds)
-float GetMusicTimePlayed(void)
+float GetMusicTimePlayed(int index)
{
- int totalSamples = stb_vorbis_stream_length_in_samples(currentMusic.stream) * currentMusic.channels;
-
- int samplesPlayed = totalSamples - currentMusic.totalSamplesLeft;
-
- float secondsPlayed = (float)samplesPlayed / (currentMusic.sampleRate * currentMusic.channels);
+ float secondsPlayed;
+ if(index < MAX_MUSIC_STREAMS && currentMusic[index].mixc)
+ {
+ if (currentMusic[index].chipTune)
+ {
+ uint64_t samples;
+ jar_xm_get_position(currentMusic[index].chipctx, NULL, NULL, NULL, &samples);
+ secondsPlayed = (float)samples / (48000 * currentMusic[index].mixc->channels); // Not sure if this is the correct value
+ }
+ else
+ {
+ int totalSamples = stb_vorbis_stream_length_in_samples(currentMusic[index].stream) * currentMusic[index].mixc->channels;
+ int samplesPlayed = totalSamples - currentMusic[index].totalSamplesLeft;
+ secondsPlayed = (float)samplesPlayed / (currentMusic[index].mixc->sampleRate * currentMusic[index].mixc->channels);
+ }
+ }
+
return secondsPlayed;
}
@@ -655,103 +982,118 @@ float GetMusicTimePlayed(void)
//----------------------------------------------------------------------------------
// Fill music buffers with new data from music stream
-static bool BufferMusicStream(ALuint buffer)
+static bool BufferMusicStream(int index, int numBuffers)
{
- short pcm[MUSIC_BUFFER_SIZE];
-
- int size = 0; // Total size of data steamed (in bytes)
- int streamedBytes = 0; // Bytes of data obtained in one samples get
-
+ short pcm[MUSIC_BUFFER_SIZE_SHORT];
+ float pcmf[MUSIC_BUFFER_SIZE_FLOAT];
+
+ int size = 0; // Total size of data steamed in L+R samples for xm floats, individual L or R for ogg shorts
bool active = true; // We can get more data from stream (not finished)
-
- if (musicEnabled)
+
+ if (currentMusic[index].chipTune) // There is no end of stream for xmfiles, once the end is reached zeros are generated for non looped chiptunes.
{
- while (size < MUSIC_BUFFER_SIZE)
+ if(currentMusic[index].totalSamplesLeft >= MUSIC_BUFFER_SIZE_SHORT)
+ size = MUSIC_BUFFER_SIZE_SHORT / 2;
+ else
+ size = currentMusic[index].totalSamplesLeft / 2;
+
+ for(int x=0; x<numBuffers; x++)
{
- streamedBytes = stb_vorbis_get_samples_short_interleaved(currentMusic.stream, currentMusic.channels, pcm + size, MUSIC_BUFFER_SIZE - size);
-
- if (streamedBytes > 0) size += (streamedBytes*currentMusic.channels);
- else break;
+ jar_xm_generate_samples_16bit(currentMusic[index].chipctx, pcm, size); // reads 2*readlen shorts and moves them to buffer+size memory location
+ BufferMixChannel(currentMusic[index].mixc, pcm, size * 2);
+ currentMusic[index].totalSamplesLeft -= size * 2;
+ if(currentMusic[index].totalSamplesLeft <= 0)
+ {
+ active = false;
+ break;
+ }
}
-
- //TraceLog(DEBUG, "Streaming music data to buffer. Bytes streamed: %i", size);
- }
-
- if (size > 0)
- {
- alBufferData(buffer, currentMusic.format, pcm, size*sizeof(short), currentMusic.sampleRate);
-
- currentMusic.totalSamplesLeft -= size;
}
else
{
- active = false;
- TraceLog(WARNING, "No more data obtained from stream");
+ if(currentMusic[index].totalSamplesLeft >= MUSIC_BUFFER_SIZE_SHORT)
+ size = MUSIC_BUFFER_SIZE_SHORT;
+ else
+ size = currentMusic[index].totalSamplesLeft;
+
+ for(int x=0; x<numBuffers; x++)
+ {
+ int streamedBytes = stb_vorbis_get_samples_short_interleaved(currentMusic[index].stream, currentMusic[index].mixc->channels, pcm, size);
+ BufferMixChannel(currentMusic[index].mixc, pcm, streamedBytes * currentMusic[index].mixc->channels);
+ currentMusic[index].totalSamplesLeft -= streamedBytes * currentMusic[index].mixc->channels;
+ if(currentMusic[index].totalSamplesLeft <= 0)
+ {
+ active = false;
+ break;
+ }
+ }
}
return active;
}
// Empty music buffers
-static void EmptyMusicStream(void)
+static void EmptyMusicStream(int index)
{
ALuint buffer = 0;
int queued = 0;
- alGetSourcei(currentMusic.source, AL_BUFFERS_QUEUED, &queued);
+ alGetSourcei(currentMusic[index].mixc->alSource, AL_BUFFERS_QUEUED, &queued);
while (queued > 0)
{
- alSourceUnqueueBuffers(currentMusic.source, 1, &buffer);
+ alSourceUnqueueBuffers(currentMusic[index].mixc->alSource, 1, &buffer);
queued--;
}
}
-// Update (re-fill) music buffers if data already processed
-void UpdateMusicStream(void)
+//determine if a music stream is ready to be written to
+static int IsMusicStreamReadyForBuffering(int index)
{
- ALuint buffer = 0;
ALint processed = 0;
- bool active = true;
+ alGetSourcei(currentMusic[index].mixc->alSource, AL_BUFFERS_PROCESSED, &processed);
+ return processed;
+}
- if (musicEnabled)
+// Update (re-fill) music buffers if data already processed
+void UpdateMusicStream(int index)
+{
+ ALenum state;
+ bool active = true;
+ int numBuffers = IsMusicStreamReadyForBuffering(index);
+
+ if (currentMusic[index].mixc->playing && index < MAX_MUSIC_STREAMS && musicEnabled_g && currentMusic[index].mixc && numBuffers)
{
- // Get the number of already processed buffers (if any)
- alGetSourcei(currentMusic.source, AL_BUFFERS_PROCESSED, &processed);
-
- while (processed > 0)
+ active = BufferMusicStream(index, numBuffers);
+
+ if (!active && currentMusic[index].loop)
{
- // Recover processed buffer for refill
- alSourceUnqueueBuffers(currentMusic.source, 1, &buffer);
-
- // Refill buffer
- active = BufferMusicStream(buffer);
-
- // If no more data to stream, restart music (if loop)
- if ((!active) && (currentMusic.loop))
+ if (currentMusic[index].chipTune)
{
- stb_vorbis_seek_start(currentMusic.stream);
- currentMusic.totalSamplesLeft = stb_vorbis_stream_length_in_samples(currentMusic.stream)*currentMusic.channels;
-
- active = BufferMusicStream(buffer);
+ currentMusic[index].totalSamplesLeft = currentMusic[index].totalLengthSeconds * 48000;
}
-
- // Add refilled buffer to queue again... don't let the music stop!
- alSourceQueueBuffers(currentMusic.source, 1, &buffer);
-
- if (alGetError() != AL_NO_ERROR) TraceLog(WARNING, "Ogg playing, error buffering data...");
-
- processed--;
+ else
+ {
+ stb_vorbis_seek_start(currentMusic[index].stream);
+ currentMusic[index].totalSamplesLeft = stb_vorbis_stream_length_in_samples(currentMusic[index].stream) * currentMusic[index].mixc->channels;
+ }
+ active = true;
}
+
- ALenum state;
- alGetSourcei(currentMusic.source, AL_SOURCE_STATE, &state);
+ if (alGetError() != AL_NO_ERROR) TraceLog(WARNING, "Error buffering data...");
+
+ alGetSourcei(currentMusic[index].mixc->alSource, AL_SOURCE_STATE, &state);
- if ((state != AL_PLAYING) && active) alSourcePlay(currentMusic.source);
+ if (state != AL_PLAYING && active) alSourcePlay(currentMusic[index].mixc->alSource);
- if (!active) StopMusicStream();
+ if (!active) StopMusicStream(index);
+
}
+ else
+ return;
+
}
// Load WAV file into Wave structure
diff --git a/src/audio.h b/src/audio.h
index ed156532..1140a60a 100644
--- a/src/audio.h
+++ b/src/audio.h
@@ -41,7 +41,9 @@
//----------------------------------------------------------------------------------
#ifndef __cplusplus
// Boolean type
-typedef enum { false, true } bool;
+ #ifndef true
+ typedef enum { false, true } bool;
+ #endif
#endif
// Sound source type
@@ -59,6 +61,8 @@ typedef struct Wave {
short channels;
} Wave;
+typedef int RawAudioContext;
+
#ifdef __cplusplus
extern "C" { // Prevents name mangling of functions
#endif
@@ -73,6 +77,7 @@ extern "C" { // Prevents name mangling of functions
//----------------------------------------------------------------------------------
void InitAudioDevice(void); // Initialize audio device and context
void CloseAudioDevice(void); // Close the audio device and context (and music stream)
+bool IsAudioDeviceReady(void); // True if call to InitAudioDevice() was successful and CloseAudioDevice() has not been called yet
Sound LoadSound(char *fileName); // Load sound to memory
Sound LoadSoundFromWave(Wave wave); // Load sound to memory from wave data
@@ -81,19 +86,28 @@ void UnloadSound(Sound sound); // Unload sound
void PlaySound(Sound sound); // Play a sound
void PauseSound(Sound sound); // Pause a sound
void StopSound(Sound sound); // Stop playing a sound
-bool SoundIsPlaying(Sound sound); // Check if a sound is currently playing
+bool IsSoundPlaying(Sound sound); // Check if a sound is currently playing
void SetSoundVolume(Sound sound, float volume); // Set volume for a sound (1.0 is max level)
void SetSoundPitch(Sound sound, float pitch); // Set pitch for a sound (1.0 is base level)
-void PlayMusicStream(char *fileName); // Start music playing (open stream)
-void UpdateMusicStream(void); // Updates buffers for music streaming
-void StopMusicStream(void); // Stop music playing (close stream)
-void PauseMusicStream(void); // Pause music playing
-void ResumeMusicStream(void); // Resume playing paused music
-bool MusicIsPlaying(void); // Check if music is playing
-void SetMusicVolume(float volume); // Set volume for music (1.0 is max level)
-float GetMusicTimeLength(void); // Get current music time length (in seconds)
-float GetMusicTimePlayed(void); // Get current music time played (in seconds)
+int PlayMusicStream(int musicIndex, char *fileName); // Start music playing (open stream)
+void UpdateMusicStream(int index); // Updates buffers for music streaming
+void StopMusicStream(int index); // Stop music playing (close stream)
+void PauseMusicStream(int index); // Pause music playing
+void ResumeMusicStream(int index); // Resume playing paused music
+bool IsMusicPlaying(int index); // Check if music is playing
+void SetMusicVolume(int index, float volume); // Set volume for music (1.0 is max level)
+float GetMusicTimeLength(int index); // Get music time length (in seconds)
+float GetMusicTimePlayed(int index); // Get current music time played (in seconds)
+int getMusicStreamCount(void);
+void SetMusicPitch(int index, float pitch);
+
+// used to output raw audio streams, returns negative numbers on error
+// if floating point is false the data size is 16bit short, otherwise it is float 32bit
+RawAudioContext InitRawAudioContext(int sampleRate, int channels, bool floatingPoint);
+
+void CloseRawAudioContext(RawAudioContext ctx);
+int BufferRawAudioContext(RawAudioContext ctx, void *data, int numberElements); // returns number of elements buffered
#ifdef __cplusplus
}
diff --git a/src/core.c b/src/core.c
index c05de93b..a94ad48d 100644
--- a/src/core.c
+++ b/src/core.c
@@ -195,26 +195,19 @@ static int renderOffsetY = 0; // Offset Y from render area (must b
static bool fullscreen = false; // Fullscreen mode (useful only for PLATFORM_DESKTOP)
static Matrix downscaleView; // Matrix to downscale view (in case screen size bigger than display size)
-static Vector2 touchPosition[MAX_TOUCH_POINTS]; // Touch position on screen
-
#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_RPI) || defined(PLATFORM_WEB)
static const char *windowTitle; // Window text title...
-
-static bool customCursor = false; // Tracks if custom cursor has been set
static bool cursorOnScreen = false; // Tracks if cursor is inside client area
-static Texture2D cursor; // Cursor texture
-
-static Vector2 mousePosition; // Mouse position on screen
static char previousKeyState[512] = { 0 }; // Required to check if key pressed/released once
static char currentKeyState[512] = { 0 }; // Required to check if key pressed/released once
-static char previousMouseState[3] = { 0 }; // Required to check if mouse btn pressed/released once
-static char currentMouseState[3] = { 0 }; // Required to check if mouse btn pressed/released once
-
static char previousGamepadState[32] = {0}; // Required to check if gamepad btn pressed/released once
static char currentGamepadState[32] = {0}; // Required to check if gamepad btn pressed/released once
+static char previousMouseState[3] = { 0 }; // Required to check if mouse btn pressed/released once
+static char currentMouseState[3] = { 0 }; // Required to check if mouse btn pressed/released once
+
static int previousMouseWheelY = 0; // Required to track mouse wheel variation
static int currentMouseWheelY = 0; // Required to track mouse wheel variation
@@ -224,6 +217,9 @@ static int lastKeyPressed = -1; // Register last key pressed
static bool cursorHidden; // Track if cursor is hidden
#endif
+static Vector2 mousePosition; // Mouse position on screen
+static Vector2 touchPosition[MAX_TOUCH_POINTS]; // Touch position on screen
+
#if defined(PLATFORM_DESKTOP)
static char **dropFilesPath; // Store dropped files paths as strings
static int dropFilesCount = 0; // Count stored strings
@@ -248,23 +244,16 @@ extern void UnloadDefaultFont(void); // [Module: text] Unloads defaul
//----------------------------------------------------------------------------------
static void InitDisplay(int width, int height); // Initialize display device and framebuffer
static void InitGraphics(void); // Initialize OpenGL graphics
+static void SetupFramebufferSize(int displayWidth, int displayHeight);
static void InitTimer(void); // Initialize timer
static double GetTime(void); // Returns time since InitTimer() was run
static bool GetKeyStatus(int key); // Returns if a key has been pressed
static bool GetMouseButtonStatus(int button); // Returns if a mouse button has been pressed
-static void SwapBuffers(void); // Copy back buffer to front buffers
static void PollInputEvents(void); // Register user events
+static void SwapBuffers(void); // Copy back buffer to front buffers
static void LogoAnimation(void); // Plays raylib logo appearing animation
-static void SetupFramebufferSize(int displayWidth, int displayHeight);
-
-#if defined(PLATFORM_RPI)
-static void InitKeyboard(void); // Init raw keyboard system (standard input reading)
-static void ProcessKeyboard(void); // Process keyboard events
-static void RestoreKeyboard(void); // Restore keyboard system
-static void InitMouse(void); // Mouse initialization (including mouse thread)
-static void *MouseThread(void *arg); // Mouse reading thread
-static void InitGamepad(void); // Init raw gamepad input
-static void *GamepadThread(void *arg); // Mouse reading thread
+#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_RPI)
+static void TakeScreenshot(void); // Takes a screenshot and saves it in the same folder as executable
#endif
#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB)
@@ -283,10 +272,6 @@ static void WindowIconifyCallback(GLFWwindow *window, int iconified);
static void WindowDropCallback(GLFWwindow *window, int count, const char **paths); // GLFW3 Window Drop Callback, runs when drop files into window
#endif
-#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_RPI)
-static void TakeScreenshot(void); // Takes a screenshot and saves it in the same folder as executable
-#endif
-
#if defined(PLATFORM_ANDROID)
static void AndroidCommandCallback(struct android_app *app, int32_t cmd); // Process Android activity lifecycle commands
static int32_t AndroidInputCallback(struct android_app *app, AInputEvent *event); // Process Android inputs
@@ -297,6 +282,16 @@ static EM_BOOL EmscriptenFullscreenChangeCallback(int eventType, const Emscripte
static EM_BOOL EmscriptenInputCallback(int eventType, const EmscriptenTouchEvent *touchEvent, void *userData);
#endif
+#if defined(PLATFORM_RPI)
+static void InitKeyboard(void); // Init raw keyboard system (standard input reading)
+static void ProcessKeyboard(void); // Process keyboard events
+static void RestoreKeyboard(void); // Restore keyboard system
+static void InitMouse(void); // Mouse initialization (including mouse thread)
+static void *MouseThread(void *arg); // Mouse reading thread
+static void InitGamepad(void); // Init raw gamepad input
+static void *GamepadThread(void *arg); // Mouse reading thread
+#endif
+
//----------------------------------------------------------------------------------
// Module Functions Definition - Window and OpenGL Context Functions
//----------------------------------------------------------------------------------
@@ -304,7 +299,7 @@ static EM_BOOL EmscriptenInputCallback(int eventType, const EmscriptenTouchEvent
// Initialize Window and Graphics Context (OpenGL)
void InitWindow(int width, int height, const char *title)
{
- TraceLog(INFO, "Initializing raylib (v1.4.0)");
+ TraceLog(INFO, "Initializing raylib (v1.5.0)");
// Store window title (could be useful...)
windowTitle = title;
@@ -360,7 +355,7 @@ void InitWindow(int width, int height, const char *title)
// Android activity initialization
void InitWindow(int width, int height, struct android_app *state)
{
- TraceLog(INFO, "Initializing raylib (v1.4.0)");
+ TraceLog(INFO, "Initializing raylib (v1.5.0)");
app_dummy();
@@ -494,29 +489,6 @@ void ToggleFullscreen(void)
#endif
}
-#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_RPI) || defined(PLATFORM_WEB)
-// Set a custom cursor icon/image
-void SetCustomCursor(const char *cursorImage)
-{
- if (customCursor) UnloadTexture(cursor);
-
- cursor = LoadTexture(cursorImage);
-
-#if defined(PLATFORM_DESKTOP)
- // NOTE: emscripten not implemented
- glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_HIDDEN);
-#endif
- customCursor = true;
-}
-
-// Set a custom key to exit program
-// NOTE: default exitKey is ESCAPE
-void SetExitKey(int key)
-{
- exitKey = key;
-}
-#endif
-
// Get current screen width
int GetScreenWidth(void)
{
@@ -543,57 +515,24 @@ void BeginDrawing(void)
updateTime = currentTime - previousTime;
previousTime = currentTime;
- if (IsPosproShaderEnabled()) rlEnablePostproFBO();
-
- rlClearScreenBuffers();
-
+ rlClearScreenBuffers(); // Clear current framebuffers
rlLoadIdentity(); // Reset current matrix (MODELVIEW)
-
rlMultMatrixf(MatrixToFloat(downscaleView)); // If downscale required, apply it here
//rlTranslatef(0.375, 0.375, 0); // HACK to have 2D pixel-perfect drawing on OpenGL 1.1
// NOTE: Not required with OpenGL 3.3+
}
-// Setup drawing canvas with 2d camera
-void BeginDrawingEx(Camera2D camera)
-{
- BeginDrawing();
-
- // TODO: Consider origin offset on position, rotation, scaling
-
- Matrix matRotation = MatrixRotate((Vector3){ 0.0f, 0.0f, 1.0f }, camera.rotation*DEG2RAD);
- Matrix matScale = MatrixScale(camera.zoom, camera.zoom, 1.0f);
- Matrix matTranslation = MatrixTranslate(camera.position.x, camera.position.y, 0.0f);
- Matrix matOrigin = MatrixTranslate(-camera.origin.x, -camera.origin.y, 0.0f);
-
- Matrix matTransform = MatrixMultiply(MatrixMultiply(matScale, matRotation), matTranslation);
-
- rlMultMatrixf(MatrixToFloat(matTransform));
-}
-
-// Setup drawing canvas with pro parameters
-void BeginDrawingPro(int blendMode, Shader shader, Matrix transform)
-{
- BeginDrawing();
-
- SetBlendMode(blendMode);
- SetPostproShader(shader);
-
- rlMultMatrixf(MatrixToFloat(transform));
-}
-
// End canvas drawing and Swap Buffers (Double Buffering)
void EndDrawing(void)
{
rlglDraw(); // Draw Buffers (Only OpenGL 3+ and ES2)
- if (IsPosproShaderEnabled()) rlglDrawPostpro(); // Draw postprocessing effect (shader)
-
SwapBuffers(); // Copy back buffer to front buffer
PollInputEvents(); // Poll user events
+ // Frame time control system
currentTime = GetTime();
drawTime = currentTime - previousTime;
previousTime = currentTime;
@@ -612,6 +551,33 @@ void EndDrawing(void)
}
}
+// Initialize 2D mode with custom camera
+void Begin2dMode(Camera2D camera)
+{
+ rlglDraw(); // Draw Buffers (Only OpenGL 3+ and ES2)
+
+ rlLoadIdentity(); // Reset current matrix (MODELVIEW)
+
+ // Camera rotation and scaling is always relative to target
+ Matrix matOrigin = MatrixTranslate(-camera.target.x, -camera.target.y, 0.0f);
+ Matrix matRotation = MatrixRotate((Vector3){ 0.0f, 0.0f, 1.0f }, camera.rotation*DEG2RAD);
+ Matrix matScale = MatrixScale(camera.zoom, camera.zoom, 1.0f);
+
+ Matrix matTranslation = MatrixTranslate(camera.offset.x + camera.target.x, camera.offset.y + camera.target.y, 0.0f);
+
+ Matrix matTransform = MatrixMultiply(MatrixMultiply(matOrigin, MatrixMultiply(matScale, matRotation)), matTranslation);
+
+ rlMultMatrixf(MatrixToFloat(matTransform));
+}
+
+// Ends 2D mode custom camera usage
+void End2dMode(void)
+{
+ rlglDraw(); // Draw Buffers (Only OpenGL 3+ and ES2)
+
+ rlLoadIdentity(); // Reset current matrix (MODELVIEW)
+}
+
// Initializes 3D mode for drawing (Camera setup)
void Begin3dMode(Camera camera)
{
@@ -656,6 +622,26 @@ void End3dMode(void)
rlDisableDepthTest(); // Disable DEPTH_TEST for 2D
}
+// Initializes render texture for drawing
+void BeginTextureMode(RenderTexture2D target)
+{
+ rlglDraw(); // Draw Buffers (Only OpenGL 3+ and ES2)
+
+ rlEnableRenderTexture(target.id);
+
+ rlClearScreenBuffers(); // Clear render texture buffers
+
+ rlLoadIdentity(); // Reset current matrix (MODELVIEW)
+}
+
+// Ends drawing to render texture
+void EndTextureMode(void)
+{
+ rlglDraw(); // Draw Buffers (Only OpenGL 3+ and ES2)
+
+ rlDisableRenderTexture();
+}
+
// Set target FPS for the game
void SetTargetFPS(int fps)
{
@@ -919,7 +905,7 @@ Ray GetMouseRay(Vector2 mousePosition, Camera camera)
Vector3 farPoint = rlglUnproject((Vector3){ deviceCoords.x, deviceCoords.y, 1.0f }, matProj, matView);
#else // OPTION 2: Compute unprojection directly here
-
+
// Calculate unproject matrix (multiply projection matrix and view matrix) and invert it
Matrix matProjView = MatrixMultiply(matProj, matView);
MatrixInvert(&matProjView);
@@ -949,7 +935,7 @@ Ray GetMouseRay(Vector2 mousePosition, Camera camera)
}
// Returns the screen space position from a 3d world space position
-Vector2 WorldToScreen(Vector3 position, Camera camera)
+Vector2 GetWorldToScreen(Vector3 position, Camera camera)
{
// Calculate projection matrix (from perspective instead of frustum
Matrix matProj = MatrixPerspective(camera.fovy, (double)GetScreenWidth()/(double)GetScreenHeight(), 0.01, 1000.0);
@@ -1028,78 +1014,11 @@ int GetKeyPressed(void)
return lastKeyPressed;
}
-// Detect if a mouse button has been pressed once
-bool IsMouseButtonPressed(int button)
-{
- bool pressed = false;
-
- if ((currentMouseState[button] != previousMouseState[button]) && (currentMouseState[button] == 1)) pressed = true;
- else pressed = false;
-
- return pressed;
-}
-
-// Detect if a mouse button is being pressed
-bool IsMouseButtonDown(int button)
-{
- if (GetMouseButtonStatus(button) == 1) return true;
- else return false;
-}
-
-// Detect if a mouse button has been released once
-bool IsMouseButtonReleased(int button)
-{
- bool released = false;
-
- if ((currentMouseState[button] != previousMouseState[button]) && (currentMouseState[button] == 0)) released = true;
- else released = false;
-
- return released;
-}
-
-// Detect if a mouse button is NOT being pressed
-bool IsMouseButtonUp(int button)
-{
- if (GetMouseButtonStatus(button) == 0) return true;
- else return false;
-}
-
-// Returns mouse position X
-int GetMouseX(void)
-{
- return (int)mousePosition.x;
-}
-
-// Returns mouse position Y
-int GetMouseY(void)
-{
- return (int)mousePosition.y;
-}
-
-// Returns mouse position XY
-Vector2 GetMousePosition(void)
-{
- return mousePosition;
-}
-
-// Set mouse position XY
-void SetMousePosition(Vector2 position)
-{
- mousePosition = position;
-#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB)
- // NOTE: emscripten not implemented
- glfwSetCursorPos(window, position.x, position.y);
-#endif
-}
-
-// Returns mouse wheel movement Y
-int GetMouseWheelMove(void)
+// Set a custom key to exit program
+// NOTE: default exitKey is ESCAPE
+void SetExitKey(int key)
{
-#if defined(PLATFORM_WEB)
- return previousMouseWheelY/100;
-#else
- return previousMouseWheelY;
-#endif
+ exitKey = key;
}
// Hide mouse cursor
@@ -1276,6 +1195,111 @@ bool IsGamepadButtonUp(int gamepad, int button)
}
#endif //defined(PLATFORM_DESKTOP) || defined(PLATFORM_RPI) || defined(PLATFORM_WEB)
+
+// Detect if a mouse button has been pressed once
+bool IsMouseButtonPressed(int button)
+{
+ bool pressed = false;
+
+#if defined(PLATFORM_ANDROID)
+ if (IsGestureDetected(GESTURE_TAP)) pressed = true;
+#else
+ if ((currentMouseState[button] != previousMouseState[button]) && (currentMouseState[button] == 1)) pressed = true;
+#endif
+
+ return pressed;
+}
+
+// Detect if a mouse button is being pressed
+bool IsMouseButtonDown(int button)
+{
+ bool down = false;
+
+#if defined(PLATFORM_ANDROID)
+ if (IsGestureDetected(GESTURE_HOLD)) down = true;
+#else
+ if (GetMouseButtonStatus(button) == 1) down = true;
+#endif
+
+ return down;
+}
+
+// Detect if a mouse button has been released once
+bool IsMouseButtonReleased(int button)
+{
+ bool released = false;
+
+#if !defined(PLATFORM_ANDROID)
+ if ((currentMouseState[button] != previousMouseState[button]) && (currentMouseState[button] == 0)) released = true;
+#endif
+
+ return released;
+}
+
+// Detect if a mouse button is NOT being pressed
+bool IsMouseButtonUp(int button)
+{
+ bool up = false;
+
+#if !defined(PLATFORM_ANDROID)
+ if (GetMouseButtonStatus(button) == 0) up = true;
+#endif
+
+ return up;
+}
+
+// Returns mouse position X
+int GetMouseX(void)
+{
+#if defined(PLATFORM_ANDROID)
+ return (int)touchPosition[0].x;
+#else
+ return (int)mousePosition.x;
+#endif
+}
+
+// Returns mouse position Y
+int GetMouseY(void)
+{
+#if defined(PLATFORM_ANDROID)
+ return (int)touchPosition[0].x;
+#else
+ return (int)mousePosition.y;
+#endif
+}
+
+// Returns mouse position XY
+Vector2 GetMousePosition(void)
+{
+#if defined(PLATFORM_ANDROID)
+ return GetTouchPosition(0);
+#else
+ return mousePosition;
+#endif
+}
+
+// Set mouse position XY
+void SetMousePosition(Vector2 position)
+{
+ mousePosition = position;
+#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB)
+ // NOTE: emscripten not implemented
+ glfwSetCursorPos(window, position.x, position.y);
+#endif
+}
+
+// Returns mouse wheel movement Y
+int GetMouseWheelMove(void)
+{
+#if defined(PLATFORM_ANDROID)
+ return 0;
+#elif defined(PLATFORM_WEB)
+ return previousMouseWheelY/100;
+#else
+ return previousMouseWheelY;
+#endif
+}
+
// Returns touch position X
int GetTouchX(void)
{
@@ -1372,7 +1396,7 @@ static void InitDisplay(int width, int height)
// Downscale matrix is required in case desired screen area is bigger than display area
downscaleView = MatrixIdentity();
-
+
#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB)
glfwSetErrorCallback(ErrorCallback);
@@ -1389,10 +1413,12 @@ static void InitDisplay(int width, int height)
// Screen size security check
if (screenWidth <= 0) screenWidth = displayWidth;
if (screenHeight <= 0) screenHeight = displayHeight;
-#elif defined(PLATFORM_WEB)
+#endif // defined(PLATFORM_DESKTOP)
+
+#if defined(PLATFORM_WEB)
displayWidth = screenWidth;
displayHeight = screenHeight;
-#endif
+#endif // defined(PLATFORM_WEB)
glfwDefaultWindowHints(); // Set default windows hints
@@ -1421,7 +1447,12 @@ static void InitDisplay(int width, int height)
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); // Choose OpenGL minor version (just hint)
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // Profiles Hint: Only 3.3 and above!
// Other values: GLFW_OPENGL_ANY_PROFILE, GLFW_OPENGL_COMPAT_PROFILE
+#ifdef __APPLE__
+ glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // OSX Requires
+#else
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_FALSE); // Fordward Compatibility Hint: Only 3.3 and above!
+#endif
+ //glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GL_TRUE);
}
if (fullscreen)
@@ -1443,7 +1474,7 @@ static void InitDisplay(int width, int height)
// TODO: Check modes[i]->width;
// TODO: Check modes[i]->height;
}
-
+
window = glfwCreateWindow(screenWidth, screenHeight, windowTitle, glfwGetPrimaryMonitor(), NULL);
}
else
@@ -1539,8 +1570,9 @@ static void InitDisplay(int width, int height)
}
//glfwGetFramebufferSize(window, &renderWidth, &renderHeight); // Get framebuffer size of current window
+#endif // defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB)
-#elif defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI)
+#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI)
fullscreen = true;
// Screen size security check
@@ -1625,8 +1657,9 @@ static void InitDisplay(int width, int height)
//ANativeWindow_setBuffersGeometry(app->window, 0, 0, displayFormat); // Force use of native display size
surface = eglCreateWindowSurface(display, config, app->window, NULL);
+#endif // defined(PLATFORM_ANDROID)
-#elif defined(PLATFORM_RPI)
+#if defined(PLATFORM_RPI)
graphics_get_display_size(0, &displayWidth, &displayHeight);
// At this point we need to manage render size vs screen size
@@ -1664,7 +1697,7 @@ static void InitDisplay(int width, int height)
surface = eglCreateWindowSurface(display, config, &nativeWindow, NULL);
//---------------------------------------------------------------------------------
-#endif
+#endif // defined(PLATFORM_RPI)
// There must be at least one frame displayed before the buffers are swapped
//eglSwapInterval(display, 1);
@@ -1684,13 +1717,17 @@ static void InitDisplay(int width, int height)
TraceLog(INFO, "Screen size: %i x %i", screenWidth, screenHeight);
TraceLog(INFO, "Viewport offsets: %i, %i", renderOffsetX, renderOffsetY);
}
-#endif
+#endif // defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI)
}
// Initialize OpenGL graphics
static void InitGraphics(void)
{
rlglInit(); // Init rlgl
+
+#if defined(PLATFORM_OCULUS)
+ //rlglInitOculus(); // Init rlgl for Oculus Rift (required textures)
+#endif
rlglInitGraphics(renderOffsetX, renderOffsetY, renderWidth, renderHeight); // Init graphics (OpenGL stuff)
@@ -1701,6 +1738,239 @@ static void InitGraphics(void)
#endif
}
+// Compute framebuffer size relative to screen size and display size
+// NOTE: Global variables renderWidth/renderHeight can be modified
+static void SetupFramebufferSize(int displayWidth, int displayHeight)
+{
+ // TODO: SetupFramebufferSize() does not consider properly display video modes.
+ // It setups a renderWidth/renderHeight with black bars that could not match a valid video mode,
+ // and so, framebuffer is not scaled properly to some monitors.
+
+ // Calculate renderWidth and renderHeight, we have the display size (input params) and the desired screen size (global var)
+ if ((screenWidth > displayWidth) || (screenHeight > displayHeight))
+ {
+ TraceLog(WARNING, "DOWNSCALING: Required screen size (%ix%i) is bigger than display size (%ix%i)", screenWidth, screenHeight, displayWidth, displayHeight);
+
+ // Downscaling to fit display with border-bars
+ float widthRatio = (float)displayWidth/(float)screenWidth;
+ float heightRatio = (float)displayHeight/(float)screenHeight;
+
+ if (widthRatio <= heightRatio)
+ {
+ renderWidth = displayWidth;
+ renderHeight = (int)round((float)screenHeight*widthRatio);
+ renderOffsetX = 0;
+ renderOffsetY = (displayHeight - renderHeight);
+ }
+ else
+ {
+ renderWidth = (int)round((float)screenWidth*heightRatio);
+ renderHeight = displayHeight;
+ renderOffsetX = (displayWidth - renderWidth);
+ renderOffsetY = 0;
+ }
+
+ // NOTE: downscale matrix required!
+ float scaleRatio = (float)renderWidth/(float)screenWidth;
+
+ downscaleView = MatrixScale(scaleRatio, scaleRatio, scaleRatio);
+
+ // NOTE: We render to full display resolution!
+ // We just need to calculate above parameters for downscale matrix and offsets
+ renderWidth = displayWidth;
+ renderHeight = displayHeight;
+
+ TraceLog(WARNING, "Downscale matrix generated, content will be rendered at: %i x %i", renderWidth, renderHeight);
+ }
+ else if ((screenWidth < displayWidth) || (screenHeight < displayHeight))
+ {
+ // Required screen size is smaller than display size
+ TraceLog(INFO, "UPSCALING: Required screen size: %i x %i -> Display size: %i x %i", screenWidth, screenHeight, displayWidth, displayHeight);
+
+ // Upscaling to fit display with border-bars
+ float displayRatio = (float)displayWidth/(float)displayHeight;
+ float screenRatio = (float)screenWidth/(float)screenHeight;
+
+ if (displayRatio <= screenRatio)
+ {
+ renderWidth = screenWidth;
+ renderHeight = (int)round((float)screenWidth/displayRatio);
+ renderOffsetX = 0;
+ renderOffsetY = (renderHeight - screenHeight);
+ }
+ else
+ {
+ renderWidth = (int)round((float)screenHeight*displayRatio);
+ renderHeight = screenHeight;
+ renderOffsetX = (renderWidth - screenWidth);
+ renderOffsetY = 0;
+ }
+ }
+ else // screen == display
+ {
+ renderWidth = screenWidth;
+ renderHeight = screenHeight;
+ renderOffsetX = 0;
+ renderOffsetY = 0;
+ }
+}
+
+// Initialize hi-resolution timer
+static void InitTimer(void)
+{
+ srand(time(NULL)); // Initialize random seed
+
+#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI)
+ struct timespec now;
+
+ if (clock_gettime(CLOCK_MONOTONIC, &now) == 0) // Success
+ {
+ baseTime = (uint64_t)now.tv_sec*1000000000LLU + (uint64_t)now.tv_nsec;
+ }
+ else TraceLog(WARNING, "No hi-resolution timer available");
+#endif
+
+ previousTime = GetTime(); // Get time as double
+}
+
+// Get current time measure (in seconds) since InitTimer()
+static double GetTime(void)
+{
+#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB)
+ return glfwGetTime();
+#elif defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI)
+ struct timespec ts;
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+ uint64_t time = (uint64_t)ts.tv_sec*1000000000LLU + (uint64_t)ts.tv_nsec;
+
+ return (double)(time - baseTime)*1e-9;
+#endif
+}
+
+// Get one key state
+static bool GetKeyStatus(int key)
+{
+#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB)
+ return glfwGetKey(window, key);
+#elif defined(PLATFORM_ANDROID)
+ // TODO: Check for virtual keyboard
+ return false;
+#elif defined(PLATFORM_RPI)
+ // NOTE: Keys states are filled in PollInputEvents()
+ if (key < 0 || key > 511) return false;
+ else return currentKeyState[key];
+#endif
+}
+
+// Get one mouse button state
+static bool GetMouseButtonStatus(int button)
+{
+#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB)
+ return glfwGetMouseButton(window, button);
+#elif defined(PLATFORM_ANDROID)
+ // TODO: Check for virtual mouse
+ return false;
+#elif defined(PLATFORM_RPI)
+ // NOTE: Mouse buttons states are filled in PollInputEvents()
+ return currentMouseState[button];
+#endif
+}
+
+// Poll (store) all input events
+static void PollInputEvents(void)
+{
+ // NOTE: Gestures update must be called every frame to reset gestures correctly
+ // because ProcessGestureEvent() is just called on an event, not every frame
+ UpdateGestures();
+
+#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB)
+ // Mouse input polling
+ double mouseX;
+ double mouseY;
+
+ glfwGetCursorPos(window, &mouseX, &mouseY);
+
+ mousePosition.x = (float)mouseX;
+ mousePosition.y = (float)mouseY;
+
+ // Keyboard input polling (automatically managed by GLFW3 through callback)
+ lastKeyPressed = -1;
+
+ // Register previous keys states
+ for (int i = 0; i < 512; i++) previousKeyState[i] = currentKeyState[i];
+
+ // Register previous mouse states
+ for (int i = 0; i < 3; i++) previousMouseState[i] = currentMouseState[i];
+
+ previousMouseWheelY = currentMouseWheelY;
+ currentMouseWheelY = 0;
+
+ glfwPollEvents(); // Register keyboard/mouse events... and window events!
+#elif defined(PLATFORM_ANDROID)
+
+ // Register previous keys states
+ for (int i = 0; i < 128; i++) previousButtonState[i] = currentButtonState[i];
+
+ // Poll Events (registered events)
+ // NOTE: Activity is paused if not enabled (appEnabled)
+ while ((ident = ALooper_pollAll(appEnabled ? 0 : -1, NULL, &events,(void**)&source)) >= 0)
+ {
+ // Process this event
+ if (source != NULL) source->process(app, source);
+
+ // NOTE: Never close window, native activity is controlled by the system!
+ if (app->destroyRequested != 0)
+ {
+ //TraceLog(INFO, "Closing Window...");
+ //windowShouldClose = true;
+ //ANativeActivity_finish(app->activity);
+ }
+ }
+#elif defined(PLATFORM_RPI)
+
+ // NOTE: Mouse input events polling is done asynchonously in another pthread - MouseThread()
+
+ // NOTE: Keyboard reading could be done using input_event(s) reading or just read from stdin,
+ // we use method 2 (stdin) but maybe in a future we should change to method 1...
+ ProcessKeyboard();
+
+ // NOTE: Gamepad (Joystick) input events polling is done asynchonously in another pthread - GamepadThread()
+
+#endif
+}
+
+// Copy back buffer to front buffers
+static void SwapBuffers(void)
+{
+#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB)
+ glfwSwapBuffers(window);
+#elif defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI)
+ eglSwapBuffers(display, surface);
+#endif
+}
+
+#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_RPI)
+// Takes a screenshot and saves it in the same folder as executable
+static void TakeScreenshot(void)
+{
+ static int shotNum = 0; // Screenshot number, increments every screenshot take during program execution
+ char buffer[20]; // Buffer to store file name
+
+ unsigned char *imgData = rlglReadScreenPixels(renderWidth, renderHeight);
+
+ sprintf(buffer, "screenshot%03i.png", shotNum);
+
+ // Save image as PNG
+ WritePNG(buffer, imgData, renderWidth, renderHeight, 4);
+
+ free(imgData);
+
+ shotNum++;
+
+ TraceLog(INFO, "[%s] Screenshot taken!", buffer);
+}
+#endif
+
#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB)
// GLFW3 Error Callback, runs on GLFW3 error
static void ErrorCallback(int error, const char *description)
@@ -2074,151 +2344,95 @@ static int32_t AndroidInputCallback(struct android_app *app, AInputEvent *event)
}
#endif
-#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_RPI)
-// Takes a screenshot and saves it in the same folder as executable
-static void TakeScreenshot(void)
+#if defined(PLATFORM_WEB)
+static EM_BOOL EmscriptenFullscreenChangeCallback(int eventType, const EmscriptenFullscreenChangeEvent *e, void *userData)
{
- static int shotNum = 0; // Screenshot number, increments every screenshot take during program execution
- char buffer[20]; // Buffer to store file name
-
- unsigned char *imgData = rlglReadScreenPixels(renderWidth, renderHeight);
-
- sprintf(buffer, "screenshot%03i.png", shotNum);
+ //isFullscreen: int e->isFullscreen
+ //fullscreenEnabled: int e->fullscreenEnabled
+ //fs element nodeName: (char *) e->nodeName
+ //fs element id: (char *) e->id
+ //Current element size: (int) e->elementWidth, (int) e->elementHeight
+ //Screen size:(int) e->screenWidth, (int) e->screenHeight
- // Save image as PNG
- WritePNG(buffer, imgData, renderWidth, renderHeight, 4);
-
- free(imgData);
-
- shotNum++;
-
- TraceLog(INFO, "[%s] Screenshot taken!", buffer);
-}
-#endif
-
-// Initialize hi-resolution timer
-static void InitTimer(void)
-{
- srand(time(NULL)); // Initialize random seed
-
-#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI)
- struct timespec now;
-
- if (clock_gettime(CLOCK_MONOTONIC, &now) == 0) // Success
+ if (e->isFullscreen)
{
- baseTime = (uint64_t)now.tv_sec*1000000000LLU + (uint64_t)now.tv_nsec;
+ TraceLog(INFO, "Canvas scaled to fullscreen. ElementSize: (%ix%i), ScreenSize(%ix%i)", e->elementWidth, e->elementHeight, e->screenWidth, e->screenHeight);
}
- else TraceLog(WARNING, "No hi-resolution timer available");
-#endif
-
- previousTime = GetTime(); // Get time as double
-}
-
-// Get current time measure (in seconds) since InitTimer()
-static double GetTime(void)
-{
-#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB)
- return glfwGetTime();
-#elif defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI)
- struct timespec ts;
- clock_gettime(CLOCK_MONOTONIC, &ts);
- uint64_t time = (uint64_t)ts.tv_sec*1000000000LLU + (uint64_t)ts.tv_nsec;
+ else
+ {
+ TraceLog(INFO, "Canvas scaled to windowed. ElementSize: (%ix%i), ScreenSize(%ix%i)", e->elementWidth, e->elementHeight, e->screenWidth, e->screenHeight);
+ }
+
+ // TODO: Depending on scaling factor (screen vs element), calculate factor to scale mouse/touch input
- return (double)(time - baseTime)*1e-9;
-#endif
+ return 0;
}
-// Get one key state
-static bool GetKeyStatus(int key)
+// Web: Get input events
+static EM_BOOL EmscriptenInputCallback(int eventType, const EmscriptenTouchEvent *touchEvent, void *userData)
{
-#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB)
- return glfwGetKey(window, key);
-#elif defined(PLATFORM_ANDROID)
- // TODO: Check for virtual keyboard
- return false;
-#elif defined(PLATFORM_RPI)
- // NOTE: Keys states are filled in PollInputEvents()
- if (key < 0 || key > 511) return false;
- else return currentKeyState[key];
-#endif
-}
+ /*
+ for (int i = 0; i < touchEvent->numTouches; i++)
+ {
+ long x, y, id;
-// Get one mouse button state
-static bool GetMouseButtonStatus(int button)
-{
-#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB)
- return glfwGetMouseButton(window, button);
-#elif defined(PLATFORM_ANDROID)
- // TODO: Check for virtual keyboard
- return false;
-#elif defined(PLATFORM_RPI)
- // NOTE: Mouse buttons states are filled in PollInputEvents()
- return currentMouseState[button];
-#endif
-}
+ if (!touchEvent->touches[i].isChanged) continue;
-// Poll (store) all input events
-static void PollInputEvents(void)
-{
- // NOTE: Gestures update must be called every frame to reset gestures correctly
- // because ProcessGestureEvent() is just called on an event, not every frame
- UpdateGestures();
+ id = touchEvent->touches[i].identifier;
+ x = touchEvent->touches[i].canvasX;
+ y = touchEvent->touches[i].canvasY;
+ }
-#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB)
- // Mouse input polling
- double mouseX;
- double mouseY;
-
- glfwGetCursorPos(window, &mouseX, &mouseY);
-
- mousePosition.x = (float)mouseX;
- mousePosition.y = (float)mouseY;
-
- // Keyboard input polling (automatically managed by GLFW3 through callback)
- lastKeyPressed = -1;
-
- // Register previous keys states
- for (int i = 0; i < 512; i++) previousKeyState[i] = currentKeyState[i];
-
- // Register previous mouse states
- for (int i = 0; i < 3; i++) previousMouseState[i] = currentMouseState[i];
-
- previousMouseWheelY = currentMouseWheelY;
- currentMouseWheelY = 0;
-
- glfwPollEvents(); // Register keyboard/mouse events... and window events!
-#elif defined(PLATFORM_ANDROID)
-
- // Register previous keys states
- for (int i = 0; i < 128; i++) previousButtonState[i] = currentButtonState[i];
+ printf("%s, numTouches: %d %s%s%s%s\n", emscripten_event_type_to_string(eventType), event->numTouches,
+ event->ctrlKey ? " CTRL" : "", event->shiftKey ? " SHIFT" : "", event->altKey ? " ALT" : "", event->metaKey ? " META" : "");
- // Poll Events (registered events)
- // NOTE: Activity is paused if not enabled (appEnabled)
- while ((ident = ALooper_pollAll(appEnabled ? 0 : -1, NULL, &events,(void**)&source)) >= 0)
+ for(int i = 0; i < event->numTouches; ++i)
{
- // Process this event
- if (source != NULL) source->process(app, source);
-
- // NOTE: Never close window, native activity is controlled by the system!
- if (app->destroyRequested != 0)
- {
- //TraceLog(INFO, "Closing Window...");
- //windowShouldClose = true;
- //ANativeActivity_finish(app->activity);
- }
+ const EmscriptenTouchPoint *t = &event->touches[i];
+
+ printf(" %ld: screen: (%ld,%ld), client: (%ld,%ld), page: (%ld,%ld), isChanged: %d, onTarget: %d, canvas: (%ld, %ld)\n",
+ t->identifier, t->screenX, t->screenY, t->clientX, t->clientY, t->pageX, t->pageY, t->isChanged, t->onTarget, t->canvasX, t->canvasY);
}
-#elif defined(PLATFORM_RPI)
+ */
+
+ GestureEvent gestureEvent;
- // NOTE: Mouse input events polling is done asynchonously in another pthread - MouseThread()
+ // Register touch actions
+ if (eventType == EMSCRIPTEN_EVENT_TOUCHSTART) gestureEvent.touchAction = TOUCH_DOWN;
+ else if (eventType == EMSCRIPTEN_EVENT_TOUCHEND) gestureEvent.touchAction = TOUCH_UP;
+ else if (eventType == EMSCRIPTEN_EVENT_TOUCHMOVE) gestureEvent.touchAction = TOUCH_MOVE;
+
+ // Register touch points count
+ gestureEvent.pointCount = touchEvent->numTouches;
+
+ // Register touch points id
+ gestureEvent.pointerId[0] = touchEvent->touches[0].identifier;
+ gestureEvent.pointerId[1] = touchEvent->touches[1].identifier;
+
+ // Register touch points position
+ // NOTE: Only two points registered
+ // TODO: Touch data should be scaled accordingly!
+ //gestureEvent.position[0] = (Vector2){ touchEvent->touches[0].canvasX, touchEvent->touches[0].canvasY };
+ //gestureEvent.position[1] = (Vector2){ touchEvent->touches[1].canvasX, touchEvent->touches[1].canvasY };
+ gestureEvent.position[0] = (Vector2){ touchEvent->touches[0].targetX, touchEvent->touches[0].targetY };
+ gestureEvent.position[1] = (Vector2){ touchEvent->touches[1].targetX, touchEvent->touches[1].targetY };
- // NOTE: Keyboard reading could be done using input_event(s) reading or just read from stdin,
- // we use method 2 (stdin) but maybe in a future we should change to method 1...
- ProcessKeyboard();
+ touchPosition[0] = gestureEvent.position[0];
+ touchPosition[1] = gestureEvent.position[1];
+
+ // Normalize gestureEvent.position[x] for screenWidth and screenHeight
+ gestureEvent.position[0].x /= (float)GetScreenWidth();
+ gestureEvent.position[0].y /= (float)GetScreenHeight();
+
+ gestureEvent.position[1].x /= (float)GetScreenWidth();
+ gestureEvent.position[1].y /= (float)GetScreenHeight();
- // NOTE: Gamepad (Joystick) input events polling is done asynchonously in another pthread - GamepadThread()
+ // Gesture data is sent to gestures system for processing
+ ProcessGestureEvent(gestureEvent); // Process obtained gestures data
-#endif
+ return 1;
}
+#endif
#if defined(PLATFORM_RPI)
// Initialize Keyboard system (using standard input)
@@ -2538,183 +2752,6 @@ static void *GamepadThread(void *arg)
}
#endif
-// Copy back buffer to front buffers
-static void SwapBuffers(void)
-{
-#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB)
- glfwSwapBuffers(window);
-#elif defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI)
- eglSwapBuffers(display, surface);
-#endif
-}
-
-// Compute framebuffer size relative to screen size and display size
-// NOTE: Global variables renderWidth/renderHeight can be modified
-static void SetupFramebufferSize(int displayWidth, int displayHeight)
-{
- // TODO: SetupFramebufferSize() does not consider properly display video modes.
- // It setups a renderWidth/renderHeight with black bars that could not match a valid video mode,
- // and so, framebuffer is not scaled properly to some monitors.
-
- // Calculate renderWidth and renderHeight, we have the display size (input params) and the desired screen size (global var)
- if ((screenWidth > displayWidth) || (screenHeight > displayHeight))
- {
- TraceLog(WARNING, "DOWNSCALING: Required screen size (%ix%i) is bigger than display size (%ix%i)", screenWidth, screenHeight, displayWidth, displayHeight);
-
- // Downscaling to fit display with border-bars
- float widthRatio = (float)displayWidth/(float)screenWidth;
- float heightRatio = (float)displayHeight/(float)screenHeight;
-
- if (widthRatio <= heightRatio)
- {
- renderWidth = displayWidth;
- renderHeight = (int)round((float)screenHeight*widthRatio);
- renderOffsetX = 0;
- renderOffsetY = (displayHeight - renderHeight);
- }
- else
- {
- renderWidth = (int)round((float)screenWidth*heightRatio);
- renderHeight = displayHeight;
- renderOffsetX = (displayWidth - renderWidth);
- renderOffsetY = 0;
- }
-
- // NOTE: downscale matrix required!
- float scaleRatio = (float)renderWidth/(float)screenWidth;
-
- downscaleView = MatrixScale(scaleRatio, scaleRatio, scaleRatio);
-
- // NOTE: We render to full display resolution!
- // We just need to calculate above parameters for downscale matrix and offsets
- renderWidth = displayWidth;
- renderHeight = displayHeight;
-
- TraceLog(WARNING, "Downscale matrix generated, content will be rendered at: %i x %i", renderWidth, renderHeight);
- }
- else if ((screenWidth < displayWidth) || (screenHeight < displayHeight))
- {
- // Required screen size is smaller than display size
- TraceLog(INFO, "UPSCALING: Required screen size: %i x %i -> Display size: %i x %i", screenWidth, screenHeight, displayWidth, displayHeight);
-
- // Upscaling to fit display with border-bars
- float displayRatio = (float)displayWidth/(float)displayHeight;
- float screenRatio = (float)screenWidth/(float)screenHeight;
-
- if (displayRatio <= screenRatio)
- {
- renderWidth = screenWidth;
- renderHeight = (int)round((float)screenWidth/displayRatio);
- renderOffsetX = 0;
- renderOffsetY = (renderHeight - screenHeight);
- }
- else
- {
- renderWidth = (int)round((float)screenHeight*displayRatio);
- renderHeight = screenHeight;
- renderOffsetX = (renderWidth - screenWidth);
- renderOffsetY = 0;
- }
- }
- else // screen == display
- {
- renderWidth = screenWidth;
- renderHeight = screenHeight;
- renderOffsetX = 0;
- renderOffsetY = 0;
- }
-}
-
-#if defined(PLATFORM_WEB)
-static EM_BOOL EmscriptenFullscreenChangeCallback(int eventType, const EmscriptenFullscreenChangeEvent *e, void *userData)
-{
- //isFullscreen: int e->isFullscreen
- //fullscreenEnabled: int e->fullscreenEnabled
- //fs element nodeName: (char *) e->nodeName
- //fs element id: (char *) e->id
- //Current element size: (int) e->elementWidth, (int) e->elementHeight
- //Screen size:(int) e->screenWidth, (int) e->screenHeight
-
- if (e->isFullscreen)
- {
- TraceLog(INFO, "Canvas scaled to fullscreen. ElementSize: (%ix%i), ScreenSize(%ix%i)", e->elementWidth, e->elementHeight, e->screenWidth, e->screenHeight);
- }
- else
- {
- TraceLog(INFO, "Canvas scaled to windowed. ElementSize: (%ix%i), ScreenSize(%ix%i)", e->elementWidth, e->elementHeight, e->screenWidth, e->screenHeight);
- }
-
- // TODO: Depending on scaling factor (screen vs element), calculate factor to scale mouse/touch input
-
- return 0;
-}
-
-// Web: Get input events
-static EM_BOOL EmscriptenInputCallback(int eventType, const EmscriptenTouchEvent *touchEvent, void *userData)
-{
- /*
- for (int i = 0; i < touchEvent->numTouches; i++)
- {
- long x, y, id;
-
- if (!touchEvent->touches[i].isChanged) continue;
-
- id = touchEvent->touches[i].identifier;
- x = touchEvent->touches[i].canvasX;
- y = touchEvent->touches[i].canvasY;
- }
-
- printf("%s, numTouches: %d %s%s%s%s\n", emscripten_event_type_to_string(eventType), event->numTouches,
- event->ctrlKey ? " CTRL" : "", event->shiftKey ? " SHIFT" : "", event->altKey ? " ALT" : "", event->metaKey ? " META" : "");
-
- for(int i = 0; i < event->numTouches; ++i)
- {
- const EmscriptenTouchPoint *t = &event->touches[i];
-
- printf(" %ld: screen: (%ld,%ld), client: (%ld,%ld), page: (%ld,%ld), isChanged: %d, onTarget: %d, canvas: (%ld, %ld)\n",
- t->identifier, t->screenX, t->screenY, t->clientX, t->clientY, t->pageX, t->pageY, t->isChanged, t->onTarget, t->canvasX, t->canvasY);
- }
- */
-
- GestureEvent gestureEvent;
-
- // Register touch actions
- if (eventType == EMSCRIPTEN_EVENT_TOUCHSTART) gestureEvent.touchAction = TOUCH_DOWN;
- else if (eventType == EMSCRIPTEN_EVENT_TOUCHEND) gestureEvent.touchAction = TOUCH_UP;
- else if (eventType == EMSCRIPTEN_EVENT_TOUCHMOVE) gestureEvent.touchAction = TOUCH_MOVE;
-
- // Register touch points count
- gestureEvent.pointCount = touchEvent->numTouches;
-
- // Register touch points id
- gestureEvent.pointerId[0] = touchEvent->touches[0].identifier;
- gestureEvent.pointerId[1] = touchEvent->touches[1].identifier;
-
- // Register touch points position
- // NOTE: Only two points registered
- // TODO: Touch data should be scaled accordingly!
- //gestureEvent.position[0] = (Vector2){ touchEvent->touches[0].canvasX, touchEvent->touches[0].canvasY };
- //gestureEvent.position[1] = (Vector2){ touchEvent->touches[1].canvasX, touchEvent->touches[1].canvasY };
- gestureEvent.position[0] = (Vector2){ touchEvent->touches[0].targetX, touchEvent->touches[0].targetY };
- gestureEvent.position[1] = (Vector2){ touchEvent->touches[1].targetX, touchEvent->touches[1].targetY };
-
- touchPosition[0] = gestureEvent.position[0];
- touchPosition[1] = gestureEvent.position[1];
-
- // Normalize gestureEvent.position[x] for screenWidth and screenHeight
- gestureEvent.position[0].x /= (float)GetScreenWidth();
- gestureEvent.position[0].y /= (float)GetScreenHeight();
-
- gestureEvent.position[1].x /= (float)GetScreenWidth();
- gestureEvent.position[1].y /= (float)GetScreenHeight();
-
- // Gesture data is sent to gestures system for processing
- ProcessGestureEvent(gestureEvent); // Process obtained gestures data
-
- return 1;
-}
-#endif
-
// Plays raylib logo appearing animation
static void LogoAnimation(void)
{
diff --git a/src/easings.h b/src/easings.h
index a198be4d..a8178f4a 100644
--- a/src/easings.h
+++ b/src/easings.h
@@ -7,6 +7,23 @@
* This header uses:
* #define EASINGS_STATIC_INLINE // Inlines all functions code, so it runs faster.
* // This requires lots of memory on system.
+* How to use:
+* The four inputs t,b,c,d are defined as follows:
+* t = current time in milliseconds
+* b = starting position in only one dimension [X || Y || Z] your choice
+* c = the total change in value of b that needs to occur
+* d = total time it should take to complete
+*
+* Example:
+* float speed = 1.f;
+* float currentTime = 0.f;
+* float currentPos[2] = {0,0};
+* float finalPos[2] = {1,1};
+* float startPosition[2] = currentPos;//x,y positions
+* while(currentPos[0] < finalPos[0])
+* currentPos[0] = EaseSineIn(currentTime, startPosition[0], startPosition[0]-finalPos[0], speed);
+* currentPos[1] = EaseSineIn(currentTime, startPosition[1], startPosition[1]-finalPos[0], speed);
+* currentTime += diffTime();
*
* A port of Robert Penner's easing equations to C (http://robertpenner.com/easing/)
*
diff --git a/src/gestures.c b/src/gestures.c
index b0a80084..d72aaf4e 100644
--- a/src/gestures.c
+++ b/src/gestures.c
@@ -292,14 +292,14 @@ void UpdateGestures(void)
}
// Check if a gesture have been detected
-bool IsGestureDetected(void)
+bool IsGestureDetected(int gesture)
{
- if ((enabledGestures & currentGesture) != GESTURE_NONE) return true;
+ if ((enabledGestures & currentGesture) == gesture) return true;
else return false;
}
// Check gesture type
-int GetGestureType(void)
+int GetGestureDetected(void)
{
// Get current gesture only if enabled
return (enabledGestures & currentGesture);
diff --git a/src/gestures.h b/src/gestures.h
index 5468eb54..f2bdaba4 100644
--- a/src/gestures.h
+++ b/src/gestures.h
@@ -92,8 +92,8 @@ extern "C" { // Prevents name mangling of functions
//----------------------------------------------------------------------------------
void ProcessGestureEvent(GestureEvent event); // Process gesture event and translate it into gestures
void UpdateGestures(void); // Update gestures detected (must be called every frame)
-bool IsGestureDetected(void); // Check if a gesture have been detected
-int GetGestureType(void); // Get latest detected gesture
+bool IsGestureDetected(int gesture); // Check if a gesture have been detected
+int GetGestureDetected(void); // Get latest detected gesture
void SetGesturesEnabled(unsigned int gestureFlags); // Enable a set of gestures using flags
int GetTouchPointsCount(void); // Get touch points count
diff --git a/src/jar_xm.h b/src/jar_xm.h
new file mode 100644
index 00000000..f9ddb511
--- /dev/null
+++ b/src/jar_xm.h
@@ -0,0 +1,2666 @@
+// jar_xm.h - v0.01 - public domain - Joshua Reisenauer, MAR 2016
+//
+// HISTORY:
+//
+// v0.01 2016-02-22 Setup
+//
+//
+// USAGE:
+//
+// In ONE source file, put:
+//
+// #define JAR_XM_IMPLEMENTATION
+// #include "jar_xm.h"
+//
+// Other source files should just include jar_xm.h
+//
+// SAMPLE CODE:
+//
+// jar_xm_context_t *musicptr;
+// float musicBuffer[48000 / 60];
+// int intro_load(void)
+// {
+// jar_xm_create_context_from_file(&musicptr, 48000, "Song.XM");
+// return 1;
+// }
+// int intro_unload(void)
+// {
+// jar_xm_free_context(musicptr);
+// return 1;
+// }
+// int intro_tick(long counter)
+// {
+// jar_xm_generate_samples(musicptr, musicBuffer, (48000 / 60) / 2);
+// if(IsKeyDown(KEY_ENTER))
+// return 1;
+// return 0;
+// }
+//
+//
+// LISCENSE - FOR LIBXM:
+//
+// Author: Romain "Artefact2" Dalmaso <artefact2@gmail.com>
+// Contributor: Dan Spencer <dan@atomicpotato.net>
+// Repackaged into jar_xm.h By: Joshua Adam Reisenauer <kd7tck@gmail.com>
+// This program is free software. It comes without any warranty, to the
+// extent permitted by applicable law. You can redistribute it and/or
+// modify it under the terms of the Do What The Fuck You Want To Public
+// License, Version 2, as published by Sam Hocevar. See
+// http://sam.zoy.org/wtfpl/COPYING for more details.
+
+#ifndef INCLUDE_JAR_XM_H
+#define INCLUDE_JAR_XM_H
+
+#define JAR_XM_DEBUG 0
+#define JAR_XM_LINEAR_INTERPOLATION 1 // speed increase with decrease in quality
+#define JAR_XM_DEFENSIVE 1
+#define JAR_XM_RAMPING 1
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <limits.h>
+#include <string.h>
+
+
+
+//-------------------------------------------------------------------------------
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct jar_xm_context_s;
+typedef struct jar_xm_context_s jar_xm_context_t;
+
+/** Create a XM context.
+ *
+ * @param moddata the contents of the module
+ * @param rate play rate in Hz, recommended value of 48000
+ *
+ * @returns 0 on success
+ * @returns 1 if module data is not sane
+ * @returns 2 if memory allocation failed
+ * @returns 3 unable to open input file
+ * @returns 4 fseek() failed
+ * @returns 5 fread() failed
+ * @returns 6 unkown error
+ *
+ * @deprecated This function is unsafe!
+ * @see jar_xm_create_context_safe()
+ */
+int jar_xm_create_context_from_file(jar_xm_context_t** ctx, uint32_t rate, const char* filename);
+
+/** Create a XM context.
+ *
+ * @param moddata the contents of the module
+ * @param rate play rate in Hz, recommended value of 48000
+ *
+ * @returns 0 on success
+ * @returns 1 if module data is not sane
+ * @returns 2 if memory allocation failed
+ *
+ * @deprecated This function is unsafe!
+ * @see jar_xm_create_context_safe()
+ */
+int jar_xm_create_context(jar_xm_context_t**, const char* moddata, uint32_t rate);
+
+/** Create a XM context.
+ *
+ * @param moddata the contents of the module
+ * @param moddata_length the length of the contents of the module, in bytes
+ * @param rate play rate in Hz, recommended value of 48000
+ *
+ * @returns 0 on success
+ * @returns 1 if module data is not sane
+ * @returns 2 if memory allocation failed
+ */
+int jar_xm_create_context_safe(jar_xm_context_t**, const char* moddata, size_t moddata_length, uint32_t rate);
+
+/** Free a XM context created by jar_xm_create_context(). */
+void jar_xm_free_context(jar_xm_context_t*);
+
+/** Play the module and put the sound samples in an output buffer.
+ *
+ * @param output buffer of 2*numsamples elements (A left and right value for each sample)
+ * @param numsamples number of samples to generate
+ */
+void jar_xm_generate_samples(jar_xm_context_t*, float* output, size_t numsamples);
+
+/** Play the module, resample from 32 bit to 16 bit, and put the sound samples in an output buffer.
+ *
+ * @param output buffer of 2*numsamples elements (A left and right value for each sample)
+ * @param numsamples number of samples to generate
+ */
+void jar_xm_generate_samples_16bit(jar_xm_context_t* ctx, short* output, size_t numsamples)
+{
+ float* musicBuffer = malloc((2*numsamples)*sizeof(float));
+ jar_xm_generate_samples(ctx, musicBuffer, numsamples);
+
+ if(output){
+ int x;
+ for(x=0;x<2*numsamples;x++)
+ output[x] = musicBuffer[x] * SHRT_MAX;
+ }
+
+ free(musicBuffer);
+}
+
+/** Play the module, resample from 32 bit to 8 bit, and put the sound samples in an output buffer.
+ *
+ * @param output buffer of 2*numsamples elements (A left and right value for each sample)
+ * @param numsamples number of samples to generate
+ */
+void jar_xm_generate_samples_8bit(jar_xm_context_t* ctx, char* output, size_t numsamples)
+{
+ float* musicBuffer = malloc((2*numsamples)*sizeof(float));
+ jar_xm_generate_samples(ctx, musicBuffer, numsamples);
+
+ if(output){
+ int x;
+ for(x=0;x<2*numsamples;x++)
+ output[x] = musicBuffer[x] * CHAR_MAX;
+ }
+
+ free(musicBuffer);
+}
+
+
+
+/** Set the maximum number of times a module can loop. After the
+ * specified number of loops, calls to jar_xm_generate_samples will only
+ * generate silence. You can control the current number of loops with
+ * jar_xm_get_loop_count().
+ *
+ * @param loopcnt maximum number of loops. Use 0 to loop
+ * indefinitely. */
+void jar_xm_set_max_loop_count(jar_xm_context_t*, uint8_t loopcnt);
+
+/** Get the loop count of the currently playing module. This value is
+ * 0 when the module is still playing, 1 when the module has looped
+ * once, etc. */
+uint8_t jar_xm_get_loop_count(jar_xm_context_t*);
+
+
+
+/** Mute or unmute a channel.
+ *
+ * @note Channel numbers go from 1 to jar_xm_get_number_of_channels(...).
+ *
+ * @return whether the channel was muted.
+ */
+bool jar_xm_mute_channel(jar_xm_context_t*, uint16_t, bool);
+
+/** Mute or unmute an instrument.
+ *
+ * @note Instrument numbers go from 1 to
+ * jar_xm_get_number_of_instruments(...).
+ *
+ * @return whether the instrument was muted.
+ */
+bool jar_xm_mute_instrument(jar_xm_context_t*, uint16_t, bool);
+
+
+
+/** Get the module name as a NUL-terminated string. */
+const char* jar_xm_get_module_name(jar_xm_context_t*);
+
+/** Get the tracker name as a NUL-terminated string. */
+const char* jar_xm_get_tracker_name(jar_xm_context_t*);
+
+
+
+/** Get the number of channels. */
+uint16_t jar_xm_get_number_of_channels(jar_xm_context_t*);
+
+/** Get the module length (in patterns). */
+uint16_t jar_xm_get_module_length(jar_xm_context_t*);
+
+/** Get the number of patterns. */
+uint16_t jar_xm_get_number_of_patterns(jar_xm_context_t*);
+
+/** Get the number of rows of a pattern.
+ *
+ * @note Pattern numbers go from 0 to
+ * jar_xm_get_number_of_patterns(...)-1.
+ */
+uint16_t jar_xm_get_number_of_rows(jar_xm_context_t*, uint16_t);
+
+/** Get the number of instruments. */
+uint16_t jar_xm_get_number_of_instruments(jar_xm_context_t*);
+
+/** Get the number of samples of an instrument.
+ *
+ * @note Instrument numbers go from 1 to
+ * jar_xm_get_number_of_instruments(...).
+ */
+uint16_t jar_xm_get_number_of_samples(jar_xm_context_t*, uint16_t);
+
+
+
+/** Get the current module speed.
+ *
+ * @param bpm will receive the current BPM
+ * @param tempo will receive the current tempo (ticks per line)
+ */
+void jar_xm_get_playing_speed(jar_xm_context_t*, uint16_t* bpm, uint16_t* tempo);
+
+/** Get the current position in the module being played.
+ *
+ * @param pattern_index if not NULL, will receive the current pattern
+ * index in the POT (pattern order table)
+ *
+ * @param pattern if not NULL, will receive the current pattern number
+ *
+ * @param row if not NULL, will receive the current row
+ *
+ * @param samples if not NULL, will receive the total number of
+ * generated samples (divide by sample rate to get seconds of
+ * generated audio)
+ */
+void jar_xm_get_position(jar_xm_context_t*, uint8_t* pattern_index, uint8_t* pattern, uint8_t* row, uint64_t* samples);
+
+/** Get the latest time (in number of generated samples) when a
+ * particular instrument was triggered in any channel.
+ *
+ * @note Instrument numbers go from 1 to
+ * jar_xm_get_number_of_instruments(...).
+ */
+uint64_t jar_xm_get_latest_trigger_of_instrument(jar_xm_context_t*, uint16_t);
+
+/** Get the latest time (in number of generated samples) when a
+ * particular sample was triggered in any channel.
+ *
+ * @note Instrument numbers go from 1 to
+ * jar_xm_get_number_of_instruments(...).
+ *
+ * @note Sample numbers go from 0 to
+ * jar_xm_get_nubmer_of_samples(...,instr)-1.
+ */
+uint64_t jar_xm_get_latest_trigger_of_sample(jar_xm_context_t*, uint16_t instr, uint16_t sample);
+
+/** Get the latest time (in number of generated samples) when any
+ * instrument was triggered in a given channel.
+ *
+ * @note Channel numbers go from 1 to jar_xm_get_number_of_channels(...).
+ */
+uint64_t jar_xm_get_latest_trigger_of_channel(jar_xm_context_t*, uint16_t);
+
+/** Get the number of remaining samples. Divide by 2 to get the number of individual LR data samples.
+ *
+ * @note This is the remaining number of samples before the loop starts module again, or halts if on last pass.
+ * @note This function is very slow and should only be run once, if at all.
+ */
+uint64_t jar_xm_get_remaining_samples(jar_xm_context_t*);
+
+#ifdef __cplusplus
+}
+#endif
+//-------------------------------------------------------------------------------
+
+
+
+
+
+
+//Function Definitions-----------------------------------------------------------
+#ifdef JAR_XM_IMPLEMENTATION
+
+#include <math.h>
+#include <string.h>
+
+#if JAR_XM_DEBUG
+#include <stdio.h>
+#define DEBUG(fmt, ...) do { \
+ fprintf(stderr, "%s(): " fmt "\n", __func__, __VA_ARGS__); \
+ fflush(stderr); \
+ } while(0)
+#else
+#define DEBUG(...)
+#endif
+
+#if jar_xm_BIG_ENDIAN
+#error "Big endian platforms are not yet supported, sorry"
+/* Make sure the compiler stops, even if #error is ignored */
+extern int __fail[-1];
+#endif
+
+/* ----- XM constants ----- */
+
+#define SAMPLE_NAME_LENGTH 22
+#define INSTRUMENT_NAME_LENGTH 22
+#define MODULE_NAME_LENGTH 20
+#define TRACKER_NAME_LENGTH 20
+#define PATTERN_ORDER_TABLE_LENGTH 256
+#define NUM_NOTES 96
+#define NUM_ENVELOPE_POINTS 12
+#define MAX_NUM_ROWS 256
+
+#if JAR_XM_RAMPING
+#define jar_xm_SAMPLE_RAMPING_POINTS 0x20
+#endif
+
+/* ----- Data types ----- */
+
+enum jar_xm_waveform_type_e {
+ jar_xm_SINE_WAVEFORM = 0,
+ jar_xm_RAMP_DOWN_WAVEFORM = 1,
+ jar_xm_SQUARE_WAVEFORM = 2,
+ jar_xm_RANDOM_WAVEFORM = 3,
+ jar_xm_RAMP_UP_WAVEFORM = 4,
+};
+typedef enum jar_xm_waveform_type_e jar_xm_waveform_type_t;
+
+enum jar_xm_loop_type_e {
+ jar_xm_NO_LOOP,
+ jar_xm_FORWARD_LOOP,
+ jar_xm_PING_PONG_LOOP,
+};
+typedef enum jar_xm_loop_type_e jar_xm_loop_type_t;
+
+enum jar_xm_frequency_type_e {
+ jar_xm_LINEAR_FREQUENCIES,
+ jar_xm_AMIGA_FREQUENCIES,
+};
+typedef enum jar_xm_frequency_type_e jar_xm_frequency_type_t;
+
+struct jar_xm_envelope_point_s {
+ uint16_t frame;
+ uint16_t value;
+};
+typedef struct jar_xm_envelope_point_s jar_xm_envelope_point_t;
+
+struct jar_xm_envelope_s {
+ jar_xm_envelope_point_t points[NUM_ENVELOPE_POINTS];
+ uint8_t num_points;
+ uint8_t sustain_point;
+ uint8_t loop_start_point;
+ uint8_t loop_end_point;
+ bool enabled;
+ bool sustain_enabled;
+ bool loop_enabled;
+};
+typedef struct jar_xm_envelope_s jar_xm_envelope_t;
+
+struct jar_xm_sample_s {
+ char name[SAMPLE_NAME_LENGTH + 1];
+ int8_t bits; /* Either 8 or 16 */
+
+ uint32_t length;
+ uint32_t loop_start;
+ uint32_t loop_length;
+ uint32_t loop_end;
+ float volume;
+ int8_t finetune;
+ jar_xm_loop_type_t loop_type;
+ float panning;
+ int8_t relative_note;
+ uint64_t latest_trigger;
+
+ float* data;
+ };
+ typedef struct jar_xm_sample_s jar_xm_sample_t;
+
+ struct jar_xm_instrument_s {
+ char name[INSTRUMENT_NAME_LENGTH + 1];
+ uint16_t num_samples;
+ uint8_t sample_of_notes[NUM_NOTES];
+ jar_xm_envelope_t volume_envelope;
+ jar_xm_envelope_t panning_envelope;
+ jar_xm_waveform_type_t vibrato_type;
+ uint8_t vibrato_sweep;
+ uint8_t vibrato_depth;
+ uint8_t vibrato_rate;
+ uint16_t volume_fadeout;
+ uint64_t latest_trigger;
+ bool muted;
+
+ jar_xm_sample_t* samples;
+ };
+ typedef struct jar_xm_instrument_s jar_xm_instrument_t;
+
+ struct jar_xm_pattern_slot_s {
+ uint8_t note; /* 1-96, 97 = Key Off note */
+ uint8_t instrument; /* 1-128 */
+ uint8_t volume_column;
+ uint8_t effect_type;
+ uint8_t effect_param;
+ };
+ typedef struct jar_xm_pattern_slot_s jar_xm_pattern_slot_t;
+
+ struct jar_xm_pattern_s {
+ uint16_t num_rows;
+ jar_xm_pattern_slot_t* slots; /* Array of size num_rows * num_channels */
+ };
+ typedef struct jar_xm_pattern_s jar_xm_pattern_t;
+
+ struct jar_xm_module_s {
+ char name[MODULE_NAME_LENGTH + 1];
+ char trackername[TRACKER_NAME_LENGTH + 1];
+ uint16_t length;
+ uint16_t restart_position;
+ uint16_t num_channels;
+ uint16_t num_patterns;
+ uint16_t num_instruments;
+ jar_xm_frequency_type_t frequency_type;
+ uint8_t pattern_table[PATTERN_ORDER_TABLE_LENGTH];
+
+ jar_xm_pattern_t* patterns;
+ jar_xm_instrument_t* instruments; /* Instrument 1 has index 0,
+ * instrument 2 has index 1, etc. */
+ };
+ typedef struct jar_xm_module_s jar_xm_module_t;
+
+ struct jar_xm_channel_context_s {
+ float note;
+ float orig_note; /* The original note before effect modifications, as read in the pattern. */
+ jar_xm_instrument_t* instrument; /* Could be NULL */
+ jar_xm_sample_t* sample; /* Could be NULL */
+ jar_xm_pattern_slot_t* current;
+
+ float sample_position;
+ float period;
+ float frequency;
+ float step;
+ bool ping; /* For ping-pong samples: true is -->, false is <-- */
+
+ float volume; /* Ideally between 0 (muted) and 1 (loudest) */
+ float panning; /* Between 0 (left) and 1 (right); 0.5 is centered */
+
+ uint16_t autovibrato_ticks;
+
+ bool sustained;
+ float fadeout_volume;
+ float volume_envelope_volume;
+ float panning_envelope_panning;
+ uint16_t volume_envelope_frame_count;
+ uint16_t panning_envelope_frame_count;
+
+ float autovibrato_note_offset;
+
+ bool arp_in_progress;
+ uint8_t arp_note_offset;
+ uint8_t volume_slide_param;
+ uint8_t fine_volume_slide_param;
+ uint8_t global_volume_slide_param;
+ uint8_t panning_slide_param;
+ uint8_t portamento_up_param;
+ uint8_t portamento_down_param;
+ uint8_t fine_portamento_up_param;
+ uint8_t fine_portamento_down_param;
+ uint8_t extra_fine_portamento_up_param;
+ uint8_t extra_fine_portamento_down_param;
+ uint8_t tone_portamento_param;
+ float tone_portamento_target_period;
+ uint8_t multi_retrig_param;
+ uint8_t note_delay_param;
+ uint8_t pattern_loop_origin; /* Where to restart a E6y loop */
+ uint8_t pattern_loop_count; /* How many loop passes have been done */
+ bool vibrato_in_progress;
+ jar_xm_waveform_type_t vibrato_waveform;
+ bool vibrato_waveform_retrigger; /* True if a new note retriggers the waveform */
+ uint8_t vibrato_param;
+ uint16_t vibrato_ticks; /* Position in the waveform */
+ float vibrato_note_offset;
+ jar_xm_waveform_type_t tremolo_waveform;
+ bool tremolo_waveform_retrigger;
+ uint8_t tremolo_param;
+ uint8_t tremolo_ticks;
+ float tremolo_volume;
+ uint8_t tremor_param;
+ bool tremor_on;
+
+ uint64_t latest_trigger;
+ bool muted;
+
+#if JAR_XM_RAMPING
+ /* These values are updated at the end of each tick, to save
+ * a couple of float operations on every generated sample. */
+ float target_panning;
+ float target_volume;
+
+ unsigned long frame_count;
+ float end_of_previous_sample[jar_xm_SAMPLE_RAMPING_POINTS];
+#endif
+
+ float actual_panning;
+ float actual_volume;
+ };
+ typedef struct jar_xm_channel_context_s jar_xm_channel_context_t;
+
+ struct jar_xm_context_s {
+ void* allocated_memory;
+ jar_xm_module_t module;
+ uint32_t rate;
+
+ uint16_t tempo;
+ uint16_t bpm;
+ float global_volume;
+ float amplification;
+
+#if JAR_XM_RAMPING
+ /* How much is a channel final volume allowed to change per
+ * sample; this is used to avoid abrubt volume changes which
+ * manifest as "clicks" in the generated sound. */
+ float volume_ramp;
+ float panning_ramp; /* Same for panning. */
+#endif
+
+ uint8_t current_table_index;
+ uint8_t current_row;
+ uint16_t current_tick; /* Can go below 255, with high tempo and a pattern delay */
+ float remaining_samples_in_tick;
+ uint64_t generated_samples;
+
+ bool position_jump;
+ bool pattern_break;
+ uint8_t jump_dest;
+ uint8_t jump_row;
+
+ /* Extra ticks to be played before going to the next row -
+ * Used for EEy effect */
+ uint16_t extra_ticks;
+
+ uint8_t* row_loop_count; /* Array of size MAX_NUM_ROWS * module_length */
+ uint8_t loop_count;
+ uint8_t max_loop_count;
+
+ jar_xm_channel_context_t* channels;
+};
+
+/* ----- Internal API ----- */
+
+#if JAR_XM_DEFENSIVE
+
+/** Check the module data for errors/inconsistencies.
+ *
+ * @returns 0 if everything looks OK. Module should be safe to load.
+ */
+int jar_xm_check_sanity_preload(const char*, size_t);
+
+/** Check a loaded module for errors/inconsistencies.
+ *
+ * @returns 0 if everything looks OK.
+ */
+int jar_xm_check_sanity_postload(jar_xm_context_t*);
+
+#endif
+
+/** Get the number of bytes needed to store the module data in a
+ * dynamically allocated blank context.
+ *
+ * Things that are dynamically allocated:
+ * - sample data
+ * - sample structures in instruments
+ * - pattern data
+ * - row loop count arrays
+ * - pattern structures in module
+ * - instrument structures in module
+ * - channel contexts
+ * - context structure itself
+
+ * @returns 0 if everything looks OK.
+ */
+size_t jar_xm_get_memory_needed_for_context(const char*, size_t);
+
+/** Populate the context from module data.
+ *
+ * @returns pointer to the memory pool
+ */
+char* jar_xm_load_module(jar_xm_context_t*, const char*, size_t, char*);
+
+int jar_xm_create_context(jar_xm_context_t** ctxp, const char* moddata, uint32_t rate) {
+ return jar_xm_create_context_safe(ctxp, moddata, SIZE_MAX, rate);
+}
+
+int jar_xm_create_context_safe(jar_xm_context_t** ctxp, const char* moddata, size_t moddata_length, uint32_t rate) {
+#if JAR_XM_DEFENSIVE
+ int ret;
+#endif
+ size_t bytes_needed;
+ char* mempool;
+ jar_xm_context_t* ctx;
+
+#if JAR_XM_DEFENSIVE
+ if((ret = jar_xm_check_sanity_preload(moddata, moddata_length))) {
+ DEBUG("jar_xm_check_sanity_preload() returned %i, module is not safe to load", ret);
+ return 1;
+ }
+#endif
+
+ bytes_needed = jar_xm_get_memory_needed_for_context(moddata, moddata_length);
+ mempool = malloc(bytes_needed);
+ if(mempool == NULL && bytes_needed > 0) {
+ /* malloc() failed, trouble ahead */
+ DEBUG("call to malloc() failed, returned %p", (void*)mempool);
+ return 2;
+ }
+
+ /* Initialize most of the fields to 0, 0.f, NULL or false depending on type */
+ memset(mempool, 0, bytes_needed);
+
+ ctx = (*ctxp = (jar_xm_context_t*)mempool);
+ ctx->allocated_memory = mempool; /* Keep original pointer for free() */
+ mempool += sizeof(jar_xm_context_t);
+
+ ctx->rate = rate;
+ mempool = jar_xm_load_module(ctx, moddata, moddata_length, mempool);
+
+ ctx->channels = (jar_xm_channel_context_t*)mempool;
+ mempool += ctx->module.num_channels * sizeof(jar_xm_channel_context_t);
+
+ ctx->global_volume = 1.f;
+ ctx->amplification = .25f; /* XXX: some bad modules may still clip. Find out something better. */
+
+#if JAR_XM_RAMPING
+ ctx->volume_ramp = (1.f / 128.f);
+ ctx->panning_ramp = (1.f / 128.f);
+#endif
+
+ for(uint8_t i = 0; i < ctx->module.num_channels; ++i) {
+ jar_xm_channel_context_t* ch = ctx->channels + i;
+
+ ch->ping = true;
+ ch->vibrato_waveform = jar_xm_SINE_WAVEFORM;
+ ch->vibrato_waveform_retrigger = true;
+ ch->tremolo_waveform = jar_xm_SINE_WAVEFORM;
+ ch->tremolo_waveform_retrigger = true;
+
+ ch->volume = ch->volume_envelope_volume = ch->fadeout_volume = 1.0f;
+ ch->panning = ch->panning_envelope_panning = .5f;
+ ch->actual_volume = .0f;
+ ch->actual_panning = .5f;
+ }
+
+ ctx->row_loop_count = (uint8_t*)mempool;
+ mempool += MAX_NUM_ROWS * sizeof(uint8_t);
+
+#if JAR_XM_DEFENSIVE
+ if((ret = jar_xm_check_sanity_postload(ctx))) {
+ DEBUG("jar_xm_check_sanity_postload() returned %i, module is not safe to play", ret);
+ jar_xm_free_context(ctx);
+ return 1;
+ }
+#endif
+
+ return 0;
+}
+
+void jar_xm_free_context(jar_xm_context_t* context) {
+ free(context->allocated_memory);
+}
+
+void jar_xm_set_max_loop_count(jar_xm_context_t* context, uint8_t loopcnt) {
+ context->max_loop_count = loopcnt;
+}
+
+uint8_t jar_xm_get_loop_count(jar_xm_context_t* context) {
+ return context->loop_count;
+}
+
+
+
+bool jar_xm_mute_channel(jar_xm_context_t* ctx, uint16_t channel, bool mute) {
+ bool old = ctx->channels[channel - 1].muted;
+ ctx->channels[channel - 1].muted = mute;
+ return old;
+}
+
+bool jar_xm_mute_instrument(jar_xm_context_t* ctx, uint16_t instr, bool mute) {
+ bool old = ctx->module.instruments[instr - 1].muted;
+ ctx->module.instruments[instr - 1].muted = mute;
+ return old;
+}
+
+
+
+const char* jar_xm_get_module_name(jar_xm_context_t* ctx) {
+ return ctx->module.name;
+}
+
+const char* jar_xm_get_tracker_name(jar_xm_context_t* ctx) {
+ return ctx->module.trackername;
+}
+
+
+
+uint16_t jar_xm_get_number_of_channels(jar_xm_context_t* ctx) {
+ return ctx->module.num_channels;
+}
+
+uint16_t jar_xm_get_module_length(jar_xm_context_t* ctx) {
+ return ctx->module.length;
+}
+
+uint16_t jar_xm_get_number_of_patterns(jar_xm_context_t* ctx) {
+ return ctx->module.num_patterns;
+}
+
+uint16_t jar_xm_get_number_of_rows(jar_xm_context_t* ctx, uint16_t pattern) {
+ return ctx->module.patterns[pattern].num_rows;
+}
+
+uint16_t jar_xm_get_number_of_instruments(jar_xm_context_t* ctx) {
+ return ctx->module.num_instruments;
+}
+
+uint16_t jar_xm_get_number_of_samples(jar_xm_context_t* ctx, uint16_t instrument) {
+ return ctx->module.instruments[instrument - 1].num_samples;
+}
+
+
+
+void jar_xm_get_playing_speed(jar_xm_context_t* ctx, uint16_t* bpm, uint16_t* tempo) {
+ if(bpm) *bpm = ctx->bpm;
+ if(tempo) *tempo = ctx->tempo;
+}
+
+void jar_xm_get_position(jar_xm_context_t* ctx, uint8_t* pattern_index, uint8_t* pattern, uint8_t* row, uint64_t* samples) {
+ if(pattern_index) *pattern_index = ctx->current_table_index;
+ if(pattern) *pattern = ctx->module.pattern_table[ctx->current_table_index];
+ if(row) *row = ctx->current_row;
+ if(samples) *samples = ctx->generated_samples;
+}
+
+uint64_t jar_xm_get_latest_trigger_of_instrument(jar_xm_context_t* ctx, uint16_t instr) {
+ return ctx->module.instruments[instr - 1].latest_trigger;
+}
+
+uint64_t jar_xm_get_latest_trigger_of_sample(jar_xm_context_t* ctx, uint16_t instr, uint16_t sample) {
+ return ctx->module.instruments[instr - 1].samples[sample].latest_trigger;
+}
+
+uint64_t jar_xm_get_latest_trigger_of_channel(jar_xm_context_t* ctx, uint16_t chn) {
+ return ctx->channels[chn - 1].latest_trigger;
+}
+
+/* .xm files are little-endian. (XXX: Are they really?) */
+
+/* Bounded reader macros.
+ * If we attempt to read the buffer out-of-bounds, pretend that the buffer is
+ * infinitely padded with zeroes.
+ */
+#define READ_U8(offset) (((offset) < moddata_length) ? (*(uint8_t*)(moddata + (offset))) : 0)
+#define READ_U16(offset) ((uint16_t)READ_U8(offset) | ((uint16_t)READ_U8((offset) + 1) << 8))
+#define READ_U32(offset) ((uint32_t)READ_U16(offset) | ((uint32_t)READ_U16((offset) + 2) << 16))
+#define READ_MEMCPY(ptr, offset, length) memcpy_pad(ptr, length, moddata, moddata_length, offset)
+
+static inline void memcpy_pad(void* dst, size_t dst_len, const void* src, size_t src_len, size_t offset) {
+ uint8_t* dst_c = dst;
+ const uint8_t* src_c = src;
+
+ /* how many bytes can be copied without overrunning `src` */
+ size_t copy_bytes = (src_len >= offset) ? (src_len - offset) : 0;
+ copy_bytes = copy_bytes > dst_len ? dst_len : copy_bytes;
+
+ memcpy(dst_c, src_c + offset, copy_bytes);
+ /* padded bytes */
+ memset(dst_c + copy_bytes, 0, dst_len - copy_bytes);
+}
+
+#if JAR_XM_DEFENSIVE
+
+int jar_xm_check_sanity_preload(const char* module, size_t module_length) {
+ if(module_length < 60) {
+ return 4;
+ }
+
+ if(memcmp("Extended Module: ", module, 17) != 0) {
+ return 1;
+ }
+
+ if(module[37] != 0x1A) {
+ return 2;
+ }
+
+ if(module[59] != 0x01 || module[58] != 0x04) {
+ /* Not XM 1.04 */
+ return 3;
+ }
+
+ return 0;
+}
+
+int jar_xm_check_sanity_postload(jar_xm_context_t* ctx) {
+ /* @todo: plenty of stuff to do here… */
+
+ /* Check the POT */
+ for(uint8_t i = 0; i < ctx->module.length; ++i) {
+ if(ctx->module.pattern_table[i] >= ctx->module.num_patterns) {
+ if(i+1 == ctx->module.length && ctx->module.length > 1) {
+ /* Cheap fix */
+ --ctx->module.length;
+ DEBUG("trimming invalid POT at pos %X", i);
+ } else {
+ DEBUG("module has invalid POT, pos %X references nonexistent pattern %X",
+ i,
+ ctx->module.pattern_table[i]);
+ return 1;
+ }
+ }
+ }
+
+ return 0;
+}
+
+#endif
+
+size_t jar_xm_get_memory_needed_for_context(const char* moddata, size_t moddata_length) {
+ size_t memory_needed = 0;
+ size_t offset = 60; /* Skip the first header */
+ uint16_t num_channels;
+ uint16_t num_patterns;
+ uint16_t num_instruments;
+
+ /* Read the module header */
+
+ num_channels = READ_U16(offset + 8);
+ num_channels = READ_U16(offset + 8);
+
+ num_patterns = READ_U16(offset + 10);
+ memory_needed += num_patterns * sizeof(jar_xm_pattern_t);
+
+ num_instruments = READ_U16(offset + 12);
+ memory_needed += num_instruments * sizeof(jar_xm_instrument_t);
+
+ memory_needed += MAX_NUM_ROWS * READ_U16(offset + 4) * sizeof(uint8_t); /* Module length */
+
+ /* Header size */
+ offset += READ_U32(offset);
+
+ /* Read pattern headers */
+ for(uint16_t i = 0; i < num_patterns; ++i) {
+ uint16_t num_rows;
+
+ num_rows = READ_U16(offset + 5);
+ memory_needed += num_rows * num_channels * sizeof(jar_xm_pattern_slot_t);
+
+ /* Pattern header length + packed pattern data size */
+ offset += READ_U32(offset) + READ_U16(offset + 7);
+ }
+
+ /* Read instrument headers */
+ for(uint16_t i = 0; i < num_instruments; ++i) {
+ uint16_t num_samples;
+ uint32_t sample_header_size = 0;
+ uint32_t sample_size_aggregate = 0;
+
+ num_samples = READ_U16(offset + 27);
+ memory_needed += num_samples * sizeof(jar_xm_sample_t);
+
+ if(num_samples > 0) {
+ sample_header_size = READ_U32(offset + 29);
+ }
+
+ /* Instrument header size */
+ offset += READ_U32(offset);
+
+ for(uint16_t j = 0; j < num_samples; ++j) {
+ uint32_t sample_size;
+ uint8_t flags;
+
+ sample_size = READ_U32(offset);
+ flags = READ_U8(offset + 14);
+ sample_size_aggregate += sample_size;
+
+ if(flags & (1 << 4)) {
+ /* 16 bit sample */
+ memory_needed += sample_size * (sizeof(float) >> 1);
+ } else {
+ /* 8 bit sample */
+ memory_needed += sample_size * sizeof(float);
+ }
+
+ offset += sample_header_size;
+ }
+
+ offset += sample_size_aggregate;
+ }
+
+ memory_needed += num_channels * sizeof(jar_xm_channel_context_t);
+ memory_needed += sizeof(jar_xm_context_t);
+
+ return memory_needed;
+}
+
+char* jar_xm_load_module(jar_xm_context_t* ctx, const char* moddata, size_t moddata_length, char* mempool) {
+ size_t offset = 0;
+ jar_xm_module_t* mod = &(ctx->module);
+
+ /* Read XM header */
+ READ_MEMCPY(mod->name, offset + 17, MODULE_NAME_LENGTH);
+ READ_MEMCPY(mod->trackername, offset + 38, TRACKER_NAME_LENGTH);
+ offset += 60;
+
+ /* Read module header */
+ uint32_t header_size = READ_U32(offset);
+
+ mod->length = READ_U16(offset + 4);
+ mod->restart_position = READ_U16(offset + 6);
+ mod->num_channels = READ_U16(offset + 8);
+ mod->num_patterns = READ_U16(offset + 10);
+ mod->num_instruments = READ_U16(offset + 12);
+
+ mod->patterns = (jar_xm_pattern_t*)mempool;
+ mempool += mod->num_patterns * sizeof(jar_xm_pattern_t);
+
+ mod->instruments = (jar_xm_instrument_t*)mempool;
+ mempool += mod->num_instruments * sizeof(jar_xm_instrument_t);
+
+ uint16_t flags = READ_U32(offset + 14);
+ mod->frequency_type = (flags & (1 << 0)) ? jar_xm_LINEAR_FREQUENCIES : jar_xm_AMIGA_FREQUENCIES;
+
+ ctx->tempo = READ_U16(offset + 16);
+ ctx->bpm = READ_U16(offset + 18);
+
+ READ_MEMCPY(mod->pattern_table, offset + 20, PATTERN_ORDER_TABLE_LENGTH);
+ offset += header_size;
+
+ /* Read patterns */
+ for(uint16_t i = 0; i < mod->num_patterns; ++i) {
+ uint16_t packed_patterndata_size = READ_U16(offset + 7);
+ jar_xm_pattern_t* pat = mod->patterns + i;
+
+ pat->num_rows = READ_U16(offset + 5);
+
+ pat->slots = (jar_xm_pattern_slot_t*)mempool;
+ mempool += mod->num_channels * pat->num_rows * sizeof(jar_xm_pattern_slot_t);
+
+ /* Pattern header length */
+ offset += READ_U32(offset);
+
+ if(packed_patterndata_size == 0) {
+ /* No pattern data is present */
+ memset(pat->slots, 0, sizeof(jar_xm_pattern_slot_t) * pat->num_rows * mod->num_channels);
+ } else {
+ /* This isn't your typical for loop */
+ for(uint16_t j = 0, k = 0; j < packed_patterndata_size; ++k) {
+ uint8_t note = READ_U8(offset + j);
+ jar_xm_pattern_slot_t* slot = pat->slots + k;
+
+ if(note & (1 << 7)) {
+ /* MSB is set, this is a compressed packet */
+ ++j;
+
+ if(note & (1 << 0)) {
+ /* Note follows */
+ slot->note = READ_U8(offset + j);
+ ++j;
+ } else {
+ slot->note = 0;
+ }
+
+ if(note & (1 << 1)) {
+ /* Instrument follows */
+ slot->instrument = READ_U8(offset + j);
+ ++j;
+ } else {
+ slot->instrument = 0;
+ }
+
+ if(note & (1 << 2)) {
+ /* Volume column follows */
+ slot->volume_column = READ_U8(offset + j);
+ ++j;
+ } else {
+ slot->volume_column = 0;
+ }
+
+ if(note & (1 << 3)) {
+ /* Effect follows */
+ slot->effect_type = READ_U8(offset + j);
+ ++j;
+ } else {
+ slot->effect_type = 0;
+ }
+
+ if(note & (1 << 4)) {
+ /* Effect parameter follows */
+ slot->effect_param = READ_U8(offset + j);
+ ++j;
+ } else {
+ slot->effect_param = 0;
+ }
+ } else {
+ /* Uncompressed packet */
+ slot->note = note;
+ slot->instrument = READ_U8(offset + j + 1);
+ slot->volume_column = READ_U8(offset + j + 2);
+ slot->effect_type = READ_U8(offset + j + 3);
+ slot->effect_param = READ_U8(offset + j + 4);
+ j += 5;
+ }
+ }
+ }
+
+ offset += packed_patterndata_size;
+ }
+
+ /* Read instruments */
+ for(uint16_t i = 0; i < ctx->module.num_instruments; ++i) {
+ uint32_t sample_header_size = 0;
+ jar_xm_instrument_t* instr = mod->instruments + i;
+
+ READ_MEMCPY(instr->name, offset + 4, INSTRUMENT_NAME_LENGTH);
+ instr->num_samples = READ_U16(offset + 27);
+
+ if(instr->num_samples > 0) {
+ /* Read extra header properties */
+ sample_header_size = READ_U32(offset + 29);
+ READ_MEMCPY(instr->sample_of_notes, offset + 33, NUM_NOTES);
+
+ instr->volume_envelope.num_points = READ_U8(offset + 225);
+ instr->panning_envelope.num_points = READ_U8(offset + 226);
+
+ for(uint8_t j = 0; j < instr->volume_envelope.num_points; ++j) {
+ instr->volume_envelope.points[j].frame = READ_U16(offset + 129 + 4 * j);
+ instr->volume_envelope.points[j].value = READ_U16(offset + 129 + 4 * j + 2);
+ }
+
+ for(uint8_t j = 0; j < instr->panning_envelope.num_points; ++j) {
+ instr->panning_envelope.points[j].frame = READ_U16(offset + 177 + 4 * j);
+ instr->panning_envelope.points[j].value = READ_U16(offset + 177 + 4 * j + 2);
+ }
+
+ instr->volume_envelope.sustain_point = READ_U8(offset + 227);
+ instr->volume_envelope.loop_start_point = READ_U8(offset + 228);
+ instr->volume_envelope.loop_end_point = READ_U8(offset + 229);
+
+ instr->panning_envelope.sustain_point = READ_U8(offset + 230);
+ instr->panning_envelope.loop_start_point = READ_U8(offset + 231);
+ instr->panning_envelope.loop_end_point = READ_U8(offset + 232);
+
+ uint8_t flags = READ_U8(offset + 233);
+ instr->volume_envelope.enabled = flags & (1 << 0);
+ instr->volume_envelope.sustain_enabled = flags & (1 << 1);
+ instr->volume_envelope.loop_enabled = flags & (1 << 2);
+
+ flags = READ_U8(offset + 234);
+ instr->panning_envelope.enabled = flags & (1 << 0);
+ instr->panning_envelope.sustain_enabled = flags & (1 << 1);
+ instr->panning_envelope.loop_enabled = flags & (1 << 2);
+
+ instr->vibrato_type = READ_U8(offset + 235);
+ if(instr->vibrato_type == 2) {
+ instr->vibrato_type = 1;
+ } else if(instr->vibrato_type == 1) {
+ instr->vibrato_type = 2;
+ }
+ instr->vibrato_sweep = READ_U8(offset + 236);
+ instr->vibrato_depth = READ_U8(offset + 237);
+ instr->vibrato_rate = READ_U8(offset + 238);
+ instr->volume_fadeout = READ_U16(offset + 239);
+
+ instr->samples = (jar_xm_sample_t*)mempool;
+ mempool += instr->num_samples * sizeof(jar_xm_sample_t);
+ } else {
+ instr->samples = NULL;
+ }
+
+ /* Instrument header size */
+ offset += READ_U32(offset);
+
+ for(uint16_t j = 0; j < instr->num_samples; ++j) {
+ /* Read sample header */
+ jar_xm_sample_t* sample = instr->samples + j;
+
+ sample->length = READ_U32(offset);
+ sample->loop_start = READ_U32(offset + 4);
+ sample->loop_length = READ_U32(offset + 8);
+ sample->loop_end = sample->loop_start + sample->loop_length;
+ sample->volume = (float)READ_U8(offset + 12) / (float)0x40;
+ sample->finetune = (int8_t)READ_U8(offset + 13);
+
+ uint8_t flags = READ_U8(offset + 14);
+ if((flags & 3) == 0) {
+ sample->loop_type = jar_xm_NO_LOOP;
+ } else if((flags & 3) == 1) {
+ sample->loop_type = jar_xm_FORWARD_LOOP;
+ } else {
+ sample->loop_type = jar_xm_PING_PONG_LOOP;
+ }
+
+ sample->bits = (flags & (1 << 4)) ? 16 : 8;
+
+ sample->panning = (float)READ_U8(offset + 15) / (float)0xFF;
+ sample->relative_note = (int8_t)READ_U8(offset + 16);
+ READ_MEMCPY(sample->name, 18, SAMPLE_NAME_LENGTH);
+ sample->data = (float*)mempool;
+
+ if(sample->bits == 16) {
+ /* 16 bit sample */
+ mempool += sample->length * (sizeof(float) >> 1);
+ sample->loop_start >>= 1;
+ sample->loop_length >>= 1;
+ sample->loop_end >>= 1;
+ sample->length >>= 1;
+ } else {
+ /* 8 bit sample */
+ mempool += sample->length * sizeof(float);
+ }
+
+ offset += sample_header_size;
+ }
+
+ for(uint16_t j = 0; j < instr->num_samples; ++j) {
+ /* Read sample data */
+ jar_xm_sample_t* sample = instr->samples + j;
+ uint32_t length = sample->length;
+
+ if(sample->bits == 16) {
+ int16_t v = 0;
+ for(uint32_t k = 0; k < length; ++k) {
+ v = v + (int16_t)READ_U16(offset + (k << 1));
+ sample->data[k] = (float)v / (float)(1 << 15);
+ }
+ offset += sample->length << 1;
+ } else {
+ int8_t v = 0;
+ for(uint32_t k = 0; k < length; ++k) {
+ v = v + (int8_t)READ_U8(offset + k);
+ sample->data[k] = (float)v / (float)(1 << 7);
+ }
+ offset += sample->length;
+ }
+ }
+ }
+
+ return mempool;
+}
+
+//-------------------------------------------------------------------------------
+//THE FOLLOWING IS FOR PLAYING
+//-------------------------------------------------------------------------------
+
+/* ----- Static functions ----- */
+
+static float jar_xm_waveform(jar_xm_waveform_type_t, uint8_t);
+static void jar_xm_autovibrato(jar_xm_context_t*, jar_xm_channel_context_t*);
+static void jar_xm_vibrato(jar_xm_context_t*, jar_xm_channel_context_t*, uint8_t, uint16_t);
+static void jar_xm_tremolo(jar_xm_context_t*, jar_xm_channel_context_t*, uint8_t, uint16_t);
+static void jar_xm_arpeggio(jar_xm_context_t*, jar_xm_channel_context_t*, uint8_t, uint16_t);
+static void jar_xm_tone_portamento(jar_xm_context_t*, jar_xm_channel_context_t*);
+static void jar_xm_pitch_slide(jar_xm_context_t*, jar_xm_channel_context_t*, float);
+static void jar_xm_panning_slide(jar_xm_channel_context_t*, uint8_t);
+static void jar_xm_volume_slide(jar_xm_channel_context_t*, uint8_t);
+
+static float jar_xm_envelope_lerp(jar_xm_envelope_point_t*, jar_xm_envelope_point_t*, uint16_t);
+static void jar_xm_envelope_tick(jar_xm_channel_context_t*, jar_xm_envelope_t*, uint16_t*, float*);
+static void jar_xm_envelopes(jar_xm_channel_context_t*);
+
+static float jar_xm_linear_period(float);
+static float jar_xm_linear_frequency(float);
+static float jar_xm_amiga_period(float);
+static float jar_xm_amiga_frequency(float);
+static float jar_xm_period(jar_xm_context_t*, float);
+static float jar_xm_frequency(jar_xm_context_t*, float, float);
+static void jar_xm_update_frequency(jar_xm_context_t*, jar_xm_channel_context_t*);
+
+static void jar_xm_handle_note_and_instrument(jar_xm_context_t*, jar_xm_channel_context_t*, jar_xm_pattern_slot_t*);
+static void jar_xm_trigger_note(jar_xm_context_t*, jar_xm_channel_context_t*, unsigned int flags);
+static void jar_xm_cut_note(jar_xm_channel_context_t*);
+static void jar_xm_key_off(jar_xm_channel_context_t*);
+
+static void jar_xm_post_pattern_change(jar_xm_context_t*);
+static void jar_xm_row(jar_xm_context_t*);
+static void jar_xm_tick(jar_xm_context_t*);
+
+static float jar_xm_next_of_sample(jar_xm_channel_context_t*);
+static void jar_xm_sample(jar_xm_context_t*, float*, float*);
+
+/* ----- Other oddities ----- */
+
+#define jar_xm_TRIGGER_KEEP_VOLUME (1 << 0)
+#define jar_xm_TRIGGER_KEEP_PERIOD (1 << 1)
+#define jar_xm_TRIGGER_KEEP_SAMPLE_POSITION (1 << 2)
+
+static const uint16_t amiga_frequencies[] = {
+ 1712, 1616, 1525, 1440, /* C-2, C#2, D-2, D#2 */
+ 1357, 1281, 1209, 1141, /* E-2, F-2, F#2, G-2 */
+ 1077, 1017, 961, 907, /* G#2, A-2, A#2, B-2 */
+ 856, /* C-3 */
+};
+
+static const float multi_retrig_add[] = {
+ 0.f, -1.f, -2.f, -4.f, /* 0, 1, 2, 3 */
+ -8.f, -16.f, 0.f, 0.f, /* 4, 5, 6, 7 */
+ 0.f, 1.f, 2.f, 4.f, /* 8, 9, A, B */
+ 8.f, 16.f, 0.f, 0.f /* C, D, E, F */
+};
+
+static const float multi_retrig_multiply[] = {
+ 1.f, 1.f, 1.f, 1.f, /* 0, 1, 2, 3 */
+ 1.f, 1.f, .6666667f, .5f, /* 4, 5, 6, 7 */
+ 1.f, 1.f, 1.f, 1.f, /* 8, 9, A, B */
+ 1.f, 1.f, 1.5f, 2.f /* C, D, E, F */
+};
+
+#define jar_xm_CLAMP_UP1F(vol, limit) do { \
+ if((vol) > (limit)) (vol) = (limit); \
+ } while(0)
+#define jar_xm_CLAMP_UP(vol) jar_xm_CLAMP_UP1F((vol), 1.f)
+
+#define jar_xm_CLAMP_DOWN1F(vol, limit) do { \
+ if((vol) < (limit)) (vol) = (limit); \
+ } while(0)
+#define jar_xm_CLAMP_DOWN(vol) jar_xm_CLAMP_DOWN1F((vol), .0f)
+
+#define jar_xm_CLAMP2F(vol, up, down) do { \
+ if((vol) > (up)) (vol) = (up); \
+ else if((vol) < (down)) (vol) = (down); \
+ } while(0)
+#define jar_xm_CLAMP(vol) jar_xm_CLAMP2F((vol), 1.f, .0f)
+
+#define jar_xm_SLIDE_TOWARDS(val, goal, incr) do { \
+ if((val) > (goal)) { \
+ (val) -= (incr); \
+ jar_xm_CLAMP_DOWN1F((val), (goal)); \
+ } else if((val) < (goal)) { \
+ (val) += (incr); \
+ jar_xm_CLAMP_UP1F((val), (goal)); \
+ } \
+ } while(0)
+
+#define jar_xm_LERP(u, v, t) ((u) + (t) * ((v) - (u)))
+#define jar_xm_INVERSE_LERP(u, v, lerp) (((lerp) - (u)) / ((v) - (u)))
+
+#define HAS_TONE_PORTAMENTO(s) ((s)->effect_type == 3 \
+ || (s)->effect_type == 5 \
+ || ((s)->volume_column >> 4) == 0xF)
+#define HAS_ARPEGGIO(s) ((s)->effect_type == 0 \
+ && (s)->effect_param != 0)
+#define HAS_VIBRATO(s) ((s)->effect_type == 4 \
+ || (s)->effect_param == 6 \
+ || ((s)->volume_column >> 4) == 0xB)
+#define NOTE_IS_VALID(n) ((n) > 0 && (n) < 97)
+
+/* ----- Function definitions ----- */
+
+static float jar_xm_waveform(jar_xm_waveform_type_t waveform, uint8_t step) {
+ static unsigned int next_rand = 24492;
+ step %= 0x40;
+
+ switch(waveform) {
+
+ case jar_xm_SINE_WAVEFORM:
+ /* Why not use a table? For saving space, and because there's
+ * very very little actual performance gain. */
+ return -sinf(2.f * 3.141592f * (float)step / (float)0x40);
+
+ case jar_xm_RAMP_DOWN_WAVEFORM:
+ /* Ramp down: 1.0f when step = 0; -1.0f when step = 0x40 */
+ return (float)(0x20 - step) / 0x20;
+
+ case jar_xm_SQUARE_WAVEFORM:
+ /* Square with a 50% duty */
+ return (step >= 0x20) ? 1.f : -1.f;
+
+ case jar_xm_RANDOM_WAVEFORM:
+ /* Use the POSIX.1-2001 example, just to be deterministic
+ * across different machines */
+ next_rand = next_rand * 1103515245 + 12345;
+ return (float)((next_rand >> 16) & 0x7FFF) / (float)0x4000 - 1.f;
+
+ case jar_xm_RAMP_UP_WAVEFORM:
+ /* Ramp up: -1.f when step = 0; 1.f when step = 0x40 */
+ return (float)(step - 0x20) / 0x20;
+
+ default:
+ break;
+
+ }
+
+ return .0f;
+}
+
+static void jar_xm_autovibrato(jar_xm_context_t* ctx, jar_xm_channel_context_t* ch) {
+ if(ch->instrument == NULL || ch->instrument->vibrato_depth == 0) return;
+ jar_xm_instrument_t* instr = ch->instrument;
+ float sweep = 1.f;
+
+ if(ch->autovibrato_ticks < instr->vibrato_sweep) {
+ /* No idea if this is correct, but it sounds close enough… */
+ sweep = jar_xm_LERP(0.f, 1.f, (float)ch->autovibrato_ticks / (float)instr->vibrato_sweep);
+ }
+
+ unsigned int step = ((ch->autovibrato_ticks++) * instr->vibrato_rate) >> 2;
+ ch->autovibrato_note_offset = .25f * jar_xm_waveform(instr->vibrato_type, step)
+ * (float)instr->vibrato_depth / (float)0xF * sweep;
+ jar_xm_update_frequency(ctx, ch);
+}
+
+static void jar_xm_vibrato(jar_xm_context_t* ctx, jar_xm_channel_context_t* ch, uint8_t param, uint16_t pos) {
+ unsigned int step = pos * (param >> 4);
+ ch->vibrato_note_offset =
+ 2.f
+ * jar_xm_waveform(ch->vibrato_waveform, step)
+ * (float)(param & 0x0F) / (float)0xF;
+ jar_xm_update_frequency(ctx, ch);
+}
+
+static void jar_xm_tremolo(jar_xm_context_t* ctx, jar_xm_channel_context_t* ch, uint8_t param, uint16_t pos) {
+ unsigned int step = pos * (param >> 4);
+ /* Not so sure about this, it sounds correct by ear compared with
+ * MilkyTracker, but it could come from other bugs */
+ ch->tremolo_volume = -1.f * jar_xm_waveform(ch->tremolo_waveform, step)
+ * (float)(param & 0x0F) / (float)0xF;
+}
+
+static void jar_xm_arpeggio(jar_xm_context_t* ctx, jar_xm_channel_context_t* ch, uint8_t param, uint16_t tick) {
+ switch(tick % 3) {
+ case 0:
+ ch->arp_in_progress = false;
+ ch->arp_note_offset = 0;
+ break;
+ case 2:
+ ch->arp_in_progress = true;
+ ch->arp_note_offset = param >> 4;
+ break;
+ case 1:
+ ch->arp_in_progress = true;
+ ch->arp_note_offset = param & 0x0F;
+ break;
+ }
+
+ jar_xm_update_frequency(ctx, ch);
+}
+
+static void jar_xm_tone_portamento(jar_xm_context_t* ctx, jar_xm_channel_context_t* ch) {
+ /* 3xx called without a note, wait until we get an actual
+ * target note. */
+ if(ch->tone_portamento_target_period == 0.f) return;
+
+ if(ch->period != ch->tone_portamento_target_period) {
+ jar_xm_SLIDE_TOWARDS(ch->period,
+ ch->tone_portamento_target_period,
+ (ctx->module.frequency_type == jar_xm_LINEAR_FREQUENCIES ?
+ 4.f : 1.f) * ch->tone_portamento_param
+ );
+ jar_xm_update_frequency(ctx, ch);
+ }
+}
+
+static void jar_xm_pitch_slide(jar_xm_context_t* ctx, jar_xm_channel_context_t* ch, float period_offset) {
+ /* Don't ask about the 4.f coefficient. I found mention of it
+ * nowhere. Found by earâ„¢. */
+ if(ctx->module.frequency_type == jar_xm_LINEAR_FREQUENCIES) {
+ period_offset *= 4.f;
+ }
+
+ ch->period += period_offset;
+ jar_xm_CLAMP_DOWN(ch->period);
+ /* XXX: upper bound of period ? */
+
+ jar_xm_update_frequency(ctx, ch);
+}
+
+static void jar_xm_panning_slide(jar_xm_channel_context_t* ch, uint8_t rawval) {
+ float f;
+
+ if((rawval & 0xF0) && (rawval & 0x0F)) {
+ /* Illegal state */
+ return;
+ }
+
+ if(rawval & 0xF0) {
+ /* Slide right */
+ f = (float)(rawval >> 4) / (float)0xFF;
+ ch->panning += f;
+ jar_xm_CLAMP_UP(ch->panning);
+ } else {
+ /* Slide left */
+ f = (float)(rawval & 0x0F) / (float)0xFF;
+ ch->panning -= f;
+ jar_xm_CLAMP_DOWN(ch->panning);
+ }
+}
+
+static void jar_xm_volume_slide(jar_xm_channel_context_t* ch, uint8_t rawval) {
+ float f;
+
+ if((rawval & 0xF0) && (rawval & 0x0F)) {
+ /* Illegal state */
+ return;
+ }
+
+ if(rawval & 0xF0) {
+ /* Slide up */
+ f = (float)(rawval >> 4) / (float)0x40;
+ ch->volume += f;
+ jar_xm_CLAMP_UP(ch->volume);
+ } else {
+ /* Slide down */
+ f = (float)(rawval & 0x0F) / (float)0x40;
+ ch->volume -= f;
+ jar_xm_CLAMP_DOWN(ch->volume);
+ }
+}
+
+static float jar_xm_envelope_lerp(jar_xm_envelope_point_t* restrict a, jar_xm_envelope_point_t* restrict b, uint16_t pos) {
+ /* Linear interpolation between two envelope points */
+ if(pos <= a->frame) return a->value;
+ else if(pos >= b->frame) return b->value;
+ else {
+ float p = (float)(pos - a->frame) / (float)(b->frame - a->frame);
+ return a->value * (1 - p) + b->value * p;
+ }
+}
+
+static void jar_xm_post_pattern_change(jar_xm_context_t* ctx) {
+ /* Loop if necessary */
+ if(ctx->current_table_index >= ctx->module.length) {
+ ctx->current_table_index = ctx->module.restart_position;
+ }
+}
+
+static float jar_xm_linear_period(float note) {
+ return 7680.f - note * 64.f;
+}
+
+static float jar_xm_linear_frequency(float period) {
+ return 8363.f * powf(2.f, (4608.f - period) / 768.f);
+}
+
+static float jar_xm_amiga_period(float note) {
+ unsigned int intnote = note;
+ uint8_t a = intnote % 12;
+ int8_t octave = note / 12.f - 2;
+ uint16_t p1 = amiga_frequencies[a], p2 = amiga_frequencies[a + 1];
+
+ if(octave > 0) {
+ p1 >>= octave;
+ p2 >>= octave;
+ } else if(octave < 0) {
+ p1 <<= (-octave);
+ p2 <<= (-octave);
+ }
+
+ return jar_xm_LERP(p1, p2, note - intnote);
+}
+
+static float jar_xm_amiga_frequency(float period) {
+ if(period == .0f) return .0f;
+
+ /* This is the PAL value. No reason to choose this one over the
+ * NTSC value. */
+ return 7093789.2f / (period * 2.f);
+}
+
+static float jar_xm_period(jar_xm_context_t* ctx, float note) {
+ switch(ctx->module.frequency_type) {
+ case jar_xm_LINEAR_FREQUENCIES:
+ return jar_xm_linear_period(note);
+ case jar_xm_AMIGA_FREQUENCIES:
+ return jar_xm_amiga_period(note);
+ }
+ return .0f;
+}
+
+static float jar_xm_frequency(jar_xm_context_t* ctx, float period, float note_offset) {
+ uint8_t a;
+ int8_t octave;
+ float note;
+ uint16_t p1, p2;
+
+ switch(ctx->module.frequency_type) {
+
+ case jar_xm_LINEAR_FREQUENCIES:
+ return jar_xm_linear_frequency(period - 64.f * note_offset);
+
+ case jar_xm_AMIGA_FREQUENCIES:
+ if(note_offset == 0) {
+ /* A chance to escape from insanity */
+ return jar_xm_amiga_frequency(period);
+ }
+
+ /* FIXME: this is very crappy at best */
+ a = octave = 0;
+
+ /* Find the octave of the current period */
+ if(period > amiga_frequencies[0]) {
+ --octave;
+ while(period > (amiga_frequencies[0] << (-octave))) --octave;
+ } else if(period < amiga_frequencies[12]) {
+ ++octave;
+ while(period < (amiga_frequencies[12] >> octave)) ++octave;
+ }
+
+ /* Find the smallest note closest to the current period */
+ for(uint8_t i = 0; i < 12; ++i) {
+ p1 = amiga_frequencies[i], p2 = amiga_frequencies[i + 1];
+
+ if(octave > 0) {
+ p1 >>= octave;
+ p2 >>= octave;
+ } else if(octave < 0) {
+ p1 <<= (-octave);
+ p2 <<= (-octave);
+ }
+
+ if(p2 <= period && period <= p1) {
+ a = i;
+ break;
+ }
+ }
+
+ if(JAR_XM_DEBUG && (p1 < period || p2 > period)) {
+ DEBUG("%i <= %f <= %i should hold but doesn't, this is a bug", p2, period, p1);
+ }
+
+ note = 12.f * (octave + 2) + a + jar_xm_INVERSE_LERP(p1, p2, period);
+
+ return jar_xm_amiga_frequency(jar_xm_amiga_period(note + note_offset));
+
+ }
+
+ return .0f;
+}
+
+static void jar_xm_update_frequency(jar_xm_context_t* ctx, jar_xm_channel_context_t* ch) {
+ ch->frequency = jar_xm_frequency(
+ ctx, ch->period,
+ (ch->arp_note_offset > 0 ? ch->arp_note_offset : (
+ ch->vibrato_note_offset + ch->autovibrato_note_offset
+ ))
+ );
+ ch->step = ch->frequency / ctx->rate;
+}
+
+static void jar_xm_handle_note_and_instrument(jar_xm_context_t* ctx, jar_xm_channel_context_t* ch,
+ jar_xm_pattern_slot_t* s) {
+ if(s->instrument > 0) {
+ if(HAS_TONE_PORTAMENTO(ch->current) && ch->instrument != NULL && ch->sample != NULL) {
+ /* Tone portamento in effect, unclear stuff happens */
+ jar_xm_trigger_note(ctx, ch, jar_xm_TRIGGER_KEEP_PERIOD | jar_xm_TRIGGER_KEEP_SAMPLE_POSITION);
+ } else if(s->instrument > ctx->module.num_instruments) {
+ /* Invalid instrument, Cut current note */
+ jar_xm_cut_note(ch);
+ ch->instrument = NULL;
+ ch->sample = NULL;
+ } else {
+ ch->instrument = ctx->module.instruments + (s->instrument - 1);
+ if(s->note == 0 && ch->sample != NULL) {
+ /* Ghost instrument, trigger note */
+ /* Sample position is kept, but envelopes are reset */
+ jar_xm_trigger_note(ctx, ch, jar_xm_TRIGGER_KEEP_SAMPLE_POSITION);
+ }
+ }
+ }
+
+ if(NOTE_IS_VALID(s->note)) {
+ /* Yes, the real note number is s->note -1. Try finding
+ * THAT in any of the specs! :-) */
+
+ jar_xm_instrument_t* instr = ch->instrument;
+
+ if(HAS_TONE_PORTAMENTO(ch->current) && instr != NULL && ch->sample != NULL) {
+ /* Tone portamento in effect */
+ ch->note = s->note + ch->sample->relative_note + ch->sample->finetune / 128.f - 1.f;
+ ch->tone_portamento_target_period = jar_xm_period(ctx, ch->note);
+ } else if(instr == NULL || ch->instrument->num_samples == 0) {
+ /* Bad instrument */
+ jar_xm_cut_note(ch);
+ } else {
+ if(instr->sample_of_notes[s->note - 1] < instr->num_samples) {
+#if JAR_XM_RAMPING
+ for(unsigned int z = 0; z < jar_xm_SAMPLE_RAMPING_POINTS; ++z) {
+ ch->end_of_previous_sample[z] = jar_xm_next_of_sample(ch);
+ }
+ ch->frame_count = 0;
+#endif
+ ch->sample = instr->samples + instr->sample_of_notes[s->note - 1];
+ ch->orig_note = ch->note = s->note + ch->sample->relative_note
+ + ch->sample->finetune / 128.f - 1.f;
+ if(s->instrument > 0) {
+ jar_xm_trigger_note(ctx, ch, 0);
+ } else {
+ /* Ghost note: keep old volume */
+ jar_xm_trigger_note(ctx, ch, jar_xm_TRIGGER_KEEP_VOLUME);
+ }
+ } else {
+ /* Bad sample */
+ jar_xm_cut_note(ch);
+ }
+ }
+ } else if(s->note == 97) {
+ /* Key Off */
+ jar_xm_key_off(ch);
+ }
+
+ switch(s->volume_column >> 4) {
+
+ case 0x5:
+ if(s->volume_column > 0x50) break;
+ case 0x1:
+ case 0x2:
+ case 0x3:
+ case 0x4:
+ /* Set volume */
+ ch->volume = (float)(s->volume_column - 0x10) / (float)0x40;
+ break;
+
+ case 0x8: /* Fine volume slide down */
+ jar_xm_volume_slide(ch, s->volume_column & 0x0F);
+ break;
+
+ case 0x9: /* Fine volume slide up */
+ jar_xm_volume_slide(ch, s->volume_column << 4);
+ break;
+
+ case 0xA: /* Set vibrato speed */
+ ch->vibrato_param = (ch->vibrato_param & 0x0F) | ((s->volume_column & 0x0F) << 4);
+ break;
+
+ case 0xC: /* Set panning */
+ ch->panning = (float)(
+ ((s->volume_column & 0x0F) << 4) | (s->volume_column & 0x0F)
+ ) / (float)0xFF;
+ break;
+
+ case 0xF: /* Tone portamento */
+ if(s->volume_column & 0x0F) {
+ ch->tone_portamento_param = ((s->volume_column & 0x0F) << 4)
+ | (s->volume_column & 0x0F);
+ }
+ break;
+
+ default:
+ break;
+
+ }
+
+ switch(s->effect_type) {
+
+ case 1: /* 1xx: Portamento up */
+ if(s->effect_param > 0) {
+ ch->portamento_up_param = s->effect_param;
+ }
+ break;
+
+ case 2: /* 2xx: Portamento down */
+ if(s->effect_param > 0) {
+ ch->portamento_down_param = s->effect_param;
+ }
+ break;
+
+ case 3: /* 3xx: Tone portamento */
+ if(s->effect_param > 0) {
+ ch->tone_portamento_param = s->effect_param;
+ }
+ break;
+
+ case 4: /* 4xy: Vibrato */
+ if(s->effect_param & 0x0F) {
+ /* Set vibrato depth */
+ ch->vibrato_param = (ch->vibrato_param & 0xF0) | (s->effect_param & 0x0F);
+ }
+ if(s->effect_param >> 4) {
+ /* Set vibrato speed */
+ ch->vibrato_param = (s->effect_param & 0xF0) | (ch->vibrato_param & 0x0F);
+ }
+ break;
+
+ case 5: /* 5xy: Tone portamento + Volume slide */
+ if(s->effect_param > 0) {
+ ch->volume_slide_param = s->effect_param;
+ }
+ break;
+
+ case 6: /* 6xy: Vibrato + Volume slide */
+ if(s->effect_param > 0) {
+ ch->volume_slide_param = s->effect_param;
+ }
+ break;
+
+ case 7: /* 7xy: Tremolo */
+ if(s->effect_param & 0x0F) {
+ /* Set tremolo depth */
+ ch->tremolo_param = (ch->tremolo_param & 0xF0) | (s->effect_param & 0x0F);
+ }
+ if(s->effect_param >> 4) {
+ /* Set tremolo speed */
+ ch->tremolo_param = (s->effect_param & 0xF0) | (ch->tremolo_param & 0x0F);
+ }
+ break;
+
+ case 8: /* 8xx: Set panning */
+ ch->panning = (float)s->effect_param / (float)0xFF;
+ break;
+
+ case 9: /* 9xx: Sample offset */
+ if(ch->sample != NULL && NOTE_IS_VALID(s->note)) {
+ uint32_t final_offset = s->effect_param << (ch->sample->bits == 16 ? 7 : 8);
+ if(final_offset >= ch->sample->length) {
+ /* Pretend the sample dosen't loop and is done playing */
+ ch->sample_position = -1;
+ break;
+ }
+ ch->sample_position = final_offset;
+ }
+ break;
+
+ case 0xA: /* Axy: Volume slide */
+ if(s->effect_param > 0) {
+ ch->volume_slide_param = s->effect_param;
+ }
+ break;
+
+ case 0xB: /* Bxx: Position jump */
+ if(s->effect_param < ctx->module.length) {
+ ctx->position_jump = true;
+ ctx->jump_dest = s->effect_param;
+ }
+ break;
+
+ case 0xC: /* Cxx: Set volume */
+ ch->volume = (float)((s->effect_param > 0x40)
+ ? 0x40 : s->effect_param) / (float)0x40;
+ break;
+
+ case 0xD: /* Dxx: Pattern break */
+ /* Jump after playing this line */
+ ctx->pattern_break = true;
+ ctx->jump_row = (s->effect_param >> 4) * 10 + (s->effect_param & 0x0F);
+ break;
+
+ case 0xE: /* EXy: Extended command */
+ switch(s->effect_param >> 4) {
+
+ case 1: /* E1y: Fine portamento up */
+ if(s->effect_param & 0x0F) {
+ ch->fine_portamento_up_param = s->effect_param & 0x0F;
+ }
+ jar_xm_pitch_slide(ctx, ch, -ch->fine_portamento_up_param);
+ break;
+
+ case 2: /* E2y: Fine portamento down */
+ if(s->effect_param & 0x0F) {
+ ch->fine_portamento_down_param = s->effect_param & 0x0F;
+ }
+ jar_xm_pitch_slide(ctx, ch, ch->fine_portamento_down_param);
+ break;
+
+ case 4: /* E4y: Set vibrato control */
+ ch->vibrato_waveform = s->effect_param & 3;
+ ch->vibrato_waveform_retrigger = !((s->effect_param >> 2) & 1);
+ break;
+
+ case 5: /* E5y: Set finetune */
+ if(NOTE_IS_VALID(ch->current->note) && ch->sample != NULL) {
+ ch->note = ch->current->note + ch->sample->relative_note +
+ (float)(((s->effect_param & 0x0F) - 8) << 4) / 128.f - 1.f;
+ ch->period = jar_xm_period(ctx, ch->note);
+ jar_xm_update_frequency(ctx, ch);
+ }
+ break;
+
+ case 6: /* E6y: Pattern loop */
+ if(s->effect_param & 0x0F) {
+ if((s->effect_param & 0x0F) == ch->pattern_loop_count) {
+ /* Loop is over */
+ ch->pattern_loop_count = 0;
+ break;
+ }
+
+ /* Jump to the beginning of the loop */
+ ch->pattern_loop_count++;
+ ctx->position_jump = true;
+ ctx->jump_row = ch->pattern_loop_origin;
+ ctx->jump_dest = ctx->current_table_index;
+ } else {
+ /* Set loop start point */
+ ch->pattern_loop_origin = ctx->current_row;
+ /* Replicate FT2 E60 bug */
+ ctx->jump_row = ch->pattern_loop_origin;
+ }
+ break;
+
+ case 7: /* E7y: Set tremolo control */
+ ch->tremolo_waveform = s->effect_param & 3;
+ ch->tremolo_waveform_retrigger = !((s->effect_param >> 2) & 1);
+ break;
+
+ case 0xA: /* EAy: Fine volume slide up */
+ if(s->effect_param & 0x0F) {
+ ch->fine_volume_slide_param = s->effect_param & 0x0F;
+ }
+ jar_xm_volume_slide(ch, ch->fine_volume_slide_param << 4);
+ break;
+
+ case 0xB: /* EBy: Fine volume slide down */
+ if(s->effect_param & 0x0F) {
+ ch->fine_volume_slide_param = s->effect_param & 0x0F;
+ }
+ jar_xm_volume_slide(ch, ch->fine_volume_slide_param);
+ break;
+
+ case 0xD: /* EDy: Note delay */
+ /* XXX: figure this out better. EDx triggers
+ * the note even when there no note and no
+ * instrument. But ED0 acts like like a ghost
+ * note, EDx (x ≠ 0) does not. */
+ if(s->note == 0 && s->instrument == 0) {
+ unsigned int flags = jar_xm_TRIGGER_KEEP_VOLUME;
+
+ if(ch->current->effect_param & 0x0F) {
+ ch->note = ch->orig_note;
+ jar_xm_trigger_note(ctx, ch, flags);
+ } else {
+ jar_xm_trigger_note(
+ ctx, ch,
+ flags
+ | jar_xm_TRIGGER_KEEP_PERIOD
+ | jar_xm_TRIGGER_KEEP_SAMPLE_POSITION
+ );
+ }
+ }
+ break;
+
+ case 0xE: /* EEy: Pattern delay */
+ ctx->extra_ticks = (ch->current->effect_param & 0x0F) * ctx->tempo;
+ break;
+
+ default:
+ break;
+
+ }
+ break;
+
+ case 0xF: /* Fxx: Set tempo/BPM */
+ if(s->effect_param > 0) {
+ if(s->effect_param <= 0x1F) {
+ ctx->tempo = s->effect_param;
+ } else {
+ ctx->bpm = s->effect_param;
+ }
+ }
+ break;
+
+ case 16: /* Gxx: Set global volume */
+ ctx->global_volume = (float)((s->effect_param > 0x40)
+ ? 0x40 : s->effect_param) / (float)0x40;
+ break;
+
+ case 17: /* Hxy: Global volume slide */
+ if(s->effect_param > 0) {
+ ch->global_volume_slide_param = s->effect_param;
+ }
+ break;
+
+ case 21: /* Lxx: Set envelope position */
+ ch->volume_envelope_frame_count = s->effect_param;
+ ch->panning_envelope_frame_count = s->effect_param;
+ break;
+
+ case 25: /* Pxy: Panning slide */
+ if(s->effect_param > 0) {
+ ch->panning_slide_param = s->effect_param;
+ }
+ break;
+
+ case 27: /* Rxy: Multi retrig note */
+ if(s->effect_param > 0) {
+ if((s->effect_param >> 4) == 0) {
+ /* Keep previous x value */
+ ch->multi_retrig_param = (ch->multi_retrig_param & 0xF0) | (s->effect_param & 0x0F);
+ } else {
+ ch->multi_retrig_param = s->effect_param;
+ }
+ }
+ break;
+
+ case 29: /* Txy: Tremor */
+ if(s->effect_param > 0) {
+ /* Tremor x and y params do not appear to be separately
+ * kept in memory, unlike Rxy */
+ ch->tremor_param = s->effect_param;
+ }
+ break;
+
+ case 33: /* Xxy: Extra stuff */
+ switch(s->effect_param >> 4) {
+
+ case 1: /* X1y: Extra fine portamento up */
+ if(s->effect_param & 0x0F) {
+ ch->extra_fine_portamento_up_param = s->effect_param & 0x0F;
+ }
+ jar_xm_pitch_slide(ctx, ch, -1.0f * ch->extra_fine_portamento_up_param);
+ break;
+
+ case 2: /* X2y: Extra fine portamento down */
+ if(s->effect_param & 0x0F) {
+ ch->extra_fine_portamento_down_param = s->effect_param & 0x0F;
+ }
+ jar_xm_pitch_slide(ctx, ch, ch->extra_fine_portamento_down_param);
+ break;
+
+ default:
+ break;
+
+ }
+ break;
+
+ default:
+ break;
+
+ }
+}
+
+static void jar_xm_trigger_note(jar_xm_context_t* ctx, jar_xm_channel_context_t* ch, unsigned int flags) {
+ if(!(flags & jar_xm_TRIGGER_KEEP_SAMPLE_POSITION)) {
+ ch->sample_position = 0.f;
+ ch->ping = true;
+ }
+
+ if(ch->sample != NULL) {
+ if(!(flags & jar_xm_TRIGGER_KEEP_VOLUME)) {
+ ch->volume = ch->sample->volume;
+ }
+
+ ch->panning = ch->sample->panning;
+ }
+
+ ch->sustained = true;
+ ch->fadeout_volume = ch->volume_envelope_volume = 1.0f;
+ ch->panning_envelope_panning = .5f;
+ ch->volume_envelope_frame_count = ch->panning_envelope_frame_count = 0;
+ ch->vibrato_note_offset = 0.f;
+ ch->tremolo_volume = 0.f;
+ ch->tremor_on = false;
+
+ ch->autovibrato_ticks = 0;
+
+ if(ch->vibrato_waveform_retrigger) {
+ ch->vibrato_ticks = 0; /* XXX: should the waveform itself also
+ * be reset to sine? */
+ }
+ if(ch->tremolo_waveform_retrigger) {
+ ch->tremolo_ticks = 0;
+ }
+
+ if(!(flags & jar_xm_TRIGGER_KEEP_PERIOD)) {
+ ch->period = jar_xm_period(ctx, ch->note);
+ jar_xm_update_frequency(ctx, ch);
+ }
+
+ ch->latest_trigger = ctx->generated_samples;
+ if(ch->instrument != NULL) {
+ ch->instrument->latest_trigger = ctx->generated_samples;
+ }
+ if(ch->sample != NULL) {
+ ch->sample->latest_trigger = ctx->generated_samples;
+ }
+}
+
+static void jar_xm_cut_note(jar_xm_channel_context_t* ch) {
+ /* NB: this is not the same as Key Off */
+ ch->volume = .0f;
+}
+
+static void jar_xm_key_off(jar_xm_channel_context_t* ch) {
+ /* Key Off */
+ ch->sustained = false;
+
+ /* If no volume envelope is used, also cut the note */
+ if(ch->instrument == NULL || !ch->instrument->volume_envelope.enabled) {
+ jar_xm_cut_note(ch);
+ }
+}
+
+static void jar_xm_row(jar_xm_context_t* ctx) {
+ if(ctx->position_jump) {
+ ctx->current_table_index = ctx->jump_dest;
+ ctx->current_row = ctx->jump_row;
+ ctx->position_jump = false;
+ ctx->pattern_break = false;
+ ctx->jump_row = 0;
+ jar_xm_post_pattern_change(ctx);
+ } else if(ctx->pattern_break) {
+ ctx->current_table_index++;
+ ctx->current_row = ctx->jump_row;
+ ctx->pattern_break = false;
+ ctx->jump_row = 0;
+ jar_xm_post_pattern_change(ctx);
+ }
+
+ jar_xm_pattern_t* cur = ctx->module.patterns + ctx->module.pattern_table[ctx->current_table_index];
+ bool in_a_loop = false;
+
+ /* Read notes… */
+ for(uint8_t i = 0; i < ctx->module.num_channels; ++i) {
+ jar_xm_pattern_slot_t* s = cur->slots + ctx->current_row * ctx->module.num_channels + i;
+ jar_xm_channel_context_t* ch = ctx->channels + i;
+
+ ch->current = s;
+
+ if(s->effect_type != 0xE || s->effect_param >> 4 != 0xD) {
+ jar_xm_handle_note_and_instrument(ctx, ch, s);
+ } else {
+ ch->note_delay_param = s->effect_param & 0x0F;
+ }
+
+ if(!in_a_loop && ch->pattern_loop_count > 0) {
+ in_a_loop = true;
+ }
+ }
+
+ if(!in_a_loop) {
+ /* No E6y loop is in effect (or we are in the first pass) */
+ ctx->loop_count = (ctx->row_loop_count[MAX_NUM_ROWS * ctx->current_table_index + ctx->current_row]++);
+ }
+
+ ctx->current_row++; /* Since this is an uint8, this line can
+ * increment from 255 to 0, in which case it
+ * is still necessary to go the next
+ * pattern. */
+ if(!ctx->position_jump && !ctx->pattern_break &&
+ (ctx->current_row >= cur->num_rows || ctx->current_row == 0)) {
+ ctx->current_table_index++;
+ ctx->current_row = ctx->jump_row; /* This will be 0 most of
+ * the time, except when E60
+ * is used */
+ ctx->jump_row = 0;
+ jar_xm_post_pattern_change(ctx);
+ }
+}
+
+static void jar_xm_envelope_tick(jar_xm_channel_context_t* ch,
+ jar_xm_envelope_t* env,
+ uint16_t* counter,
+ float* outval) {
+ if(env->num_points < 2) {
+ /* Don't really know what to do… */
+ if(env->num_points == 1) {
+ /* XXX I am pulling this out of my ass */
+ *outval = (float)env->points[0].value / (float)0x40;
+ if(*outval > 1) {
+ *outval = 1;
+ }
+ }
+
+ return;
+ } else {
+ uint8_t j;
+
+ if(env->loop_enabled) {
+ uint16_t loop_start = env->points[env->loop_start_point].frame;
+ uint16_t loop_end = env->points[env->loop_end_point].frame;
+ uint16_t loop_length = loop_end - loop_start;
+
+ if(*counter >= loop_end) {
+ *counter -= loop_length;
+ }
+ }
+
+ for(j = 0; j < (env->num_points - 2); ++j) {
+ if(env->points[j].frame <= *counter &&
+ env->points[j+1].frame >= *counter) {
+ break;
+ }
+ }
+
+ *outval = jar_xm_envelope_lerp(env->points + j, env->points + j + 1, *counter) / (float)0x40;
+
+ /* Make sure it is safe to increment frame count */
+ if(!ch->sustained || !env->sustain_enabled ||
+ *counter != env->points[env->sustain_point].frame) {
+ (*counter)++;
+ }
+ }
+}
+
+static void jar_xm_envelopes(jar_xm_channel_context_t* ch) {
+ if(ch->instrument != NULL) {
+ if(ch->instrument->volume_envelope.enabled) {
+ if(!ch->sustained) {
+ ch->fadeout_volume -= (float)ch->instrument->volume_fadeout / 65536.f;
+ jar_xm_CLAMP_DOWN(ch->fadeout_volume);
+ }
+
+ jar_xm_envelope_tick(ch,
+ &(ch->instrument->volume_envelope),
+ &(ch->volume_envelope_frame_count),
+ &(ch->volume_envelope_volume));
+ }
+
+ if(ch->instrument->panning_envelope.enabled) {
+ jar_xm_envelope_tick(ch,
+ &(ch->instrument->panning_envelope),
+ &(ch->panning_envelope_frame_count),
+ &(ch->panning_envelope_panning));
+ }
+ }
+}
+
+static void jar_xm_tick(jar_xm_context_t* ctx) {
+ if(ctx->current_tick == 0) {
+ jar_xm_row(ctx);
+ }
+
+ for(uint8_t i = 0; i < ctx->module.num_channels; ++i) {
+ jar_xm_channel_context_t* ch = ctx->channels + i;
+
+ jar_xm_envelopes(ch);
+ jar_xm_autovibrato(ctx, ch);
+
+ if(ch->arp_in_progress && !HAS_ARPEGGIO(ch->current)) {
+ ch->arp_in_progress = false;
+ ch->arp_note_offset = 0;
+ jar_xm_update_frequency(ctx, ch);
+ }
+ if(ch->vibrato_in_progress && !HAS_VIBRATO(ch->current)) {
+ ch->vibrato_in_progress = false;
+ ch->vibrato_note_offset = 0.f;
+ jar_xm_update_frequency(ctx, ch);
+ }
+
+ switch(ch->current->volume_column >> 4) {
+
+ case 0x6: /* Volume slide down */
+ if(ctx->current_tick == 0) break;
+ jar_xm_volume_slide(ch, ch->current->volume_column & 0x0F);
+ break;
+
+ case 0x7: /* Volume slide up */
+ if(ctx->current_tick == 0) break;
+ jar_xm_volume_slide(ch, ch->current->volume_column << 4);
+ break;
+
+ case 0xB: /* Vibrato */
+ if(ctx->current_tick == 0) break;
+ ch->vibrato_in_progress = false;
+ jar_xm_vibrato(ctx, ch, ch->vibrato_param, ch->vibrato_ticks++);
+ break;
+
+ case 0xD: /* Panning slide left */
+ if(ctx->current_tick == 0) break;
+ jar_xm_panning_slide(ch, ch->current->volume_column & 0x0F);
+ break;
+
+ case 0xE: /* Panning slide right */
+ if(ctx->current_tick == 0) break;
+ jar_xm_panning_slide(ch, ch->current->volume_column << 4);
+ break;
+
+ case 0xF: /* Tone portamento */
+ if(ctx->current_tick == 0) break;
+ jar_xm_tone_portamento(ctx, ch);
+ break;
+
+ default:
+ break;
+
+ }
+
+ switch(ch->current->effect_type) {
+
+ case 0: /* 0xy: Arpeggio */
+ if(ch->current->effect_param > 0) {
+ char arp_offset = ctx->tempo % 3;
+ switch(arp_offset) {
+ case 2: /* 0 -> x -> 0 -> y -> x -> … */
+ if(ctx->current_tick == 1) {
+ ch->arp_in_progress = true;
+ ch->arp_note_offset = ch->current->effect_param >> 4;
+ jar_xm_update_frequency(ctx, ch);
+ break;
+ }
+ /* No break here, this is intended */
+ case 1: /* 0 -> 0 -> y -> x -> … */
+ if(ctx->current_tick == 0) {
+ ch->arp_in_progress = false;
+ ch->arp_note_offset = 0;
+ jar_xm_update_frequency(ctx, ch);
+ break;
+ }
+ /* No break here, this is intended */
+ case 0: /* 0 -> y -> x -> … */
+ jar_xm_arpeggio(ctx, ch, ch->current->effect_param, ctx->current_tick - arp_offset);
+ default:
+ break;
+ }
+ }
+ break;
+
+ case 1: /* 1xx: Portamento up */
+ if(ctx->current_tick == 0) break;
+ jar_xm_pitch_slide(ctx, ch, -ch->portamento_up_param);
+ break;
+
+ case 2: /* 2xx: Portamento down */
+ if(ctx->current_tick == 0) break;
+ jar_xm_pitch_slide(ctx, ch, ch->portamento_down_param);
+ break;
+
+ case 3: /* 3xx: Tone portamento */
+ if(ctx->current_tick == 0) break;
+ jar_xm_tone_portamento(ctx, ch);
+ break;
+
+ case 4: /* 4xy: Vibrato */
+ if(ctx->current_tick == 0) break;
+ ch->vibrato_in_progress = true;
+ jar_xm_vibrato(ctx, ch, ch->vibrato_param, ch->vibrato_ticks++);
+ break;
+
+ case 5: /* 5xy: Tone portamento + Volume slide */
+ if(ctx->current_tick == 0) break;
+ jar_xm_tone_portamento(ctx, ch);
+ jar_xm_volume_slide(ch, ch->volume_slide_param);
+ break;
+
+ case 6: /* 6xy: Vibrato + Volume slide */
+ if(ctx->current_tick == 0) break;
+ ch->vibrato_in_progress = true;
+ jar_xm_vibrato(ctx, ch, ch->vibrato_param, ch->vibrato_ticks++);
+ jar_xm_volume_slide(ch, ch->volume_slide_param);
+ break;
+
+ case 7: /* 7xy: Tremolo */
+ if(ctx->current_tick == 0) break;
+ jar_xm_tremolo(ctx, ch, ch->tremolo_param, ch->tremolo_ticks++);
+ break;
+
+ case 0xA: /* Axy: Volume slide */
+ if(ctx->current_tick == 0) break;
+ jar_xm_volume_slide(ch, ch->volume_slide_param);
+ break;
+
+ case 0xE: /* EXy: Extended command */
+ switch(ch->current->effect_param >> 4) {
+
+ case 0x9: /* E9y: Retrigger note */
+ if(ctx->current_tick != 0 && ch->current->effect_param & 0x0F) {
+ if(!(ctx->current_tick % (ch->current->effect_param & 0x0F))) {
+ jar_xm_trigger_note(ctx, ch, 0);
+ jar_xm_envelopes(ch);
+ }
+ }
+ break;
+
+ case 0xC: /* ECy: Note cut */
+ if((ch->current->effect_param & 0x0F) == ctx->current_tick) {
+ jar_xm_cut_note(ch);
+ }
+ break;
+
+ case 0xD: /* EDy: Note delay */
+ if(ch->note_delay_param == ctx->current_tick) {
+ jar_xm_handle_note_and_instrument(ctx, ch, ch->current);
+ jar_xm_envelopes(ch);
+ }
+ break;
+
+ default:
+ break;
+
+ }
+ break;
+
+ case 17: /* Hxy: Global volume slide */
+ if(ctx->current_tick == 0) break;
+ if((ch->global_volume_slide_param & 0xF0) &&
+ (ch->global_volume_slide_param & 0x0F)) {
+ /* Illegal state */
+ break;
+ }
+ if(ch->global_volume_slide_param & 0xF0) {
+ /* Global slide up */
+ float f = (float)(ch->global_volume_slide_param >> 4) / (float)0x40;
+ ctx->global_volume += f;
+ jar_xm_CLAMP_UP(ctx->global_volume);
+ } else {
+ /* Global slide down */
+ float f = (float)(ch->global_volume_slide_param & 0x0F) / (float)0x40;
+ ctx->global_volume -= f;
+ jar_xm_CLAMP_DOWN(ctx->global_volume);
+ }
+ break;
+
+ case 20: /* Kxx: Key off */
+ /* Most documentations will tell you the parameter has no
+ * use. Don't be fooled. */
+ if(ctx->current_tick == ch->current->effect_param) {
+ jar_xm_key_off(ch);
+ }
+ break;
+
+ case 25: /* Pxy: Panning slide */
+ if(ctx->current_tick == 0) break;
+ jar_xm_panning_slide(ch, ch->panning_slide_param);
+ break;
+
+ case 27: /* Rxy: Multi retrig note */
+ if(ctx->current_tick == 0) break;
+ if(((ch->multi_retrig_param) & 0x0F) == 0) break;
+ if((ctx->current_tick % (ch->multi_retrig_param & 0x0F)) == 0) {
+ float v = ch->volume * multi_retrig_multiply[ch->multi_retrig_param >> 4]
+ + multi_retrig_add[ch->multi_retrig_param >> 4];
+ jar_xm_CLAMP(v);
+ jar_xm_trigger_note(ctx, ch, 0);
+ ch->volume = v;
+ }
+ break;
+
+ case 29: /* Txy: Tremor */
+ if(ctx->current_tick == 0) break;
+ ch->tremor_on = (
+ (ctx->current_tick - 1) % ((ch->tremor_param >> 4) + (ch->tremor_param & 0x0F) + 2)
+ >
+ (ch->tremor_param >> 4)
+ );
+ break;
+
+ default:
+ break;
+
+ }
+
+ float panning, volume;
+
+ panning = ch->panning +
+ (ch->panning_envelope_panning - .5f) * (.5f - fabsf(ch->panning - .5f)) * 2.0f;
+
+ if(ch->tremor_on) {
+ volume = .0f;
+ } else {
+ volume = ch->volume + ch->tremolo_volume;
+ jar_xm_CLAMP(volume);
+ volume *= ch->fadeout_volume * ch->volume_envelope_volume;
+ }
+
+#if JAR_XM_RAMPING
+ ch->target_panning = panning;
+ ch->target_volume = volume;
+#else
+ ch->actual_panning = panning;
+ ch->actual_volume = volume;
+#endif
+ }
+
+ ctx->current_tick++;
+ if(ctx->current_tick >= ctx->tempo + ctx->extra_ticks) {
+ ctx->current_tick = 0;
+ ctx->extra_ticks = 0;
+ }
+
+ /* FT2 manual says number of ticks / second = BPM * 0.4 */
+ ctx->remaining_samples_in_tick += (float)ctx->rate / ((float)ctx->bpm * 0.4f);
+}
+
+static float jar_xm_next_of_sample(jar_xm_channel_context_t* ch) {
+ if(ch->instrument == NULL || ch->sample == NULL || ch->sample_position < 0) {
+#if JAR_XM_RAMPING
+ if(ch->frame_count < jar_xm_SAMPLE_RAMPING_POINTS) {
+ return jar_xm_LERP(ch->end_of_previous_sample[ch->frame_count], .0f,
+ (float)ch->frame_count / (float)jar_xm_SAMPLE_RAMPING_POINTS);
+ }
+#endif
+ return .0f;
+ }
+ if(ch->sample->length == 0) {
+ return .0f;
+ }
+
+ float u, v, t;
+ uint32_t a, b;
+ a = (uint32_t)ch->sample_position; /* This cast is fine,
+ * sample_position will not
+ * go above integer
+ * ranges */
+ if(JAR_XM_LINEAR_INTERPOLATION) {
+ b = a + 1;
+ t = ch->sample_position - a; /* Cheaper than fmodf(., 1.f) */
+ }
+ u = ch->sample->data[a];
+
+ switch(ch->sample->loop_type) {
+
+ case jar_xm_NO_LOOP:
+ if(JAR_XM_LINEAR_INTERPOLATION) {
+ v = (b < ch->sample->length) ? ch->sample->data[b] : .0f;
+ }
+ ch->sample_position += ch->step;
+ if(ch->sample_position >= ch->sample->length) {
+ ch->sample_position = -1;
+ }
+ break;
+
+ case jar_xm_FORWARD_LOOP:
+ if(JAR_XM_LINEAR_INTERPOLATION) {
+ v = ch->sample->data[
+ (b == ch->sample->loop_end) ? ch->sample->loop_start : b
+ ];
+ }
+ ch->sample_position += ch->step;
+ while(ch->sample_position >= ch->sample->loop_end) {
+ ch->sample_position -= ch->sample->loop_length;
+ }
+ break;
+
+ case jar_xm_PING_PONG_LOOP:
+ if(ch->ping) {
+ ch->sample_position += ch->step;
+ } else {
+ ch->sample_position -= ch->step;
+ }
+ /* XXX: this may not work for very tight ping-pong loops
+ * (ie switches direction more than once per sample */
+ if(ch->ping) {
+ if(JAR_XM_LINEAR_INTERPOLATION) {
+ v = (b >= ch->sample->loop_end) ? ch->sample->data[a] : ch->sample->data[b];
+ }
+ if(ch->sample_position >= ch->sample->loop_end) {
+ ch->ping = false;
+ ch->sample_position = (ch->sample->loop_end << 1) - ch->sample_position;
+ }
+ /* sanity checking */
+ if(ch->sample_position >= ch->sample->length) {
+ ch->ping = false;
+ ch->sample_position -= ch->sample->length - 1;
+ }
+ } else {
+ if(JAR_XM_LINEAR_INTERPOLATION) {
+ v = u;
+ u = (b == 1 || b - 2 <= ch->sample->loop_start) ? ch->sample->data[a] : ch->sample->data[b - 2];
+ }
+ if(ch->sample_position <= ch->sample->loop_start) {
+ ch->ping = true;
+ ch->sample_position = (ch->sample->loop_start << 1) - ch->sample_position;
+ }
+ /* sanity checking */
+ if(ch->sample_position <= .0f) {
+ ch->ping = true;
+ ch->sample_position = .0f;
+ }
+ }
+ break;
+
+ default:
+ v = .0f;
+ break;
+ }
+
+ float endval = JAR_XM_LINEAR_INTERPOLATION ? jar_xm_LERP(u, v, t) : u;
+
+#if JAR_XM_RAMPING
+ if(ch->frame_count < jar_xm_SAMPLE_RAMPING_POINTS) {
+ /* Smoothly transition between old and new sample. */
+ return jar_xm_LERP(ch->end_of_previous_sample[ch->frame_count], endval,
+ (float)ch->frame_count / (float)jar_xm_SAMPLE_RAMPING_POINTS);
+ }
+#endif
+
+ return endval;
+}
+
+static void jar_xm_sample(jar_xm_context_t* ctx, float* left, float* right) {
+ if(ctx->remaining_samples_in_tick <= 0) {
+ jar_xm_tick(ctx);
+ }
+ ctx->remaining_samples_in_tick--;
+
+ *left = 0.f;
+ *right = 0.f;
+
+ if(ctx->max_loop_count > 0 && ctx->loop_count >= ctx->max_loop_count) {
+ return;
+ }
+
+ for(uint8_t i = 0; i < ctx->module.num_channels; ++i) {
+ jar_xm_channel_context_t* ch = ctx->channels + i;
+
+ if(ch->instrument == NULL || ch->sample == NULL || ch->sample_position < 0) {
+ continue;
+ }
+
+ const float fval = jar_xm_next_of_sample(ch);
+
+ if(!ch->muted && !ch->instrument->muted) {
+ *left += fval * ch->actual_volume * (1.f - ch->actual_panning);
+ *right += fval * ch->actual_volume * ch->actual_panning;
+ }
+
+#if JAR_XM_RAMPING
+ ch->frame_count++;
+ jar_xm_SLIDE_TOWARDS(ch->actual_volume, ch->target_volume, ctx->volume_ramp);
+ jar_xm_SLIDE_TOWARDS(ch->actual_panning, ch->target_panning, ctx->panning_ramp);
+#endif
+ }
+
+ const float fgvol = ctx->global_volume * ctx->amplification;
+ *left *= fgvol;
+ *right *= fgvol;
+
+#if JAR_XM_DEBUG
+ if(fabs(*left) > 1 || fabs(*right) > 1) {
+ DEBUG("clipping frame: %f %f, this is a bad module or a libxm bug", *left, *right);
+ }
+#endif
+}
+
+void jar_xm_generate_samples(jar_xm_context_t* ctx, float* output, size_t numsamples) {
+ if(ctx && output) {
+ ctx->generated_samples += numsamples;
+ for(size_t i = 0; i < numsamples; i++) {
+ jar_xm_sample(ctx, output + (2 * i), output + (2 * i + 1));
+ }
+ }
+}
+
+uint64_t jar_xm_get_remaining_samples(jar_xm_context_t* ctx)
+{
+ uint64_t total = 0;
+ uint8_t currentLoopCount = jar_xm_get_loop_count(ctx);
+ jar_xm_set_max_loop_count(ctx, 0);
+
+ while(jar_xm_get_loop_count(ctx) == currentLoopCount)
+ {
+ total += ctx->remaining_samples_in_tick;
+ ctx->remaining_samples_in_tick = 0;
+ jar_xm_tick(ctx);
+ }
+
+ ctx->loop_count = currentLoopCount;
+ return total;
+}
+
+
+
+
+
+//--------------------------------------------
+//FILE LOADER - TODO - NEEDS TO BE CLEANED UP
+//--------------------------------------------
+
+
+
+#undef DEBUG
+#define DEBUG(...) do { \
+ fprintf(stderr, __VA_ARGS__); \
+ fflush(stderr); \
+ } while(0)
+
+#define DEBUG_ERR(...) do { \
+ fprintf(stderr, __VA_ARGS__); \
+ fflush(stderr); \
+ } while(0)
+
+#define FATAL(...) do { \
+ fprintf(stderr, __VA_ARGS__); \
+ fflush(stderr); \
+ exit(1); \
+ } while(0)
+
+#define FATAL_ERR(...) do { \
+ fprintf(stderr, __VA_ARGS__); \
+ fflush(stderr); \
+ exit(1); \
+ } while(0)
+
+
+int jar_xm_create_context_from_file(jar_xm_context_t** ctx, uint32_t rate, const char* filename) {
+ FILE* xmf;
+ int size;
+
+ xmf = fopen(filename, "rb");
+ if(xmf == NULL) {
+ DEBUG_ERR("Could not open input file");
+ *ctx = NULL;
+ return 3;
+ }
+
+ fseek(xmf, 0, SEEK_END);
+ size = ftell(xmf);
+ rewind(xmf);
+ if(size == -1) {
+ fclose(xmf);
+ DEBUG_ERR("fseek() failed");
+ *ctx = NULL;
+ return 4;
+ }
+
+ char* data = malloc(size + 1);
+ if(fread(data, 1, size, xmf) < size) {
+ fclose(xmf);
+ DEBUG_ERR("fread() failed");
+ *ctx = NULL;
+ return 5;
+ }
+
+ fclose(xmf);
+
+ switch(jar_xm_create_context_safe(ctx, data, size, rate)) {
+ case 0:
+ break;
+
+ case 1:
+ DEBUG("could not create context: module is not sane\n");
+ *ctx = NULL;
+ return 1;
+ break;
+
+ case 2:
+ FATAL("could not create context: malloc failed\n");
+ return 2;
+ break;
+
+ default:
+ FATAL("could not create context: unknown error\n");
+ return 6;
+ break;
+
+ }
+
+ return 0;
+}
+
+
+
+
+#endif//end of JAR_XM_IMPLEMENTATION
+//-------------------------------------------------------------------------------
+
+
+
+
+#endif//end of INCLUDE_JAR_XM_H
diff --git a/src/models.c b/src/models.c
index a1590424..44e30390 100644
--- a/src/models.c
+++ b/src/models.c
@@ -55,7 +55,9 @@ extern unsigned int whiteTexture;
//----------------------------------------------------------------------------------
// Module specific Functions Declaration
//----------------------------------------------------------------------------------
-static Mesh LoadOBJ(const char *fileName);
+static Mesh LoadOBJ(const char *fileName); // Load OBJ mesh data
+static Material LoadMTL(const char *fileName); // Load MTL material data
+
static Mesh GenMeshHeightmap(Image image, Vector3 size);
static Mesh GenMeshCubicmap(Image cubicmap, Vector3 cubeSize);
@@ -542,48 +544,134 @@ void DrawGizmo(Vector3 position)
Model LoadModel(const char *fileName)
{
Model model = { 0 };
- Mesh mesh = { 0 };
- // NOTE: Initialize default data for model in case loading fails, maybe a cube?
+ // TODO: Initialize default data for model in case loading fails, maybe a cube?
- if (strcmp(GetExtension(fileName),"obj") == 0) mesh = LoadOBJ(fileName);
+ if (strcmp(GetExtension(fileName),"obj") == 0) model.mesh = LoadOBJ(fileName);
else TraceLog(WARNING, "[%s] Model extension not recognized, it can't be loaded", fileName);
- // NOTE: At this point we have all vertex, texcoord, normal data for the model in mesh struct
-
- if (mesh.vertexCount == 0) TraceLog(WARNING, "Model could not be loaded");
+ if (model.mesh.vertexCount == 0) TraceLog(WARNING, "Model could not be loaded");
else
{
- // NOTE: model properties (transform, texture, shader) are initialized inside rlglLoadModel()
- model = rlglLoadModel(mesh); // Upload vertex data to GPU
-
- // NOTE: Now that vertex data is uploaded to GPU VRAM, we can free arrays from CPU RAM
- // We don't need CPU vertex data on OpenGL 3.3 or ES2... for static meshes...
- // ...but we could keep CPU vertex data in case we need to update the mesh
+ rlglLoadMesh(&model.mesh, false); // Upload vertex data to GPU (static model)
+
+ model.transform = MatrixIdentity();
+ model.material = LoadDefaultMaterial();
}
return model;
}
// Load a 3d model (from vertex data)
-Model LoadModelEx(Mesh data)
+Model LoadModelEx(Mesh data, bool dynamic)
{
- Model model;
+ Model model = { 0 };
- // NOTE: model properties (transform, texture, shader) are initialized inside rlglLoadModel()
- model = rlglLoadModel(data); // Upload vertex data to GPU
+ model.mesh = data;
+
+ rlglLoadMesh(&model.mesh, dynamic); // Upload vertex data to GPU
- // NOTE: Vertex data is managed externally, must be deallocated manually
+ model.transform = MatrixIdentity();
+ model.material = LoadDefaultMaterial();
return model;
}
+// Load a 3d model from rRES file (raylib Resource)
+Model LoadModelFromRES(const char *rresName, int resId)
+{
+ Model model = { 0 };
+ bool found = false;
+
+ char id[4]; // rRES file identifier
+ unsigned char version; // rRES file version and subversion
+ char useless; // rRES header reserved data
+ short numRes;
+
+ ResInfoHeader infoHeader;
+
+ FILE *rresFile = fopen(rresName, "rb");
+
+ if (rresFile == NULL)
+ {
+ TraceLog(WARNING, "[%s] rRES raylib resource file could not be opened", rresName);
+ }
+ else
+ {
+ // Read rres file (basic file check - id)
+ fread(&id[0], sizeof(char), 1, rresFile);
+ fread(&id[1], sizeof(char), 1, rresFile);
+ fread(&id[2], sizeof(char), 1, rresFile);
+ fread(&id[3], sizeof(char), 1, rresFile);
+ fread(&version, sizeof(char), 1, rresFile);
+ fread(&useless, sizeof(char), 1, rresFile);
+
+ if ((id[0] != 'r') && (id[1] != 'R') && (id[2] != 'E') &&(id[3] != 'S'))
+ {
+ TraceLog(WARNING, "[%s] This is not a valid raylib resource file", rresName);
+ }
+ else
+ {
+ // Read number of resources embedded
+ fread(&numRes, sizeof(short), 1, rresFile);
+
+ for (int i = 0; i < numRes; i++)
+ {
+ fread(&infoHeader, sizeof(ResInfoHeader), 1, rresFile);
+
+ if (infoHeader.id == resId)
+ {
+ found = true;
+
+ // Check data is of valid MODEL type
+ if (infoHeader.type == 8)
+ {
+ // TODO: Load model data
+ }
+ else
+ {
+ TraceLog(WARNING, "[%s] Required resource do not seem to be a valid MODEL resource", rresName);
+ }
+ }
+ else
+ {
+ // Depending on type, skip the right amount of parameters
+ switch (infoHeader.type)
+ {
+ case 0: fseek(rresFile, 6, SEEK_CUR); break; // IMAGE: Jump 6 bytes of parameters
+ case 1: fseek(rresFile, 6, SEEK_CUR); break; // SOUND: Jump 6 bytes of parameters
+ case 2: fseek(rresFile, 5, SEEK_CUR); break; // MODEL: Jump 5 bytes of parameters (TODO: Review)
+ case 3: break; // TEXT: No parameters
+ case 4: break; // RAW: No parameters
+ default: break;
+ }
+
+ // Jump DATA to read next infoHeader
+ fseek(rresFile, infoHeader.size, SEEK_CUR);
+ }
+ }
+ }
+
+ fclose(rresFile);
+ }
+
+ if (!found) TraceLog(WARNING, "[%s] Required resource id [%i] could not be found in the raylib resource file", rresName, resId);
+
+ return model;
+}
+
// Load a heightmap image as a 3d model
// NOTE: model map size is defined in generic units
Model LoadHeightmap(Image heightmap, Vector3 size)
{
- Mesh mesh = GenMeshHeightmap(heightmap, size);
- Model model = rlglLoadModel(mesh);
+ Model model = { 0 };
+
+ model.mesh = GenMeshHeightmap(heightmap, size);
+
+ rlglLoadMesh(&model.mesh, false); // Upload vertex data to GPU (static model)
+
+ model.transform = MatrixIdentity();
+ model.material = LoadDefaultMaterial();
return model;
}
@@ -591,34 +679,76 @@ Model LoadHeightmap(Image heightmap, Vector3 size)
// Load a map image as a 3d model (cubes based)
Model LoadCubicmap(Image cubicmap)
{
- Mesh mesh = GenMeshCubicmap(cubicmap, (Vector3){ 1.0, 1.0, 1.5f });
- Model model = rlglLoadModel(mesh);
+ Model model = { 0 };
+
+ model.mesh = GenMeshCubicmap(cubicmap, (Vector3){ 1.0, 1.0, 1.5f });
+
+ rlglLoadMesh(&model.mesh, false); // Upload vertex data to GPU (static model)
+
+ model.transform = MatrixIdentity();
+ model.material = LoadDefaultMaterial();
return model;
}
-// Unload 3d model from memory
+// Unload 3d model from memory (mesh and material)
void UnloadModel(Model model)
{
- // Unload mesh data
- free(model.mesh.vertices);
- free(model.mesh.texcoords);
- free(model.mesh.normals);
- free(model.mesh.colors);
- //if (model.mesh.texcoords2 != NULL) free(model.mesh.texcoords2); // Not used
- //if (model.mesh.tangents != NULL) free(model.mesh.tangents); // Not used
+ rlglUnloadMesh(&model.mesh);
+
+ UnloadMaterial(model.material);
- rlDeleteBuffers(model.mesh.vboId[0]); // vertex
- rlDeleteBuffers(model.mesh.vboId[1]); // texcoords
- rlDeleteBuffers(model.mesh.vboId[2]); // normals
- //rlDeleteBuffers(model.mesh.vboId[3]); // texcoords2 (NOT USED)
- //rlDeleteBuffers(model.mesh.vboId[4]); // tangents (NOT USED)
- //rlDeleteBuffers(model.mesh.vboId[5]); // colors (NOT USED)
-
- rlDeleteVertexArrays(model.mesh.vaoId);
+ TraceLog(INFO, "Unloaded model data from RAM and VRAM");
+}
+
+// Load material data (from file)
+Material LoadMaterial(const char *fileName)
+{
+ Material material = { 0 };
+
+ if (strcmp(GetExtension(fileName),"mtl") == 0) material = LoadMTL(fileName);
+ else TraceLog(WARNING, "[%s] Material extension not recognized, it can't be loaded", fileName);
+
+ return material;
+}
+
+// Load default material (uses default models shader)
+Material LoadDefaultMaterial(void)
+{
+ Material material = { 0 };
+
+ material.shader = GetDefaultShader();
+ material.texDiffuse = GetDefaultTexture(); // White texture (1x1 pixel)
+ //material.texNormal; // NOTE: By default, not set
+ //material.texSpecular; // NOTE: By default, not set
+
+ material.colDiffuse = WHITE; // Diffuse color
+ material.colAmbient = WHITE; // Ambient color
+ material.colSpecular = WHITE; // Specular color
+
+ material.glossiness = 100.0f; // Glossiness level
+ material.normalDepth = 1.0f; // Normal map depth
- if (model.mesh.vaoId > 0) TraceLog(INFO, "[VAO ID %i] Unloaded model data from VRAM (GPU)", model.mesh.vaoId);
- else TraceLog(INFO, "[VBO ID %i][VBO ID %i][VBO ID %i] Unloaded model data from VRAM (GPU)", model.mesh.vboId[0], model.mesh.vboId[1], model.mesh.vboId[2]);
+ return material;
+}
+
+// Load standard material (uses standard models shader)
+// NOTE: Standard shader supports multiple maps and lights
+Material LoadStandardMaterial(void)
+{
+ Material material = LoadDefaultMaterial();
+
+ //material.shader = GetStandardShader();
+
+ return material;
+}
+
+// Unload material from memory
+void UnloadMaterial(Material material)
+{
+ rlDeleteTextures(material.texDiffuse.id);
+ rlDeleteTextures(material.texNormal.id);
+ rlDeleteTextures(material.texSpecular.id);
}
// Link a texture to a model
@@ -628,11 +758,12 @@ void SetModelTexture(Model *model, Texture2D texture)
else model->material.texDiffuse = texture;
}
+// Generate a mesh from heightmap
static Mesh GenMeshHeightmap(Image heightmap, Vector3 size)
{
#define GRAY_VALUE(c) ((c.r+c.g+c.b)/3)
- Mesh mesh;
+ Mesh mesh = { 0 };
int mapX = heightmap.width;
int mapZ = heightmap.height;
@@ -647,7 +778,7 @@ static Mesh GenMeshHeightmap(Image heightmap, Vector3 size)
mesh.vertices = (float *)malloc(mesh.vertexCount*3*sizeof(float));
mesh.normals = (float *)malloc(mesh.vertexCount*3*sizeof(float));
mesh.texcoords = (float *)malloc(mesh.vertexCount*2*sizeof(float));
- mesh.colors = (unsigned char *)malloc(mesh.vertexCount*4*sizeof(unsigned char)); // Not used...
+ mesh.colors = NULL;
int vCounter = 0; // Used to count vertices float by float
int tcCounter = 0; // Used to count texcoords float by float
@@ -730,16 +861,12 @@ static Mesh GenMeshHeightmap(Image heightmap, Vector3 size)
free(pixels);
- // Fill color data
- // NOTE: Not used any more... just one plain color defined at DrawModel()
- for (int i = 0; i < (4*mesh.vertexCount); i++) mesh.colors[i] = 255;
-
return mesh;
}
static Mesh GenMeshCubicmap(Image cubicmap, Vector3 cubeSize)
{
- Mesh mesh;
+ Mesh mesh = { 0 };
Color *cubicmapPixels = GetImageData(cubicmap);
@@ -1048,11 +1175,7 @@ static Mesh GenMeshCubicmap(Image cubicmap, Vector3 cubeSize)
mesh.vertices = (float *)malloc(mesh.vertexCount*3*sizeof(float));
mesh.normals = (float *)malloc(mesh.vertexCount*3*sizeof(float));
mesh.texcoords = (float *)malloc(mesh.vertexCount*2*sizeof(float));
- mesh.colors = (unsigned char *)malloc(mesh.vertexCount*4*sizeof(unsigned char)); // Not used...
-
- // Fill color data
- // NOTE: Not used any more... just one plain color defined at DrawModel()
- for (int i = 0; i < (4*mesh.vertexCount); i++) mesh.colors[i] = 255;
+ mesh.colors = NULL;
int fCounter = 0;
@@ -1100,31 +1223,46 @@ void DrawModel(Model model, Vector3 position, float scale, Color tint)
{
Vector3 vScale = { scale, scale, scale };
Vector3 rotationAxis = { 0.0f, 0.0f, 0.0f };
-
+
DrawModelEx(model, position, rotationAxis, 0.0f, vScale, tint);
}
// Draw a model with extended parameters
void DrawModelEx(Model model, Vector3 position, Vector3 rotationAxis, float rotationAngle, Vector3 scale, Color tint)
{
- // NOTE: Rotation must be provided in degrees, it's converted to radians inside rlglDrawModel()
- rlglDrawModel(model, position, rotationAxis, rotationAngle, scale, tint, false);
+ // Calculate transformation matrix from function parameters
+ // Get transform matrix (rotation -> scale -> translation)
+ Matrix matRotation = MatrixRotate(rotationAxis, rotationAngle*DEG2RAD);
+ Matrix matScale = MatrixScale(scale.x, scale.y, scale.z);
+ Matrix matTranslation = MatrixTranslate(position.x, position.y, position.z);
+
+ // Combine model transformation matrix (model.transform) with matrix generated by function parameters (matTransform)
+ //Matrix matModel = MatrixMultiply(model.transform, matTransform); // Transform to world-space coordinates
+
+ model.transform = MatrixMultiply(MatrixMultiply(matScale, matRotation), matTranslation);
+ model.material.colDiffuse = tint;
+
+ rlglDrawMesh(model.mesh, model.material, model.transform);
}
// Draw a model wires (with texture if set)
-void DrawModelWires(Model model, Vector3 position, float scale, Color color)
+void DrawModelWires(Model model, Vector3 position, float scale, Color tint)
{
- Vector3 vScale = { scale, scale, scale };
- Vector3 rotationAxis = { 0.0f, 0.0f, 0.0f };
-
- rlglDrawModel(model, position, rotationAxis, 0.0f, vScale, color, true);
+ rlEnableWireMode();
+
+ DrawModel(model, position, scale, tint);
+
+ rlDisableWireMode();
}
// Draw a model wires (with texture if set) with extended parameters
void DrawModelWiresEx(Model model, Vector3 position, Vector3 rotationAxis, float rotationAngle, Vector3 scale, Color tint)
{
- // NOTE: Rotation must be provided in degrees, it's converted to radians inside rlglDrawModel()
- rlglDrawModel(model, position, rotationAxis, rotationAngle, scale, tint, true);
+ rlEnableWireMode();
+
+ DrawModelEx(model, position, rotationAxis, rotationAngle, scale, tint);
+
+ rlDisableWireMode();
}
// Draw a billboard
@@ -1329,13 +1467,19 @@ bool CheckCollisionRayBox(Ray ray, BoundingBox box)
BoundingBox CalculateBoundingBox(Mesh mesh)
{
// Get min and max vertex to construct bounds (AABB)
- Vector3 minVertex = (Vector3){ mesh.vertices[0], mesh.vertices[1], mesh.vertices[2] };
- Vector3 maxVertex = (Vector3){ mesh.vertices[0], mesh.vertices[1], mesh.vertices[2] };
+ Vector3 minVertex = { 0 };
+ Vector3 maxVertex = { 0 };
- for (int i = 1; i < mesh.vertexCount; i++)
+ if (mesh.vertices != NULL)
{
- minVertex = VectorMin(minVertex, (Vector3){ mesh.vertices[i*3], mesh.vertices[i*3 + 1], mesh.vertices[i*3 + 2] });
- maxVertex = VectorMax(maxVertex, (Vector3){ mesh.vertices[i*3], mesh.vertices[i*3 + 1], mesh.vertices[i*3 + 2] });
+ minVertex = (Vector3){ mesh.vertices[0], mesh.vertices[1], mesh.vertices[2] };
+ maxVertex = (Vector3){ mesh.vertices[0], mesh.vertices[1], mesh.vertices[2] };
+
+ for (int i = 1; i < mesh.vertexCount; i++)
+ {
+ minVertex = VectorMin(minVertex, (Vector3){ mesh.vertices[i*3], mesh.vertices[i*3 + 1], mesh.vertices[i*3 + 2] });
+ maxVertex = VectorMax(maxVertex, (Vector3){ mesh.vertices[i*3], mesh.vertices[i*3 + 1], mesh.vertices[i*3 + 2] });
+ }
}
// Create the bounding box
@@ -1736,7 +1880,7 @@ static Mesh LoadOBJ(const char *fileName)
mesh.vertices = (float *)malloc(mesh.vertexCount*3*sizeof(float));
mesh.texcoords = (float *)malloc(mesh.vertexCount*2*sizeof(float));
mesh.normals = (float *)malloc(mesh.vertexCount*3*sizeof(float));
- mesh.colors = (unsigned char *)malloc(mesh.vertexCount*4*sizeof(unsigned char));
+ mesh.colors = NULL;
int vCounter = 0; // Used to count vertices float by float
int tcCounter = 0; // Used to count texcoords float by float
@@ -1835,10 +1979,6 @@ static Mesh LoadOBJ(const char *fileName)
// Security check, just in case no normals or no texcoords defined in OBJ
if (numTexCoords == 0) for (int i = 0; i < (2*mesh.vertexCount); i++) mesh.texcoords[i] = 0.0f;
-
- // NOTE: We set all vertex colors to white
- // NOTE: Not used any more... just one plain color defined at DrawModel()
- for (int i = 0; i < (4*mesh.vertexCount); i++) mesh.colors[i] = 255;
// Now we can free temp mid* arrays
free(midVertices);
@@ -1850,3 +1990,163 @@ static Mesh LoadOBJ(const char *fileName)
return mesh;
}
+
+// Load MTL material data (specs: http://paulbourke.net/dataformats/mtl/)
+// NOTE: Texture map parameters are not supported
+static Material LoadMTL(const char *fileName)
+{
+ #define MAX_BUFFER_SIZE 128
+
+ Material material = { 0 }; // LoadDefaultMaterial();
+
+ char buffer[MAX_BUFFER_SIZE];
+ Vector3 color = { 1.0f, 1.0f, 1.0f };
+ char *mapFileName = NULL;
+
+ FILE *mtlFile;
+
+ mtlFile = fopen(fileName, "rt");
+
+ if (mtlFile == NULL)
+ {
+ TraceLog(WARNING, "[%s] MTL file could not be opened", fileName);
+ return material;
+ }
+
+ while(!feof(mtlFile))
+ {
+ fgets(buffer, MAX_BUFFER_SIZE, mtlFile);
+
+ switch (buffer[0])
+ {
+ case 'n': // newmtl string Material name. Begins a new material description.
+ {
+ // TODO: Support multiple materials in a single .mtl
+ sscanf(buffer, "newmtl %s", mapFileName);
+
+ TraceLog(INFO, "[%s] Loading material...", mapFileName);
+ }
+ case 'i': // illum int Illumination model
+ {
+ // illum = 1 if specular disabled
+ // illum = 2 if specular enabled (lambertian model)
+ // ...
+ }
+ case 'K': // Ka, Kd, Ks, Ke
+ {
+ switch (buffer[1])
+ {
+ case 'a': // Ka float float float Ambient color (RGB)
+ {
+ sscanf(buffer, "Ka %f %f %f", &color.x, &color.y, &color.z);
+ material.colAmbient.r = (unsigned char)(color.x*255);
+ material.colAmbient.g = (unsigned char)(color.y*255);
+ material.colAmbient.b = (unsigned char)(color.z*255);
+ } break;
+ case 'd': // Kd float float float Diffuse color (RGB)
+ {
+ sscanf(buffer, "Kd %f %f %f", &color.x, &color.y, &color.z);
+ material.colDiffuse.r = (unsigned char)(color.x*255);
+ material.colDiffuse.g = (unsigned char)(color.y*255);
+ material.colDiffuse.b = (unsigned char)(color.z*255);
+ } break;
+ case 's': // Ks float float float Specular color (RGB)
+ {
+ sscanf(buffer, "Ks %f %f %f", &color.x, &color.y, &color.z);
+ material.colSpecular.r = (unsigned char)(color.x*255);
+ material.colSpecular.g = (unsigned char)(color.y*255);
+ material.colSpecular.b = (unsigned char)(color.z*255);
+ } break;
+ case 'e': // Ke float float float Emmisive color (RGB)
+ {
+ // TODO: Support Ke ?
+ } break;
+ default: break;
+ }
+ } break;
+ case 'N': // Ns, Ni
+ {
+ if (buffer[1] == 's') // Ns int Shininess (specular exponent). Ranges from 0 to 1000.
+ {
+ sscanf(buffer, "Ns %i", &material.glossiness);
+ }
+ else if (buffer[1] == 'i') // Ni int Refraction index.
+ {
+ // Not supported...
+ }
+ } break;
+ case 'm': // map_Kd, map_Ks, map_Ka, map_Bump, map_d
+ {
+ switch (buffer[4])
+ {
+ case 'K': // Color texture maps
+ {
+ if (buffer[5] == 'd') // map_Kd string Diffuse color texture map.
+ {
+ sscanf(buffer, "map_Kd %s", mapFileName);
+ if (mapFileName != NULL) material.texDiffuse = LoadTexture(mapFileName);
+ }
+ else if (buffer[5] == 's') // map_Ks string Specular color texture map.
+ {
+ sscanf(buffer, "map_Ks %s", mapFileName);
+ if (mapFileName != NULL) material.texSpecular = LoadTexture(mapFileName);
+ }
+ else if (buffer[5] == 'a') // map_Ka string Ambient color texture map.
+ {
+ // Not supported...
+ }
+ } break;
+ case 'B': // map_Bump string Bump texture map.
+ {
+ sscanf(buffer, "map_Bump %s", mapFileName);
+ if (mapFileName != NULL) material.texNormal = LoadTexture(mapFileName);
+ } break;
+ case 'b': // map_bump string Bump texture map.
+ {
+ sscanf(buffer, "map_bump %s", mapFileName);
+ if (mapFileName != NULL) material.texNormal = LoadTexture(mapFileName);
+ } break;
+ case 'd': // map_d string Opacity texture map.
+ {
+ // Not supported...
+ } break;
+ default: break;
+ }
+ } break;
+ case 'd': // d, disp
+ {
+ if (buffer[1] == ' ') // d float Dissolve factor. d is inverse of Tr
+ {
+ float alpha = 1.0f;
+ sscanf(buffer, "d %f", &alpha);
+ material.colDiffuse.a = (unsigned char)(alpha*255);
+ }
+ else if (buffer[1] == 'i') // disp string Displacement map
+ {
+ // Not supported...
+ }
+ } break;
+ case 'b': // bump string Bump texture map
+ {
+ sscanf(buffer, "bump %s", mapFileName);
+ if (mapFileName != NULL) material.texNormal = LoadTexture(mapFileName);
+ } break;
+ case 'T': // Tr float Transparency Tr (alpha). Tr is inverse of d
+ {
+ float ialpha = 0.0f;
+ sscanf(buffer, "Tr %f", &ialpha);
+ material.colDiffuse.a = (unsigned char)((1.0f - ialpha)*255);
+
+ } break;
+ case 'r': // refl string Reflection texture map
+ default: break;
+ }
+ }
+
+ fclose(mtlFile);
+
+ // NOTE: At this point we have all material data
+ TraceLog(INFO, "[%s] Material loaded successfully", fileName);
+
+ return material;
+}
diff --git a/src/physac.c b/src/physac.c
index ed707474..181488ac 100644
--- a/src/physac.c
+++ b/src/physac.c
@@ -49,7 +49,7 @@
//----------------------------------------------------------------------------------
// Global Variables Definition
//----------------------------------------------------------------------------------
-static PhysicObject *physicObjects[MAX_PHYSIC_OBJECTS]; // Physic objects pool
+static PhysicObject physicObjects[MAX_PHYSIC_OBJECTS]; // Physic objects pool
static int physicObjectsCount; // Counts current enabled physic objects
static Vector2 gravityForce; // Gravity force
@@ -463,10 +463,10 @@ void ClosePhysics()
}
// Create a new physic object dinamically, initialize it and add to pool
-PhysicObject *CreatePhysicObject(Vector2 position, float rotation, Vector2 scale)
+PhysicObject CreatePhysicObject(Vector2 position, float rotation, Vector2 scale)
{
// Allocate dynamic memory
- PhysicObject *obj = (PhysicObject *)malloc(sizeof(PhysicObject));
+ PhysicObject obj = (PhysicObject)malloc(sizeof(PhysicObjectData));
// Initialize physic object values with generic values
obj->id = physicObjectsCount;
@@ -498,7 +498,7 @@ PhysicObject *CreatePhysicObject(Vector2 position, float rotation, Vector2 scale
}
// Destroy a specific physic object and take it out of the list
-void DestroyPhysicObject(PhysicObject *pObj)
+void DestroyPhysicObject(PhysicObject pObj)
{
// Free dynamic memory allocation
free(physicObjects[pObj->id]);
@@ -520,7 +520,7 @@ void DestroyPhysicObject(PhysicObject *pObj)
}
// Apply directional force to a physic object
-void ApplyForce(PhysicObject *pObj, Vector2 force)
+void ApplyForce(PhysicObject pObj, Vector2 force)
{
if (pObj->rigidbody.enabled)
{
@@ -571,7 +571,7 @@ Rectangle TransformToRectangle(Transform transform)
}
// Draw physic object information at screen position
-void DrawPhysicObjectInfo(PhysicObject *pObj, Vector2 position, int fontSize)
+void DrawPhysicObjectInfo(PhysicObject pObj, Vector2 position, int fontSize)
{
// Draw physic object ID
DrawText(FormatText("PhysicObject ID: %i - Enabled: %i", pObj->id, pObj->enabled), position.x, position.y, fontSize, BLACK);
diff --git a/src/physac.h b/src/physac.h
index 37544686..6cef480a 100644
--- a/src/physac.h
+++ b/src/physac.h
@@ -66,13 +66,13 @@ typedef struct Collider {
int radius; // Used for COLLIDER_CIRCLE
} Collider;
-typedef struct PhysicObject {
+typedef struct PhysicObjectData {
unsigned int id;
Transform transform;
Rigidbody rigidbody;
Collider collider;
bool enabled;
-} PhysicObject;
+} PhysicObjectData, *PhysicObject;
#ifdef __cplusplus
extern "C" { // Prevents name mangling of functions
@@ -85,14 +85,14 @@ void InitPhysics(Vector2 gravity);
void UpdatePhysics(); // Update physic objects, calculating physic behaviours and collisions detection
void ClosePhysics(); // Unitialize all physic objects and empty the objects pool
-PhysicObject *CreatePhysicObject(Vector2 position, float rotation, Vector2 scale); // Create a new physic object dinamically, initialize it and add to pool
-void DestroyPhysicObject(PhysicObject *pObj); // Destroy a specific physic object and take it out of the list
+PhysicObject CreatePhysicObject(Vector2 position, float rotation, Vector2 scale); // Create a new physic object dinamically, initialize it and add to pool
+void DestroyPhysicObject(PhysicObject pObj); // Destroy a specific physic object and take it out of the list
-void ApplyForce(PhysicObject *pObj, Vector2 force); // Apply directional force to a physic object
+void ApplyForce(PhysicObject pObj, Vector2 force); // Apply directional force to a physic object
void ApplyForceAtPosition(Vector2 position, float force, float radius); // Apply radial force to all physic objects in range
Rectangle TransformToRectangle(Transform transform); // Convert Transform data type to Rectangle (position and scale)
-void DrawPhysicObjectInfo(PhysicObject *pObj, Vector2 position, int fontSize); // Draw physic object information at screen position
+void DrawPhysicObjectInfo(PhysicObject pObj, Vector2 position, int fontSize); // Draw physic object information at screen position
#ifdef __cplusplus
}
diff --git a/src/raylib.h b/src/raylib.h
index 1782fef3..d98a0797 100644
--- a/src/raylib.h
+++ b/src/raylib.h
@@ -1,6 +1,6 @@
/**********************************************************************************************
*
-* raylib 1.4.0 (www.raylib.com)
+* raylib 1.5.0 (www.raylib.com)
*
* A simple and easy-to-use library to learn videogames programming
*
@@ -261,7 +261,9 @@
//----------------------------------------------------------------------------------
#ifndef __cplusplus
// Boolean type
-typedef enum { false, true } bool;
+ #ifndef true
+ typedef enum { false, true } bool;
+ #endif
#endif
// byte type
@@ -324,6 +326,13 @@ typedef struct Texture2D {
int format; // Data format (TextureFormat)
} Texture2D;
+// RenderTexture2D type, for texture rendering
+typedef struct RenderTexture2D {
+ unsigned int id; // Render texture (fbo) id
+ Texture2D texture; // Color buffer attachment texture
+ Texture2D depth; // Depth buffer attachment texture
+} RenderTexture2D;
+
// SpriteFont type, includes texture and charSet array data
typedef struct SpriteFont {
Texture2D texture; // Font texture
@@ -345,8 +354,8 @@ typedef struct Camera {
// Camera2D type, defines a 2d camera
typedef struct Camera2D {
- Vector2 position; // Camera position
- Vector2 origin; // Camera origin (for rotation and zoom)
+ Vector2 offset; // Camera offset (displacement from target)
+ Vector2 target; // Camera target (rotation and zoom origin)
float rotation; // Camera rotation in degrees
float zoom; // Camera zoom (scaling), should be 1.0f by default
} Camera2D;
@@ -359,34 +368,39 @@ typedef struct BoundingBox {
// Vertex data definning a mesh
typedef struct Mesh {
- int vertexCount; // num vertices
- float *vertices; // vertex position (XYZ - 3 components per vertex)
- float *texcoords; // vertex texture coordinates (UV - 2 components per vertex)
- float *texcoords2; // vertex second texture coordinates (useful for lightmaps)
- float *normals; // vertex normals (XYZ - 3 components per vertex)
- float *tangents; // vertex tangents (XYZ - 3 components per vertex)
- unsigned char *colors; // vertex colors (RGBA - 4 components per vertex)
+ int vertexCount; // number of vertices stored in arrays
+ 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 *normals; // vertex normals (XYZ - 3 components per vertex) (shader-location = 2)
+ float *tangents; // vertex tangents (XYZ - 3 components per vertex) (shader-location = 4)
+ unsigned char *colors; // vertex colors (RGBA - 4 components per vertex) (shader-location = 3)
+ unsigned short *indices; // vertex indices (in case vertex data comes indexed)
+ int triangleCount; // number of triangles stored (indexed or not)
BoundingBox bounds; // mesh limits defined by min and max points
unsigned int vaoId; // OpenGL Vertex Array Object id
- unsigned int vboId[6]; // OpenGL Vertex Buffer Objects id (6 types of vertex data)
+ unsigned int vboId[7]; // OpenGL Vertex Buffer Objects id (7 types of vertex data)
} Mesh;
// Shader type (generic shader)
typedef struct Shader {
- unsigned int id; // Shader program id
+ unsigned int id; // Shader program id
- // Variable attributes
- int vertexLoc; // Vertex attribute location point (vertex shader)
- int texcoordLoc; // Texcoord attribute location point (vertex shader)
- int normalLoc; // Normal attribute location point (vertex shader)
- int colorLoc; // Color attibute location point (vertex shader)
-
- // Uniforms
+ // Vertex attributes locations (default locations)
+ int vertexLoc; // Vertex attribute location point (default-location = 0)
+ int texcoordLoc; // Texcoord attribute location point (default-location = 1)
+ int normalLoc; // Normal attribute location point (default-location = 2)
+ int colorLoc; // Color attibute location point (default-location = 3)
+ int tangentLoc; // Tangent attribute location point (default-location = 4)
+ int texcoord2Loc; // Texcoord2 attribute location point (default-location = 5)
+
+ // Uniform locations
int mvpLoc; // ModelView-Projection matrix uniform location point (vertex shader)
int tintColorLoc; // Color uniform location point (fragment shader)
+ // Texture map locations
int mapDiffuseLoc; // Diffuse map texture uniform location point (fragment shader)
int mapNormalLoc; // Normal map texture uniform location point (fragment shader)
int mapSpecularLoc; // Specular map texture uniform location point (fragment shader)
@@ -408,14 +422,38 @@ typedef struct Material {
float normalDepth; // Normal map depth
} Material;
-// 3d Model type
-// TODO: Replace shader/testure by material
+// Model type
typedef struct Model {
- Mesh mesh;
- Matrix transform;
- Material material;
+ Mesh mesh; // Vertex data buffers (RAM and VRAM)
+ Matrix transform; // Local transform matrix
+ Material material; // Shader and textures data
} Model;
+// Light type
+// TODO: Review contained data to support different light types and features
+typedef struct LightData {
+ int id;
+ int type; // LIGHT_POINT, LIGHT_DIRECTIONAL, LIGHT_SPOT
+ bool enabled;
+
+ Vector3 position;
+ Vector3 direction; // Used on LIGHT_DIRECTIONAL and LIGHT_SPOT (cone direction)
+ float attenuation; // Lost of light intensity with distance (use radius?)
+
+ Color diffuse; // Use Vector3 diffuse (including intensities)?
+ float intensity;
+
+ Color specular;
+ //float specFactor; // Specular intensity ?
+
+ //Color ambient; // Required?
+
+ float coneAngle; // SpotLight
+} LightData, *Light;
+
+// Light types
+typedef enum { LIGHT_POINT, LIGHT_DIRECTIONAL, LIGHT_SPOT } LightType;
+
// Ray type (useful for raycast)
typedef struct Ray {
Vector3 position;
@@ -432,11 +470,13 @@ typedef struct Sound {
typedef struct Wave {
void *data; // Buffer data pointer
unsigned int dataSize; // Data size in bytes
- unsigned int sampleRate;
- short bitsPerSample;
+ unsigned int sampleRate; // Samples per second to be played
+ short bitsPerSample; // Sample size in bits
short channels;
} Wave;
+typedef int RawAudioContext;
+
// Texture formats
// NOTE: Support depends on OpenGL version and platform
typedef enum {
@@ -484,7 +524,7 @@ typedef enum { TOUCH_UP, TOUCH_DOWN, TOUCH_MOVE } TouchAction;
// Gesture events
// NOTE: MAX_TOUCH_POINTS fixed to 2
-typedef struct {
+typedef struct GestureEvent {
int touchAction;
int pointCount;
int pointerId[MAX_TOUCH_POINTS];
@@ -520,13 +560,13 @@ typedef struct Collider {
int radius; // Used for COLLIDER_CIRCLE
} Collider;
-typedef struct PhysicObject {
+typedef struct PhysicObjectData {
unsigned int id;
Transform transform;
Rigidbody rigidbody;
Collider collider;
bool enabled;
-} PhysicObject;
+} PhysicObjectData, *PhysicObject;
#ifdef __cplusplus
extern "C" { // Prevents name mangling of functions
@@ -550,24 +590,28 @@ void CloseWindow(void); // Close Window and
bool WindowShouldClose(void); // Detect if KEY_ESCAPE pressed or Close icon pressed
bool IsWindowMinimized(void); // Detect if window has been minimized (or lost focus)
void ToggleFullscreen(void); // Fullscreen toggle (only PLATFORM_DESKTOP)
-#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_RPI)
-void SetCustomCursor(const char *cursorImage); // Set a custom cursor icon/image
-void SetExitKey(int key); // Set a custom key to exit program (default is ESC)
-#endif
int GetScreenWidth(void); // Get current screen width
int GetScreenHeight(void); // Get current screen height
+void ShowCursor(void); // Shows cursor
+void HideCursor(void); // Hides cursor
+bool IsCursorHidden(void); // Returns true if cursor is not visible
+void EnableCursor(void); // Enables cursor
+void DisableCursor(void); // Disables cursor
+
void ClearBackground(Color color); // Sets Background Color
void BeginDrawing(void); // Setup drawing canvas to start drawing
-void BeginDrawingEx(Camera2D camera); // Setup drawing canvas with 2d camera
-void BeginDrawingPro(int blendMode, Shader shader, Matrix transform); // Setup drawing canvas with pro parameters
void EndDrawing(void); // End canvas drawing and Swap Buffers (Double Buffering)
+void Begin2dMode(Camera2D camera); // Initialize 2D mode with custom camera
+void End2dMode(void); // Ends 2D mode custom camera usage
void Begin3dMode(Camera camera); // Initializes 3D mode for drawing (Camera setup)
void End3dMode(void); // Ends 3D mode and returns to default 2D orthographic mode
+void BeginTextureMode(RenderTexture2D target); // Initializes render texture for drawing
+void EndTextureMode(void); // Ends drawing to render texture
Ray GetMouseRay(Vector2 mousePosition, Camera camera); // Returns a ray trace from mouse position
-Vector2 WorldToScreen(Vector3 position, Camera camera); // Returns the screen space position from a 3d world space position
+Vector2 GetWorldToScreen(Vector3 position, Camera camera); // Returns the screen space position from a 3d world space position
Matrix GetCameraMatrix(Camera camera); // Returns camera transform matrix (view matrix)
void SetTargetFPS(int fps); // Set target FPS (maximum)
@@ -602,6 +646,15 @@ bool IsKeyDown(int key); // Detect if a key is be
bool IsKeyReleased(int key); // Detect if a key has been released once
bool IsKeyUp(int key); // Detect if a key is NOT being pressed
int GetKeyPressed(void); // Get latest key pressed
+void SetExitKey(int key); // Set a custom key to exit program (default is ESC)
+
+bool IsGamepadAvailable(int gamepad); // Detect if a gamepad is available
+float GetGamepadAxisMovement(int gamepad, int axis); // Return axis movement value for a gamepad axis
+bool IsGamepadButtonPressed(int gamepad, int button); // Detect if a gamepad button has been pressed once
+bool IsGamepadButtonDown(int gamepad, int button); // Detect if a gamepad button is being pressed
+bool IsGamepadButtonReleased(int gamepad, int button); // Detect if a gamepad button has been released once
+bool IsGamepadButtonUp(int gamepad, int button); // Detect if a gamepad button is NOT being pressed
+#endif
bool IsMouseButtonPressed(int button); // Detect if a mouse button has been pressed once
bool IsMouseButtonDown(int button); // Detect if a mouse button is being pressed
@@ -613,20 +666,6 @@ Vector2 GetMousePosition(void); // Returns mouse positio
void SetMousePosition(Vector2 position); // Set mouse position XY
int GetMouseWheelMove(void); // Returns mouse wheel movement Y
-void ShowCursor(void); // Shows cursor
-void HideCursor(void); // Hides cursor
-void EnableCursor(void); // Enables cursor
-void DisableCursor(void); // Disables cursor
-bool IsCursorHidden(void); // Returns true if cursor is not visible
-
-bool IsGamepadAvailable(int gamepad); // Detect if a gamepad is available
-float GetGamepadAxisMovement(int gamepad, int axis); // Return axis movement value for a gamepad axis
-bool IsGamepadButtonPressed(int gamepad, int button); // Detect if a gamepad button has been pressed once
-bool IsGamepadButtonDown(int gamepad, int button); // Detect if a gamepad button is being pressed
-bool IsGamepadButtonReleased(int gamepad, int button); // Detect if a gamepad button has been released once
-bool IsGamepadButtonUp(int gamepad, int button); // Detect if a gamepad button is NOT being pressed
-#endif
-
int GetTouchX(void); // Returns touch position X for touch point 0 (relative to screen size)
int GetTouchY(void); // Returns touch position Y for touch point 0 (relative to screen size)
Vector2 GetTouchPosition(int index); // Returns touch position XY for a touch point index (relative to screen size)
@@ -641,9 +680,9 @@ bool IsButtonReleased(int button); // Detect if an android
// Gestures and Touch Handling Functions (Module: gestures)
//------------------------------------------------------------------------------------
void ProcessGestureEvent(GestureEvent event); // Process gesture event and translate it into gestures
-void UpdateGestures(void); // Update gestures detected (must be called every frame)
-bool IsGestureDetected(void); // Check if a gesture have been detected
-int GetGestureType(void); // Get latest detected gesture
+void UpdateGestures(void); // Update gestures detected (called automatically in PollInputEvents())
+bool IsGestureDetected(int gesture); // Check if a gesture have been detected
+int GetGestureDetected(void); // Get latest detected gesture
void SetGesturesEnabled(unsigned int gestureFlags); // Enable a set of gestures using flags
int GetTouchPointsCount(void); // Get touch points count
@@ -714,8 +753,10 @@ Texture2D LoadTexture(const char *fileName);
Texture2D LoadTextureEx(void *data, int width, int height, int textureFormat); // Load a texture from raw data into GPU memory
Texture2D LoadTextureFromRES(const char *rresName, int resId); // Load an image as texture from rRES file (raylib Resource)
Texture2D LoadTextureFromImage(Image image); // Load a texture from image data
+RenderTexture2D LoadRenderTexture(int width, int height); // Load a texture to be used for rendering
void UnloadImage(Image image); // Unload image from CPU memory (RAM)
void UnloadTexture(Texture2D texture); // Unload texture from GPU memory
+void UnloadRenderTexture(RenderTexture2D target); // Unload render texture from GPU memory
Color *GetImageData(Image image); // Get pixel data from image as a Color struct array
Image GetTextureData(Texture2D texture); // Get pixel data from GPU texture and return an Image
void ImageToPOT(Image *image, Color fillColor); // Convert image to POT (power-of-two)
@@ -785,17 +826,21 @@ void DrawGizmo(Vector3 position);
//------------------------------------------------------------------------------------
// Model 3d Loading and Drawing Functions (Module: models)
//------------------------------------------------------------------------------------
-Model LoadModel(const char *fileName); // Load a 3d model (.OBJ)
-Model LoadModelEx(Mesh data); // Load a 3d model (from mesh data)
-//Model LoadModelFromRES(const char *rresName, int resId); // TODO: Load a 3d model from rRES file (raylib Resource)
-Model LoadHeightmap(Image heightmap, Vector3 size); // Load a heightmap image as a 3d model
-Model LoadCubicmap(Image cubicmap); // Load a map image as a 3d model (cubes based)
-void UnloadModel(Model model); // Unload 3d model from memory
-void SetModelTexture(Model *model, Texture2D texture); // Link a texture to a model
+Model LoadModel(const char *fileName); // Load a 3d model (.OBJ)
+Model LoadModelEx(Mesh data, bool dynamic); // Load a 3d model (from mesh data)
+Model LoadModelFromRES(const char *rresName, int resId); // Load a 3d model from rRES file (raylib Resource)
+Model LoadHeightmap(Image heightmap, Vector3 size); // Load a heightmap image as a 3d model
+Model LoadCubicmap(Image cubicmap); // Load a map image as a 3d model (cubes based)
+void UnloadModel(Model model); // Unload 3d model from memory
+void SetModelTexture(Model *model, Texture2D texture); // Link a texture to a model
+
+Material LoadMaterial(const char *fileName); // Load material data (from file)
+Material LoadDefaultMaterial(void); // Load default material (uses default models shader)
+void UnloadMaterial(Material material); // Unload material textures from VRAM
void DrawModel(Model model, Vector3 position, float scale, Color tint); // Draw a model (with texture if set)
void DrawModelEx(Model model, Vector3 position, Vector3 rotationAxis, float rotationAngle, Vector3 scale, Color tint); // Draw a model with extended parameters
-void DrawModelWires(Model model, Vector3 position, float scale, Color color); // Draw a model wires (with texture if set)
+void DrawModelWires(Model model, Vector3 position, float scale, Color tint); // Draw a model wires (with texture if set)
void DrawModelWiresEx(Model model, Vector3 position, Vector3 rotationAxis, float rotationAngle, Vector3 scale, Color tint); // Draw a model wires (with texture if set) with extended parameters
void DrawBoundingBox(BoundingBox box, Color color); // Draw bounding box (wires)
@@ -816,25 +861,22 @@ Vector3 ResolveCollisionCubicmap(Image cubicmap, Vector3 mapPosition, Vector3 *p
// NOTE: This functions are useless when using OpenGL 1.1
//------------------------------------------------------------------------------------
Shader LoadShader(char *vsFileName, char *fsFileName); // Load a custom shader and bind default locations
-unsigned int LoadShaderProgram(char *vShaderStr, char *fShaderStr); // Load custom shaders strings and return program id
void UnloadShader(Shader shader); // Unload a custom shader from memory
-void SetPostproShader(Shader shader); // Set fullscreen postproduction shader
-void SetCustomShader(Shader shader); // Set custom shader to be used in batch draw
void SetDefaultShader(void); // Set default shader to be used in batch draw
-void SetModelShader(Model *model, Shader shader); // Link a shader to a model
-bool IsPosproShaderEnabled(void); // Check if postprocessing shader is enabled
-
-int GetShaderLocation(Shader shader, const char *uniformName); // Get shader uniform location
-void SetShaderValue(Shader shader, int uniformLoc, float *value, int size); // Set shader uniform value (float)
-void SetShaderValuei(Shader shader, int uniformLoc, int *value, int size); // Set shader uniform value (int)
-void SetShaderValueMatrix(Shader shader, int uniformLoc, Matrix mat); // Set shader uniform value (matrix 4x4)
-//void SetShaderMapDiffuse(Shader *shader, Texture2D texture); // Default diffuse shader map texture assignment
-//void SetShaderMapNormal(Shader *shader, const char *uniformName, Texture2D texture); // Normal map texture shader assignment
-//void SetShaderMapSpecular(Shader *shader, const char *uniformName, Texture2D texture); // Specular map texture shader assignment
-//void SetShaderMap(Shader *shader, int mapLocation, Texture2D texture, int textureUnit); // TODO: Generic shader map assignment
+void SetCustomShader(Shader shader); // Set custom shader to be used in batch draw
+Shader GetDefaultShader(void); // Get default shader
+Texture2D GetDefaultTexture(void); // Get default texture
+
+int GetShaderLocation(Shader shader, const char *uniformName); // Get shader uniform location
+void SetShaderValue(Shader shader, int uniformLoc, float *value, int size); // Set shader uniform value (float)
+void SetShaderValuei(Shader shader, int uniformLoc, int *value, int size); // Set shader uniform value (int)
+void SetShaderValueMatrix(Shader shader, int uniformLoc, Matrix mat); // Set shader uniform value (matrix 4x4)
void SetBlendMode(int mode); // Set blending mode (alpha, additive, multiplied)
+Light CreateLight(int type, Vector3 position, Color diffuse); // Create a new light, initialize it and add to pool
+void DestroyLight(Light light); // Destroy a light and take it out of the list
+
//----------------------------------------------------------------------------------
// Physics System Functions (Module: physac)
//----------------------------------------------------------------------------------
@@ -842,20 +884,21 @@ void InitPhysics(Vector2 gravity);
void UpdatePhysics(); // Update physic objects, calculating physic behaviours and collisions detection
void ClosePhysics(); // Unitialize all physic objects and empty the objects pool
-PhysicObject *CreatePhysicObject(Vector2 position, float rotation, Vector2 scale); // Create a new physic object dinamically, initialize it and add to pool
-void DestroyPhysicObject(PhysicObject *pObj); // Destroy a specific physic object and take it out of the list
+PhysicObject CreatePhysicObject(Vector2 position, float rotation, Vector2 scale); // Create a new physic object dinamically, initialize it and add to pool
+void DestroyPhysicObject(PhysicObject pObj); // Destroy a specific physic object and take it out of the list
-void ApplyForce(PhysicObject *pObj, Vector2 force); // Apply directional force to a physic object
+void ApplyForce(PhysicObject pObj, Vector2 force); // Apply directional force to a physic object
void ApplyForceAtPosition(Vector2 position, float force, float radius); // Apply radial force to all physic objects in range
Rectangle TransformToRectangle(Transform transform); // Convert Transform data type to Rectangle (position and scale)
-void DrawPhysicObjectInfo(PhysicObject *pObj, Vector2 position, int fontSize); // Draw physic object information at screen position
+void DrawPhysicObjectInfo(PhysicObject pObj, Vector2 position, int fontSize); // Draw physic object information at screen position
//------------------------------------------------------------------------------------
// Audio Loading and Playing Functions (Module: audio)
//------------------------------------------------------------------------------------
void InitAudioDevice(void); // Initialize audio device and context
void CloseAudioDevice(void); // Close the audio device and context (and music stream)
+bool IsAudioDeviceReady(void); // True if call to InitAudioDevice() was successful and CloseAudioDevice() has not been called yet
Sound LoadSound(char *fileName); // Load sound to memory
Sound LoadSoundFromWave(Wave wave); // Load sound to memory from wave data
@@ -864,19 +907,28 @@ void UnloadSound(Sound sound); // Unload sound
void PlaySound(Sound sound); // Play a sound
void PauseSound(Sound sound); // Pause a sound
void StopSound(Sound sound); // Stop playing a sound
-bool SoundIsPlaying(Sound sound); // Check if a sound is currently playing
+bool IsSoundPlaying(Sound sound); // Check if a sound is currently playing
void SetSoundVolume(Sound sound, float volume); // Set volume for a sound (1.0 is max level)
void SetSoundPitch(Sound sound, float pitch); // Set pitch for a sound (1.0 is base level)
-void PlayMusicStream(char *fileName); // Start music playing (open stream)
-void UpdateMusicStream(void); // Updates buffers for music streaming
-void StopMusicStream(void); // Stop music playing (close stream)
-void PauseMusicStream(void); // Pause music playing
-void ResumeMusicStream(void); // Resume playing paused music
-bool MusicIsPlaying(void); // Check if music is playing
-void SetMusicVolume(float volume); // Set volume for music (1.0 is max level)
-float GetMusicTimeLength(void); // Get current music time length (in seconds)
-float GetMusicTimePlayed(void); // Get current music time played (in seconds)
+int PlayMusicStream(int musicIndex, char *fileName); // Start music playing (open stream)
+void UpdateMusicStream(int index); // Updates buffers for music streaming
+void StopMusicStream(int index); // Stop music playing (close stream)
+void PauseMusicStream(int index); // Pause music playing
+void ResumeMusicStream(int index); // Resume playing paused music
+bool IsMusicPlaying(int index); // Check if music is playing
+void SetMusicVolume(int index, float volume); // Set volume for music (1.0 is max level)
+float GetMusicTimeLength(int index); // Get current music time length (in seconds)
+float GetMusicTimePlayed(int index); // Get current music time played (in seconds)
+int getMusicStreamCount(void);
+void SetMusicPitch(int index, float pitch);
+
+// used to output raw audio streams, returns negative numbers on error
+// if floating point is false the data size is 16bit short, otherwise it is float 32bit
+RawAudioContext InitRawAudioContext(int sampleRate, int channels, bool floatingPoint);
+
+void CloseRawAudioContext(RawAudioContext ctx);
+int BufferRawAudioContext(RawAudioContext ctx, void *data, int numberElements); // returns number of elements buffered
#ifdef __cplusplus
}
diff --git a/src/raymath.h b/src/raymath.h
index 52e92b50..59d66e56 100644
--- a/src/raymath.h
+++ b/src/raymath.h
@@ -158,6 +158,7 @@ RMDEF void PrintMatrix(Matrix m); // Print matrix ut
//------------------------------------------------------------------------------------
RMDEF float QuaternionLength(Quaternion quat); // Compute the length of a quaternion
RMDEF void QuaternionNormalize(Quaternion *q); // Normalize provided quaternion
+RMDEF void QuaternionInvert(Quaternion *quat); // Invert provided quaternion
RMDEF Quaternion QuaternionMultiply(Quaternion q1, Quaternion q2); // Calculate two quaternion multiplication
RMDEF Quaternion QuaternionSlerp(Quaternion q1, Quaternion q2, float slerp); // Calculates spherical linear interpolation between two quaternions
RMDEF Quaternion QuaternionFromMatrix(Matrix matrix); // Returns a quaternion for a given rotation matrix
@@ -908,6 +909,23 @@ RMDEF void QuaternionNormalize(Quaternion *q)
q->w *= ilength;
}
+// Invert provided quaternion
+RMDEF void QuaternionInvert(Quaternion *quat)
+{
+ float length = QuaternionLength(*quat);
+ float lengthSq = length*length;
+
+ if (lengthSq != 0.0)
+ {
+ float i = 1.0f/lengthSq;
+
+ quat->x *= -i;
+ quat->y *= -i;
+ quat->z *= -i;
+ quat->w *= i;
+ }
+}
+
// Calculate two quaternion multiplication
RMDEF Quaternion QuaternionMultiply(Quaternion q1, Quaternion q2)
{
diff --git a/src/rlgl.c b/src/rlgl.c
index fc14a0af..e971d747 100644
--- a/src/rlgl.c
+++ b/src/rlgl.c
@@ -71,6 +71,8 @@
#define MAX_DRAWS_BY_TEXTURE 256 // Draws are organized by texture changes
#define TEMP_VERTEX_BUFFER_SIZE 4096 // Temporal Vertex Buffer (required for vertex-transformations)
// NOTE: Every vertex are 3 floats (12 bytes)
+
+#define MAX_LIGHTS 8 // Max lights supported by standard shader
#ifndef GL_SHADING_LANGUAGE_VERSION
#define GL_SHADING_LANGUAGE_VERSION 0x8B8C
@@ -115,58 +117,36 @@
#define GL_UNSIGNED_SHORT_5_5_5_1 0x8034
#define GL_UNSIGNED_SHORT_4_4_4_4 0x8033
#endif
+
+// Default vertex attribute names on shader to set location points
+#define DEFAULT_ATTRIB_POSITION_NAME "vertexPosition" // shader-location = 0
+#define DEFAULT_ATTRIB_TEXCOORD_NAME "vertexTexCoord" // shader-location = 1
+#define DEFAULT_ATTRIB_NORMAL_NAME "vertexNormal" // shader-location = 2
+#define DEFAULT_ATTRIB_COLOR_NAME "vertexColor" // shader-location = 3
+#define DEFAULT_ATTRIB_TANGENT_NAME "vertexTangent" // shader-location = 4
+#define DEFAULT_ATTRIB_TEXCOORD2_NAME "vertexTexCoord2" // shader-location = 5
+
//----------------------------------------------------------------------------------
// Types and Structures Definition
//----------------------------------------------------------------------------------
-// Vertex buffer (position + color arrays)
-// NOTE: Used for lines and triangles VAOs
-typedef struct {
- int vCounter;
- int cCounter;
- float *vertices; // 3 components per vertex
- unsigned char *colors; // 4 components per vertex
-} VertexPositionColorBuffer;
-
-// Vertex buffer (position + texcoords + color arrays)
-// NOTE: Not used
-typedef struct {
- int vCounter;
- int tcCounter;
- int cCounter;
- float *vertices; // 3 components per vertex
- float *texcoords; // 2 components per vertex
- unsigned char *colors; // 4 components per vertex
-} VertexPositionColorTextureBuffer;
-
-// Vertex buffer (position + texcoords + normals arrays)
-// NOTE: Not used
-typedef struct {
- int vCounter;
- int tcCounter;
- int nCounter;
- float *vertices; // 3 components per vertex
- float *texcoords; // 2 components per vertex
- float *normals; // 3 components per vertex
- //short *normals; // NOTE: Less data load... but padding issues and normalizing required!
-} VertexPositionTextureNormalBuffer;
-
-// Vertex buffer (position + texcoords + colors + indices arrays)
-// NOTE: Used for quads VAO
+// Dynamic vertex buffers (position + texcoords + colors + indices arrays)
typedef struct {
- int vCounter;
- int tcCounter;
- int cCounter;
- float *vertices; // 3 components per vertex
- float *texcoords; // 2 components per vertex
- unsigned char *colors; // 4 components per vertex
+ int vCounter; // vertex position counter to process (and draw) from full buffer
+ int tcCounter; // vertex texcoord counter to process (and draw) from full buffer
+ int cCounter; // vertex color counter to process (and draw) from full buffer
+ 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)
+ unsigned char *colors; // vertex colors (RGBA - 4 components per vertex) (shader-location = 3)
#if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33)
- unsigned int *indices; // 6 indices per quad (could be int)
+ unsigned int *indices; // vertex indices (in case vertex data comes indexed) (6 indices per quad)
#elif defined(GRAPHICS_API_OPENGL_ES2)
- unsigned short *indices; // 6 indices per quad (must be short)
+ unsigned short *indices; // vertex indices (in case vertex data comes indexed) (6 indices per quad)
// NOTE: 6*2 byte = 12 byte, not alignment problem!
#endif
-} VertexPositionColorTextureIndexBuffer;
+ unsigned int vaoId; // OpenGL Vertex Array Object id
+ unsigned int vboId[4]; // OpenGL Vertex Buffer Objects id (4 types of vertex data)
+} DynamicBuffer;
// Draw call type
// NOTE: Used to track required draw-calls, organized by texture
@@ -176,24 +156,6 @@ typedef struct {
// TODO: Store draw state -> blending mode, shader
} DrawCall;
-// pixel type (same as Color type)
-// NOTE: Used exclusively in mipmap generation functions
-typedef struct {
- unsigned char r;
- unsigned char g;
- unsigned char b;
- unsigned char a;
-} pixel;
-
-// Framebuffer Object type
-typedef struct {
- GLuint id;
- int width;
- int height;
- GLuint colorTextureId;
- GLuint depthTextureId;
-} FBO;
-
#if defined(RLGL_STANDALONE)
typedef enum { INFO = 0, ERROR, WARNING, DEBUG, OTHER } TraceLogType;
#endif
@@ -214,23 +176,11 @@ static DrawMode currentDrawMode;
static float currentDepth = -1.0f;
-// Vertex arrays for lines, triangles and quads
-static VertexPositionColorBuffer lines; // No texture support
-static VertexPositionColorBuffer triangles; // No texture support
-static VertexPositionColorTextureIndexBuffer quads;
-
-// Shader Programs
-static Shader defaultShader, simpleShader;
-static Shader currentShader; // By default, defaultShader
-
-// Vertex Array Objects (VAO)
-static GLuint vaoLines, vaoTriangles, vaoQuads;
-
-// Vertex Buffer Objects (VBO)
-static GLuint linesBuffer[2];
-static GLuint trianglesBuffer[2];
-static GLuint quadsBuffer[4];
+static DynamicBuffer lines;
+static DynamicBuffer triangles;
+static DynamicBuffer quads;
+// Default buffers draw calls
static DrawCall *draws;
static int drawsCounter;
@@ -239,27 +189,27 @@ static Vector3 *tempBuffer;
static int tempBufferCount = 0;
static bool useTempBuffer = false;
+// Shader Programs
+static Shader defaultShader;
+static Shader currentShader; // By default, defaultShader
+
// Flags for supported extensions
static bool vaoSupported = false; // VAO support (OpenGL ES2 could not support VAO extension)
// Compressed textures support flags
-//static bool texCompDXTSupported = false; // DDS texture compression support
static bool texCompETC1Supported = false; // ETC1 texture compression support
static bool texCompETC2Supported = false; // ETC2/EAC texture compression support
static bool texCompPVRTSupported = false; // PVR texture compression support
static bool texCompASTCSupported = false; // ASTC texture compression support
-// Framebuffer object and texture
-static FBO postproFbo;
-static Model postproQuad;
-
-// Shaders related variables
-static bool enabledPostpro = false;
+// Lighting data
+static Light lights[MAX_LIGHTS]; // Lights pool
+static int lightsCount; // Counts current enabled physic objects
#endif
// Compressed textures support flags
-static bool texCompDXTSupported = false; // DDS texture compression support
-static bool npotSupported = false; // NPOT textures full support
+static bool texCompDXTSupported = false; // DDS texture compression support
+static bool npotSupported = false; // NPOT textures full support
#if defined(GRAPHICS_API_OPENGL_ES2)
// NOTE: VAO functionality is exposed through extensions (OES)
@@ -269,9 +219,6 @@ static PFNGLDELETEVERTEXARRAYSOESPROC glDeleteVertexArrays;
//static PFNGLISVERTEXARRAYOESPROC glIsVertexArray; // NOTE: Fails in WebGL, omitted
#endif
-// Save screen size data (render size), required for postpro quad
-static int screenWidth, screenHeight;
-
static int blendMode = 0;
// White texture useful for plain color polys (required by shader)
@@ -282,22 +229,27 @@ unsigned int whiteTexture;
// Module specific Functions Declaration
//----------------------------------------------------------------------------------
#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
-static Shader LoadDefaultShader(void);
-static Shader LoadSimpleShader(void);
-static void InitializeBuffers(void);
-static void InitializeBuffersGPU(void);
-static void UpdateBuffers(void);
-static char *TextFileRead(char *fn);
-
static void LoadCompressedTexture(unsigned char *data, int width, int height, int mipmapCount, int compressedFormat);
+static unsigned int LoadShaderProgram(char *vShaderStr, char *fShaderStr); // Load custom shader strings and return program id
+
+static Shader LoadDefaultShader(void); // Load default shader (just vertex positioning and texture coloring)
+static Shader LoadStandardShader(void); // Load standard shader (support materials and lighting)
+static void LoadDefaultShaderLocations(Shader *shader); // Bind default shader locations (attributes and uniforms)
+static void UnloadDefaultShader(void); // Unload default shader
-FBO rlglLoadFBO(int width, int height);
-void rlglUnloadFBO(FBO fbo);
+static void LoadDefaultBuffers(void); // Load default internal buffers (lines, triangles, quads)
+static void UpdateDefaultBuffers(void); // Update default internal buffers (VAOs/VBOs) with vertex data
+static void DrawDefaultBuffers(void); // Draw default internal buffers vertex data
+static void UnloadDefaultBuffers(void); // Unload default internal buffers vertex data from CPU and GPU
+
+static void SetShaderLights(Shader shader); // Sets shader uniform values for lights array
+
+static char *ReadTextFile(const char *fileName);
#endif
#if defined(GRAPHICS_API_OPENGL_11)
static int GenerateMipmaps(unsigned char *data, int baseWidth, int baseHeight);
-static pixel *GenNextMipmap(pixel *srcData, int srcWidth, int srcHeight);
+static Color *GenNextMipmap(Color *srcData, int srcWidth, int srcHeight);
#endif
#if defined(RLGL_STANDALONE)
@@ -305,20 +257,6 @@ static void TraceLog(int msgType, const char *text, ...);
float *MatrixToFloat(Matrix mat); // Converts Matrix to float array
#endif
-#if defined(GRAPHICS_API_OPENGL_ES2)
-// NOTE: strdup() functions replacement (not C99, POSIX function, not available on emscripten)
-// Duplicates a string, returning an identical malloc'd string
-char *mystrdup(const char *str)
-{
- size_t len = strlen(str) + 1;
- void *newstr = malloc(len);
-
- if (newstr == NULL) return NULL;
-
- return (char *)memcpy(newstr, str, len);
-}
-#endif
-
//----------------------------------------------------------------------------------
// Module Functions Definition - Matrix operations
//----------------------------------------------------------------------------------
@@ -785,6 +723,20 @@ void rlDisableTexture(void)
#endif
}
+void rlEnableRenderTexture(unsigned int id)
+{
+#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
+ glBindFramebuffer(GL_FRAMEBUFFER, id);
+#endif
+}
+
+void rlDisableRenderTexture(void)
+{
+#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+#endif
+}
+
// Enable depth test
void rlEnableDepthTest(void)
{
@@ -797,17 +749,37 @@ void rlDisableDepthTest(void)
glDisable(GL_DEPTH_TEST);
}
+// Enable wire mode
+void rlEnableWireMode(void)
+{
+#if defined (GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33)
+ // NOTE: glPolygonMode() not available on OpenGL ES
+ glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
+#endif
+}
+
+// Disable wire mode
+void rlDisableWireMode(void)
+{
+#if defined (GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33)
+ // NOTE: glPolygonMode() not available on OpenGL ES
+ glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
+#endif
+}
+
// Unload texture from GPU memory
void rlDeleteTextures(unsigned int id)
{
- glDeleteTextures(1, &id);
+ if (id != 0) glDeleteTextures(1, &id);
}
-// Enable rendering to postprocessing FBO
-void rlEnablePostproFBO()
+// Unload render texture from GPU memory
+void rlDeleteRenderTextures(RenderTexture2D target)
{
#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
- glBindFramebuffer(GL_FRAMEBUFFER, postproFbo.id);
+ if (target.id != 0) glDeleteFramebuffers(1, &target.id);
+ if (target.texture.id != 0) glDeleteTextures(1, &target.texture.id);
+ if (target.depth.id != 0) glDeleteTextures(1, &target.depth.id);
#endif
}
@@ -815,7 +787,7 @@ void rlEnablePostproFBO()
void rlDeleteShader(unsigned int id)
{
#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
- glDeleteProgram(id);
+ if (id != 0) glDeleteProgram(id);
#endif
}
@@ -823,7 +795,11 @@ void rlDeleteShader(unsigned int id)
void rlDeleteVertexArrays(unsigned int id)
{
#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
- if (vaoSupported) glDeleteVertexArrays(1, &id);
+ if (vaoSupported)
+ {
+ if (id != 0) glDeleteVertexArrays(1, &id);
+ TraceLog(INFO, "[VAO ID %i] Unloaded model data from VRAM (GPU)", id);
+ }
#endif
}
@@ -831,7 +807,11 @@ void rlDeleteVertexArrays(unsigned int id)
void rlDeleteBuffers(unsigned int id)
{
#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
- glDeleteBuffers(1, &id);
+ if (id != 0)
+ {
+ glDeleteBuffers(1, &id);
+ if (!vaoSupported) TraceLog(INFO, "[VBO ID %i] Unloaded model vertex data from VRAM (GPU)", id);
+ }
#endif
}
@@ -928,13 +908,18 @@ void rlglInit(void)
// NOTE: We have to duplicate string because glGetString() returns a const value
// If not duplicated, it fails in some systems (Raspberry Pi)
- char *extensionsDup = mystrdup(extensions);
+ // Equivalent to function: char *strdup(const char *str)
+ char *extensionsDup;
+ size_t len = strlen(extensions) + 1;
+ void *newstr = malloc(len);
+ if (newstr == NULL) extensionsDup = NULL;
+ extensionsDup = (char *)memcpy(newstr, extensions, len);
// NOTE: String could be splitted using strtok() function (string.h)
// NOTE: strtok() modifies the received string, it can not be const
char *extList[512]; // Allocate 512 strings pointers (2 KB)
-
+
extList[numExt] = strtok(extensionsDup, " ");
while (extList[numExt] != NULL)
@@ -978,10 +963,12 @@ void rlglInit(void)
// DDS texture compression support
if ((strcmp(extList[i], (const char *)"GL_EXT_texture_compression_s3tc") == 0) ||
+ (strcmp(extList[i], (const char *)"GL_WEBGL_compressed_texture_s3tc") == 0) ||
(strcmp(extList[i], (const char *)"GL_WEBKIT_WEBGL_compressed_texture_s3tc") == 0)) texCompDXTSupported = true;
// ETC1 texture compression support
- if (strcmp(extList[i], (const char *)"GL_OES_compressed_ETC1_RGB8_texture") == 0) texCompETC1Supported = true;
+ if ((strcmp(extList[i], (const char *)"GL_OES_compressed_ETC1_RGB8_texture") == 0) ||
+ (strcmp(extList[i], (const char *)"GL_WEBGL_compressed_texture_etc1") == 0)) texCompETC1Supported = true;
// ETC2/EAC texture compression support
if (strcmp(extList[i], (const char *)"GL_ARB_ES3_compatibility") == 0) texCompETC2Supported = true;
@@ -1029,15 +1016,11 @@ void rlglInit(void)
if (whiteTexture != 0) TraceLog(INFO, "[TEX ID %i] Base white texture loaded successfully", whiteTexture);
else TraceLog(WARNING, "Base white texture could not be loaded");
- // Init default Shader (Custom for GL 3.3 and ES2)
+ // Init default Shader (customized for GL 3.3 and ES2)
defaultShader = LoadDefaultShader();
- simpleShader = LoadSimpleShader();
- //customShader = LoadShader("custom.vs", "custom.fs"); // Works ok
-
currentShader = defaultShader;
- InitializeBuffers(); // Init vertex arrays
- InitializeBuffersGPU(); // Init VBO and VAO
+ LoadDefaultBuffers(); // Initialize default vertex arrays buffers (lines, triangles, quads)
// Init temp vertex buffer, used when transformation required (translate, rotate, scale)
tempBuffer = (Vector3 *)malloc(sizeof(Vector3)*TEMP_VERTEX_BUFFER_SIZE);
@@ -1058,201 +1041,17 @@ void rlglInit(void)
#endif
}
-// Init postpro system
-// NOTE: Uses global variables screenWidth and screenHeight
-// Modifies global variables: postproFbo, postproQuad
-void rlglInitPostpro(void)
-{
-#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
- postproFbo = rlglLoadFBO(screenWidth, screenHeight);
-
- if (postproFbo.id > 0)
- {
- // Create a simple quad model to render fbo texture
- Mesh quad;
-
- quad.vertexCount = 6;
-
- float w = (float)postproFbo.width;
- float h = (float)postproFbo.height;
-
- float quadPositions[6*3] = { w, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, h, 0.0f, 0.0f, h, 0.0f, w, h, 0.0f, w, 0.0f, 0.0f };
- float quadTexcoords[6*2] = { 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f };
- float quadNormals[6*3] = { 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f };
- unsigned char quadColors[6*4] = { 255 };
-
- quad.vertices = quadPositions;
- quad.texcoords = quadTexcoords;
- quad.normals = quadNormals;
- quad.colors = quadColors;
-
- postproQuad = rlglLoadModel(quad);
-
- // NOTE: postproFbo.colorTextureId must be assigned to postproQuad model shader
- }
-#endif
-}
-
-// Load a framebuffer object
-FBO rlglLoadFBO(int width, int height)
-{
- FBO fbo;
- fbo.id = 0;
- fbo.width = width;
- fbo.height = height;
-
-#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
- // Create the texture that will serve as the color attachment for the framebuffer
- glGenTextures(1, &fbo.colorTextureId);
- glBindTexture(GL_TEXTURE_2D, fbo.colorTextureId);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
- glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
- glBindTexture(GL_TEXTURE_2D, 0);
-
- // Create the renderbuffer that will serve as the depth attachment for the framebuffer.
- glGenRenderbuffers(1, &fbo.depthTextureId);
- glBindRenderbuffer(GL_RENDERBUFFER, fbo.depthTextureId);
- glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, width, height);
-
- // NOTE: We can also use a texture for depth buffer (GL_ARB_depth_texture/GL_OES_depth_texture extensions)
- // A renderbuffer is simpler than a texture and could offer better performance on embedded devices
-/*
- glGenTextures(1, &fbo.depthTextureId);
- glBindTexture(GL_TEXTURE_2D, fbo.depthTextureId);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
- glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, width, height, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, NULL);
- glBindTexture(GL_TEXTURE_2D, 0);
-*/
- // Create the framebuffer object
- glGenFramebuffers(1, &fbo.id);
- glBindFramebuffer(GL_FRAMEBUFFER, fbo.id);
-
- // Attach color texture and depth renderbuffer to FBO
- glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fbo.colorTextureId, 0);
- glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, fbo.depthTextureId);
-
- GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
-
- if (status != GL_FRAMEBUFFER_COMPLETE)
- {
- TraceLog(WARNING, "Framebuffer object could not be created...");
-
- switch(status)
- {
- case GL_FRAMEBUFFER_UNSUPPORTED: TraceLog(WARNING, "Framebuffer is unsupported"); break;
- case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: TraceLog(WARNING, "Framebuffer incomplete attachment"); break;
-#if defined(GRAPHICS_API_OPENGL_ES2)
- case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS: TraceLog(WARNING, "Framebuffer incomplete dimensions"); break;
-#endif
- case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: TraceLog(WARNING, "Framebuffer incomplete missing attachment"); break;
- default: break;
- }
-
- glDeleteTextures(1, &fbo.colorTextureId);
- glDeleteTextures(1, &fbo.depthTextureId);
- }
- else TraceLog(INFO, "[FBO ID %i] Framebuffer object created successfully", fbo);
-
- glBindFramebuffer(GL_FRAMEBUFFER, 0);
-#endif
-
- return fbo;
-}
-
-// Unload framebuffer object
-void rlglUnloadFBO(FBO fbo)
-{
-#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
- glDeleteFramebuffers(1, &fbo.id);
- glDeleteTextures(1, &fbo.colorTextureId);
- glDeleteTextures(1, &fbo.depthTextureId);
-
- TraceLog(INFO, "[FBO ID %i] Unloaded framebuffer object successfully", fbo.id);
-#endif
-}
-
// Vertex Buffer Object deinitialization (memory free)
void rlglClose(void)
{
#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
- // Unbind everything
- if (vaoSupported) glBindVertexArray(0);
- glDisableVertexAttribArray(0);
- glDisableVertexAttribArray(1);
- glDisableVertexAttribArray(2);
- glDisableVertexAttribArray(3);
- glBindBuffer(GL_ARRAY_BUFFER, 0);
- glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
-
- glUseProgram(0);
-
- // Delete VBOs
- glDeleteBuffers(1, &linesBuffer[0]);
- glDeleteBuffers(1, &linesBuffer[1]);
- glDeleteBuffers(1, &trianglesBuffer[0]);
- glDeleteBuffers(1, &trianglesBuffer[1]);
- glDeleteBuffers(1, &quadsBuffer[0]);
- glDeleteBuffers(1, &quadsBuffer[1]);
- glDeleteBuffers(1, &quadsBuffer[2]);
- glDeleteBuffers(1, &quadsBuffer[3]);
-
- if (vaoSupported)
- {
- // Delete VAOs
- glDeleteVertexArrays(1, &vaoLines);
- glDeleteVertexArrays(1, &vaoTriangles);
- glDeleteVertexArrays(1, &vaoQuads);
- }
-
- //glDetachShader(defaultShaderProgram, v);
- //glDetachShader(defaultShaderProgram, f);
- //glDeleteShader(v);
- //glDeleteShader(f);
- glDeleteProgram(defaultShader.id);
- glDeleteProgram(simpleShader.id);
-
- // Free vertex arrays memory
- free(lines.vertices);
- free(lines.colors);
-
- free(triangles.vertices);
- free(triangles.colors);
-
- free(quads.vertices);
- free(quads.texcoords);
- free(quads.colors);
- free(quads.indices);
-
- // Free GPU texture
+ UnloadDefaultShader();
+ UnloadDefaultBuffers();
+
+ // Delete default white texture
glDeleteTextures(1, &whiteTexture);
TraceLog(INFO, "[TEX ID %i] Unloaded texture data (base white texture) from VRAM", whiteTexture);
- if (postproFbo.id != 0)
- {
- rlglUnloadFBO(postproFbo);
-
- // Unload postpro quad model data
-#if defined(GRAPHICS_API_OPENGL_11)
- free(postproQuad.mesh.vertices);
- free(postproQuad.mesh.texcoords);
- free(postproQuad.mesh.normals);
-#endif
-
- rlDeleteBuffers(postproQuad.mesh.vboId[0]);
- rlDeleteBuffers(postproQuad.mesh.vboId[1]);
- rlDeleteBuffers(postproQuad.mesh.vboId[2]);
-
- rlDeleteVertexArrays(postproQuad.mesh.vaoId);
-
- TraceLog(INFO, "Unloaded postprocessing data");
- }
-
free(draws);
#endif
}
@@ -1261,333 +1060,22 @@ void rlglClose(void)
void rlglDraw(void)
{
#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
- UpdateBuffers();
-
- if ((lines.vCounter > 0) || (triangles.vCounter > 0) || (quads.vCounter > 0))
- {
- glUseProgram(currentShader.id);
-
- Matrix matMVP = MatrixMultiply(modelview, projection); // Create modelview-projection matrix
-
- glUniformMatrix4fv(currentShader.mvpLoc, 1, false, MatrixToFloat(matMVP));
- glUniform1i(currentShader.mapDiffuseLoc, 0);
- }
-
- // NOTE: We draw in this order: lines, triangles, quads
-
- if (lines.vCounter > 0)
- {
- glBindTexture(GL_TEXTURE_2D, whiteTexture);
-
- if (vaoSupported)
- {
- glBindVertexArray(vaoLines);
- }
- else
- {
- glBindBuffer(GL_ARRAY_BUFFER, linesBuffer[0]);
- glVertexAttribPointer(currentShader.vertexLoc, 3, GL_FLOAT, 0, 0, 0);
- glEnableVertexAttribArray(currentShader.vertexLoc);
-
- if (currentShader.colorLoc != -1)
- {
- glBindBuffer(GL_ARRAY_BUFFER, linesBuffer[1]);
- glVertexAttribPointer(currentShader.colorLoc, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0);
- glEnableVertexAttribArray(currentShader.colorLoc);
- }
- }
-
- glDrawArrays(GL_LINES, 0, lines.vCounter);
-
- if (!vaoSupported) glBindBuffer(GL_ARRAY_BUFFER, 0);
- glBindTexture(GL_TEXTURE_2D, 0);
- }
-
- if (triangles.vCounter > 0)
- {
- glBindTexture(GL_TEXTURE_2D, whiteTexture);
-
- if (vaoSupported)
- {
- glBindVertexArray(vaoTriangles);
- }
- else
- {
- glBindBuffer(GL_ARRAY_BUFFER, trianglesBuffer[0]);
- glVertexAttribPointer(currentShader.vertexLoc, 3, GL_FLOAT, 0, 0, 0);
- glEnableVertexAttribArray(currentShader.vertexLoc);
-
- if (currentShader.colorLoc != -1)
- {
- glBindBuffer(GL_ARRAY_BUFFER, trianglesBuffer[1]);
- glVertexAttribPointer(currentShader.colorLoc, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0);
- glEnableVertexAttribArray(currentShader.colorLoc);
- }
- }
-
- glDrawArrays(GL_TRIANGLES, 0, triangles.vCounter);
-
- if (!vaoSupported) glBindBuffer(GL_ARRAY_BUFFER, 0);
- glBindTexture(GL_TEXTURE_2D, 0);
- }
-
- if (quads.vCounter > 0)
- {
- int quadsCount = 0;
- int numIndicesToProcess = 0;
- int indicesOffset = 0;
-
- if (vaoSupported)
- {
- glBindVertexArray(vaoQuads);
- }
- else
- {
- // Enable vertex attributes
- glBindBuffer(GL_ARRAY_BUFFER, quadsBuffer[0]);
- glVertexAttribPointer(currentShader.vertexLoc, 3, GL_FLOAT, 0, 0, 0);
- glEnableVertexAttribArray(currentShader.vertexLoc);
-
- glBindBuffer(GL_ARRAY_BUFFER, quadsBuffer[1]);
- glVertexAttribPointer(currentShader.texcoordLoc, 2, GL_FLOAT, 0, 0, 0);
- glEnableVertexAttribArray(currentShader.texcoordLoc);
-
- if (currentShader.colorLoc != -1)
- {
- glBindBuffer(GL_ARRAY_BUFFER, quadsBuffer[2]);
- glVertexAttribPointer(currentShader.colorLoc, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0);
- glEnableVertexAttribArray(currentShader.colorLoc);
- }
-
- glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, quadsBuffer[3]);
- }
-
- //TraceLog(DEBUG, "Draws required per frame: %i", drawsCounter);
-
- for (int i = 0; i < drawsCounter; i++)
- {
- quadsCount = draws[i].vertexCount/4;
- numIndicesToProcess = quadsCount*6; // Get number of Quads * 6 index by Quad
-
- //TraceLog(DEBUG, "Quads to render: %i - Vertex Count: %i", quadsCount, draws[i].vertexCount);
-
- glBindTexture(GL_TEXTURE_2D, draws[i].textureId);
-
- // NOTE: The final parameter tells the GPU the offset in bytes from the start of the index buffer to the location of the first index to process
-#if defined(GRAPHICS_API_OPENGL_33)
- glDrawElements(GL_TRIANGLES, numIndicesToProcess, GL_UNSIGNED_INT, (GLvoid*) (sizeof(GLuint) * indicesOffset));
-#elif defined(GRAPHICS_API_OPENGL_ES2)
- glDrawElements(GL_TRIANGLES, numIndicesToProcess, GL_UNSIGNED_SHORT, (GLvoid*) (sizeof(GLushort) * indicesOffset));
-#endif
- //GLenum err;
- //if ((err = glGetError()) != GL_NO_ERROR) TraceLog(INFO, "OpenGL error: %i", (int)err); //GL_INVALID_ENUM!
-
- indicesOffset += draws[i].vertexCount/4*6;
- }
-
- if (!vaoSupported)
- {
- glBindBuffer(GL_ARRAY_BUFFER, 0);
- glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
- }
-
- glBindTexture(GL_TEXTURE_2D, 0); // Unbind textures
- }
-
- if (vaoSupported) glBindVertexArray(0); // Unbind VAO
-
- glUseProgram(0); // Unbind shader program
-
- // Reset draws counter
- drawsCounter = 1;
- draws[0].textureId = whiteTexture;
- draws[0].vertexCount = 0;
-
- // Reset vertex counters for next frame
- lines.vCounter = 0;
- lines.cCounter = 0;
-
- triangles.vCounter = 0;
- triangles.cCounter = 0;
-
- quads.vCounter = 0;
- quads.tcCounter = 0;
- quads.cCounter = 0;
-
- // Reset depth for next draw
- currentDepth = -1.0f;
-#endif
-}
-
-// Draw with postprocessing shader
-void rlglDrawPostpro(void)
-{
-#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
- glBindFramebuffer(GL_FRAMEBUFFER, 0);
-
- rlglDrawModel(postproQuad, (Vector3){ 0.0f, 0.0f, 0.0f }, (Vector3){ 0.0f, 0.0f, 0.0f }, 0.0f, (Vector3){1.0f, 1.0f, 1.0f}, (Color){ 255, 255, 255, 255 }, false);
-#endif
-}
-
-// Draw a 3d model
-// NOTE: Model transform can come within model struct
-void rlglDrawModel(Model model, Vector3 position, Vector3 rotationAxis, float rotationAngle, Vector3 scale, Color color, bool wires)
-{
-#if defined (GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33)
- // NOTE: glPolygonMode() not available on OpenGL ES
- if (wires) glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
-#endif
-
-#if defined(GRAPHICS_API_OPENGL_11)
- glEnable(GL_TEXTURE_2D);
- glBindTexture(GL_TEXTURE_2D, model.material.texDiffuse.id);
-
- // NOTE: On OpenGL 1.1 we use Vertex Arrays to draw model
- glEnableClientState(GL_VERTEX_ARRAY); // Enable vertex array
- glEnableClientState(GL_TEXTURE_COORD_ARRAY); // Enable texture coords array
- glEnableClientState(GL_NORMAL_ARRAY); // Enable normals array
-
- glVertexPointer(3, GL_FLOAT, 0, model.mesh.vertices); // Pointer to vertex coords array
- glTexCoordPointer(2, GL_FLOAT, 0, model.mesh.texcoords); // Pointer to texture coords array
- glNormalPointer(GL_FLOAT, 0, model.mesh.normals); // Pointer to normals array
- //glColorPointer(4, GL_UNSIGNED_BYTE, 0, model.mesh.colors); // Pointer to colors array (NOT USED)
-
- rlPushMatrix();
- rlTranslatef(position.x, position.y, position.z);
- rlScalef(scale.x, scale.y, scale.z);
- rlRotatef(rotationAngle, rotationAxis.x, rotationAxis.y, rotationAxis.z);
-
- rlColor4ub(color.r, color.g, color.b, color.a);
-
- glDrawArrays(GL_TRIANGLES, 0, model.mesh.vertexCount);
- rlPopMatrix();
-
- glDisableClientState(GL_VERTEX_ARRAY); // Disable vertex array
- glDisableClientState(GL_TEXTURE_COORD_ARRAY); // Disable texture coords array
- glDisableClientState(GL_NORMAL_ARRAY); // Disable normals array
-
- glDisable(GL_TEXTURE_2D);
- glBindTexture(GL_TEXTURE_2D, 0);
-#endif
-
-#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
- glUseProgram(model.material.shader.id);
-
- // At this point the modelview matrix just contains the view matrix (camera)
- // That's because Begin3dMode() sets it an no model-drawing function modifies it, all use rlPushMatrix() and rlPopMatrix()
- Matrix matView = modelview; // View matrix (camera)
- Matrix matProjection = projection; // Projection matrix (perspective)
-
- // Calculate transformation matrix from function parameters
- // Get transform matrix (rotation -> scale -> translation)
- Matrix matRotation = MatrixRotate(rotationAxis, rotationAngle*DEG2RAD);
- Matrix matScale = MatrixScale(scale.x, scale.y, scale.z);
- Matrix matTranslation = MatrixTranslate(position.x, position.y, position.z);
- Matrix matTransform = MatrixMultiply(MatrixMultiply(matScale, matRotation), matTranslation);
-
- // Combine model internal transformation matrix (model.transform) with matrix generated by function parameters (matTransform)
- Matrix matModel = MatrixMultiply(model.transform, matTransform); // Transform to world-space coordinates
-
- // Calculate model-view matrix combining matModel and matView
- Matrix matModelView = MatrixMultiply(matModel, matView); // Transform to camera-space coordinates
-
- // Calculate model-view-projection matrix (MVP)
- Matrix matMVP = MatrixMultiply(matModelView, matProjection); // Transform to screen-space coordinates
-
- // Send combined model-view-projection matrix to shader
- glUniformMatrix4fv(model.material.shader.mvpLoc, 1, false, MatrixToFloat(matMVP));
-
- // Apply color tinting to model
- // NOTE: Just update one uniform on fragment shader
- float vColor[4] = { (float)color.r/255, (float)color.g/255, (float)color.b/255, (float)color.a/255 };
- glUniform4fv(model.material.shader.tintColorLoc, 1, vColor);
-
- // Set shader textures (diffuse, normal, specular)
- glActiveTexture(GL_TEXTURE0);
- glBindTexture(GL_TEXTURE_2D, model.material.texDiffuse.id);
- glUniform1i(model.material.shader.mapDiffuseLoc, 0); // Texture fits in active texture unit 0
-
- if (model.material.texNormal.id != 0)
- {
- glActiveTexture(GL_TEXTURE1);
- glBindTexture(GL_TEXTURE_2D, model.material.texNormal.id);
- glUniform1i(model.material.shader.mapNormalLoc, 1); // Texture fits in active texture unit 1
- }
-
- if (model.material.texSpecular.id != 0)
- {
- glActiveTexture(GL_TEXTURE2);
- glBindTexture(GL_TEXTURE_2D, model.material.texSpecular.id);
- glUniform1i(model.material.shader.mapSpecularLoc, 2); // Texture fits in active texture unit 2
- }
-
- if (vaoSupported)
- {
- glBindVertexArray(model.mesh.vaoId);
- }
- else
- {
- // Bind model VBOs data
- glBindBuffer(GL_ARRAY_BUFFER, model.mesh.vboId[0]);
- glVertexAttribPointer(model.material.shader.vertexLoc, 3, GL_FLOAT, 0, 0, 0);
- glEnableVertexAttribArray(model.material.shader.vertexLoc);
-
- glBindBuffer(GL_ARRAY_BUFFER, model.mesh.vboId[1]);
- glVertexAttribPointer(model.material.shader.texcoordLoc, 2, GL_FLOAT, 0, 0, 0);
- glEnableVertexAttribArray(model.material.shader.texcoordLoc);
-
- // Add normals support
- if (model.material.shader.normalLoc != -1)
- {
- glBindBuffer(GL_ARRAY_BUFFER, model.mesh.vboId[2]);
- glVertexAttribPointer(model.material.shader.normalLoc, 3, GL_FLOAT, 0, 0, 0);
- glEnableVertexAttribArray(model.material.shader.normalLoc);
- }
- }
-
- // Draw call!
- glDrawArrays(GL_TRIANGLES, 0, model.mesh.vertexCount);
-
- //glDisableVertexAttribArray(model.shader.vertexLoc);
- //glDisableVertexAttribArray(model.shader.texcoordLoc);
- //if (model.shader.normalLoc != -1) glDisableVertexAttribArray(model.shader.normalLoc);
-
- if (model.material.texNormal.id != 0)
+/*
+ for (int i = 0; i < modelsCount; i++)
{
- glActiveTexture(GL_TEXTURE1);
- glBindTexture(GL_TEXTURE_2D, 0);
+ rlglDrawMesh(models[i]->mesh, models[i]->material, models[i]->transform);
}
-
- if (model.material.texSpecular.id != 0)
- {
- glActiveTexture(GL_TEXTURE2);
- glBindTexture(GL_TEXTURE_2D, 0);
- }
-
- glActiveTexture(GL_TEXTURE0); // Set shader active texture to default 0
- glBindTexture(GL_TEXTURE_2D, 0); // Unbind textures
-
- if (vaoSupported) glBindVertexArray(0); // Unbind VAO
- else glBindBuffer(GL_ARRAY_BUFFER, 0); // Unbind VBOs
-
- glUseProgram(0); // Unbind shader program
-#endif
-
-#if defined (GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33)
- // NOTE: glPolygonMode() not available on OpenGL ES
- if (wires) glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
+*/
+ // NOTE: Default buffers always drawn at the end
+ UpdateDefaultBuffers();
+ DrawDefaultBuffers();
#endif
}
// Initialize Graphics Device (OpenGL stuff)
// NOTE: Stores global variables screenWidth and screenHeight
void rlglInitGraphics(int offsetX, int offsetY, int width, int height)
-{
- // Save screen size data (global vars), required on postpro quad
- // NOTE: Size represents render size, it could differ from screen size!
- screenWidth = width;
- screenHeight = height;
-
+{
// NOTE: Required! viewport must be recalculated if screen resized!
glViewport(offsetX/2, offsetY/2, width - offsetX, height - offsetY); // Set viewport width and height
@@ -1833,6 +1321,102 @@ unsigned int rlglLoadTexture(void *data, int width, int height, int textureForma
return id;
}
+// Load a texture to be used for rendering (fbo with color and depth attachments)
+RenderTexture2D rlglLoadRenderTexture(int width, int height)
+{
+ RenderTexture2D target;
+
+ target.id = 0;
+
+ target.texture.id = 0;
+ target.texture.width = width;
+ target.texture.height = height;
+ target.texture.format = UNCOMPRESSED_R8G8B8;
+ target.texture.mipmaps = 1;
+
+ target.depth.id = 0;
+ target.depth.width = width;
+ target.depth.height = height;
+ target.depth.format = 19; //DEPTH_COMPONENT_24BIT
+ target.depth.mipmaps = 1;
+
+#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
+ // Create the texture that will serve as the color attachment for the framebuffer
+ glGenTextures(1, &target.texture.id);
+ glBindTexture(GL_TEXTURE_2D, target.texture.id);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
+ glBindTexture(GL_TEXTURE_2D, 0);
+
+#if defined(GRAPHICS_API_OPENGL_33)
+ #define USE_DEPTH_TEXTURE
+#else
+ #define USE_DEPTH_RENDERBUFFER
+#endif
+
+#if defined(USE_DEPTH_RENDERBUFFER)
+ // Create the renderbuffer that will serve as the depth attachment for the framebuffer.
+ glGenRenderbuffers(1, &target.depth.id);
+ glBindRenderbuffer(GL_RENDERBUFFER, target.depth.id);
+ glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, width, height); // GL_DEPTH_COMPONENT24 not supported on Android
+#elif defined(USE_DEPTH_TEXTURE)
+ // NOTE: We can also use a texture for depth buffer (GL_ARB_depth_texture/GL_OES_depth_texture extension required)
+ // A renderbuffer is simpler than a texture and could offer better performance on embedded devices
+ glGenTextures(1, &target.depth.id);
+ glBindTexture(GL_TEXTURE_2D, target.depth.id);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT24, width, height, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, NULL);
+ glBindTexture(GL_TEXTURE_2D, 0);
+#endif
+
+ // Create the framebuffer object
+ glGenFramebuffers(1, &target.id);
+ glBindFramebuffer(GL_FRAMEBUFFER, target.id);
+
+ // Attach color texture and depth renderbuffer to FBO
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, target.texture.id, 0);
+#if defined(USE_DEPTH_RENDERBUFFER)
+ glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, target.depth.id);
+#elif defined(USE_DEPTH_TEXTURE)
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, target.depth.id, 0);
+#endif
+
+ GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
+
+ if (status != GL_FRAMEBUFFER_COMPLETE)
+ {
+ TraceLog(WARNING, "Framebuffer object could not be created...");
+
+ switch(status)
+ {
+ case GL_FRAMEBUFFER_UNSUPPORTED: TraceLog(WARNING, "Framebuffer is unsupported"); break;
+ case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: TraceLog(WARNING, "Framebuffer incomplete attachment"); break;
+#if defined(GRAPHICS_API_OPENGL_ES2)
+ case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS: TraceLog(WARNING, "Framebuffer incomplete dimensions"); break;
+#endif
+ case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: TraceLog(WARNING, "Framebuffer incomplete missing attachment"); break;
+ default: break;
+ }
+
+ glDeleteTextures(1, &target.texture.id);
+ glDeleteTextures(1, &target.depth.id);
+ glDeleteFramebuffers(1, &target.id);
+ }
+ else TraceLog(INFO, "[FBO ID %i] Framebuffer object created successfully", target.id);
+
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+#endif
+
+ return target;
+}
+
+// Update already loaded texture in GPU with new data
void rlglUpdateTexture(unsigned int id, int width, int height, int format, void *data)
{
glBindTexture(GL_TEXTURE_2D, id);
@@ -1907,9 +1491,11 @@ void rlglGenerateMipmaps(Texture2D texture)
TraceLog(WARNING, "[TEX ID %i] Mipmaps generated manually on CPU side", texture.id);
// NOTE: Once mipmaps have been generated and data has been uploaded to GPU VRAM, we can discard RAM data
- free(data):
+ free(data);
-#elif defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
+#endif
+
+#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
glGenerateMipmap(GL_TEXTURE_2D); // Generate mipmaps automatically
TraceLog(INFO, "[TEX ID %i] Mipmaps generated automatically", texture.id);
@@ -1922,87 +1508,398 @@ void rlglGenerateMipmaps(Texture2D texture)
glBindTexture(GL_TEXTURE_2D, 0);
}
-// Load vertex data into a VAO (if supported) and VBO
-Model rlglLoadModel(Mesh mesh)
+// Upload vertex data into a VAO (if supported) and VBO
+void rlglLoadMesh(Mesh *mesh, bool dynamic)
{
- Model model;
+ mesh->vaoId = 0; // Vertex Array Object
+ mesh->vboId[0] = 0; // Vertex positions VBO
+ mesh->vboId[1] = 0; // Vertex texcoords VBO
+ mesh->vboId[2] = 0; // Vertex normals VBO
+ mesh->vboId[3] = 0; // Vertex colors VBO
+ mesh->vboId[4] = 0; // Vertex tangents VBO
+ mesh->vboId[5] = 0; // Vertex texcoords2 VBO
+ mesh->vboId[6] = 0; // Vertex indices VBO
+
+ int drawHint = GL_STATIC_DRAW;
+ if (dynamic) drawHint = GL_DYNAMIC_DRAW;
+
+#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
+ GLuint vaoId = 0; // Vertex Array Objects (VAO)
+ GLuint vboId[7]; // Vertex Buffer Objects (VBOs)
+
+ if (vaoSupported)
+ {
+ // Initialize Quads VAO (Buffer A)
+ glGenVertexArrays(1, &vaoId);
+ glBindVertexArray(vaoId);
+ }
- model.mesh = mesh;
- model.mesh.vaoId = 0; // Vertex Array Object
- model.mesh.vboId[0] = 0; // Vertex positions VBO
- model.mesh.vboId[1] = 0; // Vertex texcoords VBO
- model.mesh.vboId[2] = 0; // Vertex normals VBO
+ // NOTE: Attributes must be uploaded considering default locations points
- model.transform = MatrixIdentity();
+ // Enable vertex attributes: position (shader-location = 0)
+ glGenBuffers(1, &vboId[0]);
+ glBindBuffer(GL_ARRAY_BUFFER, vboId[0]);
+ glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*mesh->vertexCount, mesh->vertices, drawHint);
+ glVertexAttribPointer(0, 3, GL_FLOAT, 0, 0, 0);
+ glEnableVertexAttribArray(0);
-#if defined(GRAPHICS_API_OPENGL_11)
- model.material.texDiffuse.id = 0; // No texture required
- model.material.shader.id = 0; // No shader used
+ // Enable vertex attributes: texcoords (shader-location = 1)
+ glGenBuffers(1, &vboId[1]);
+ glBindBuffer(GL_ARRAY_BUFFER, vboId[1]);
+ glBufferData(GL_ARRAY_BUFFER, sizeof(float)*2*mesh->vertexCount, mesh->texcoords, drawHint);
+ glVertexAttribPointer(1, 2, GL_FLOAT, 0, 0, 0);
+ glEnableVertexAttribArray(1);
-#elif defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
- model.material.shader = simpleShader; // Default model shader
+ // Enable vertex attributes: normals (shader-location = 2)
+ if (mesh->normals != NULL)
+ {
+ glGenBuffers(1, &vboId[2]);
+ glBindBuffer(GL_ARRAY_BUFFER, vboId[2]);
+ glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*mesh->vertexCount, mesh->normals, drawHint);
+ glVertexAttribPointer(2, 3, GL_FLOAT, 0, 0, 0);
+ glEnableVertexAttribArray(2);
+ }
+ else
+ {
+ // Default color vertex attribute set to WHITE
+ glVertexAttrib3f(2, 1.0f, 1.0f, 1.0f);
+ glDisableVertexAttribArray(2);
+ }
- model.material.texDiffuse.id = whiteTexture; // Default whiteTexture
- model.material.texDiffuse.width = 1; // Default whiteTexture width
- model.material.texDiffuse.height = 1; // Default whiteTexture height
- model.material.texDiffuse.format = UNCOMPRESSED_R8G8B8A8; // Default whiteTexture format
+ // Default color vertex attribute (shader-location = 3)
+ if (mesh->colors != NULL)
+ {
+ glGenBuffers(1, &vboId[3]);
+ glBindBuffer(GL_ARRAY_BUFFER, vboId[3]);
+ glBufferData(GL_ARRAY_BUFFER, sizeof(unsigned char)*4*mesh->vertexCount, mesh->colors, drawHint);
+ glVertexAttribPointer(3, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0);
+ glEnableVertexAttribArray(3);
+ }
+ else
+ {
+ // Default color vertex attribute set to WHITE
+ glVertexAttrib4f(3, 1.0f, 1.0f, 1.0f, 1.0f);
+ glDisableVertexAttribArray(3);
+ }
- model.material.texNormal.id = 0; // By default, no normal texture
- model.material.texSpecular.id = 0; // By default, no specular texture
+ // Default tangent vertex attribute (shader-location = 4)
+ if (mesh->tangents != NULL)
+ {
+ glGenBuffers(1, &vboId[4]);
+ glBindBuffer(GL_ARRAY_BUFFER, vboId[4]);
+ glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*mesh->vertexCount, mesh->tangents, drawHint);
+ glVertexAttribPointer(4, 3, GL_FLOAT, 0, 0, 0);
+ glEnableVertexAttribArray(4);
+ }
+ else
+ {
+ // Default tangents vertex attribute
+ glVertexAttrib3f(4, 0.0f, 0.0f, 0.0f);
+ glDisableVertexAttribArray(4);
+ }
- // TODO: Fill default material properties (color, glossiness...)
+ // Default texcoord2 vertex attribute (shader-location = 5)
+ if (mesh->texcoords2 != NULL)
+ {
+ glGenBuffers(1, &vboId[5]);
+ glBindBuffer(GL_ARRAY_BUFFER, vboId[5]);
+ glBufferData(GL_ARRAY_BUFFER, sizeof(float)*2*mesh->vertexCount, mesh->texcoords2, drawHint);
+ glVertexAttribPointer(5, 2, GL_FLOAT, 0, 0, 0);
+ glEnableVertexAttribArray(5);
+ }
+ else
+ {
+ // Default tangents vertex attribute
+ glVertexAttrib2f(5, 0.0f, 0.0f);
+ glDisableVertexAttribArray(5);
+ }
- GLuint vaoModel = 0; // Vertex Array Objects (VAO)
- GLuint vertexBuffer[3]; // Vertex Buffer Objects (VBO)
+ if (mesh->indices != NULL)
+ {
+ glGenBuffers(1, &vboId[6]);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vboId[6]);
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(unsigned short)*mesh->triangleCount*3, mesh->indices, GL_STATIC_DRAW);
+ }
+
+
+ mesh->vboId[0] = vboId[0]; // Vertex position VBO
+ mesh->vboId[1] = vboId[1]; // Texcoords VBO
+ mesh->vboId[2] = vboId[2]; // Normals VBO
+ mesh->vboId[3] = vboId[3]; // Colors VBO
+ mesh->vboId[4] = vboId[4]; // Tangents VBO
+ mesh->vboId[5] = vboId[5]; // Texcoords2 VBO
+ mesh->vboId[6] = vboId[6]; // Indices VBO
if (vaoSupported)
{
- // Initialize Quads VAO (Buffer A)
- glGenVertexArrays(1, &vaoModel);
- glBindVertexArray(vaoModel);
+ if (vaoId > 0)
+ {
+ mesh->vaoId = vaoId;
+ TraceLog(INFO, "[VAO ID %i] Mesh uploaded successfully to VRAM (GPU)", mesh->vaoId);
+ }
+ else TraceLog(WARNING, "Mesh could not be uploaded to VRAM (GPU)");
+ }
+ else
+ {
+ TraceLog(INFO, "[VBOs] Mesh uploaded successfully to VRAM (GPU)");
}
+#endif
+}
- // Create buffers for our vertex data (positions, texcoords, normals)
- glGenBuffers(3, vertexBuffer);
+// Update vertex data on GPU (upload new data to one buffer)
+void rlglUpdateMesh(Mesh mesh, int buffer, int numVertex)
+{
+ // Activate mesh VAO
+ if (vaoSupported) glBindVertexArray(mesh.vaoId);
+
+ switch (buffer)
+ {
+ case 0: // Update vertices (vertex position)
+ {
+ glBindBuffer(GL_ARRAY_BUFFER, mesh.vboId[0]);
+ if (numVertex >= mesh.vertexCount) glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*numVertex, mesh.vertices, GL_DYNAMIC_DRAW);
+ else glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(float)*3*numVertex, mesh.vertices);
+
+ } break;
+ case 1: // Update texcoords (vertex texture coordinates)
+ {
+ glBindBuffer(GL_ARRAY_BUFFER, mesh.vboId[1]);
+ if (numVertex >= mesh.vertexCount) glBufferData(GL_ARRAY_BUFFER, sizeof(float)*2*numVertex, mesh.texcoords, GL_DYNAMIC_DRAW);
+ else glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(float)*2*numVertex, mesh.texcoords);
+
+ } break;
+ case 2: // Update normals (vertex normals)
+ {
+ glBindBuffer(GL_ARRAY_BUFFER, mesh.vboId[0]);
+ if (numVertex >= mesh.vertexCount) glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*numVertex, mesh.normals, GL_DYNAMIC_DRAW);
+ else glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(float)*3*numVertex, mesh.normals);
+
+ } break;
+ case 3: // Update colors (vertex colors)
+ {
+ glBindBuffer(GL_ARRAY_BUFFER, mesh.vboId[2]);
+ if (numVertex >= mesh.vertexCount) glBufferData(GL_ARRAY_BUFFER, sizeof(float)*4*numVertex, mesh.colors, GL_DYNAMIC_DRAW);
+ else glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(unsigned char)*4*numVertex, mesh.colors);
+
+ } break;
+ case 4: // Update tangents (vertex tangents)
+ {
+ glBindBuffer(GL_ARRAY_BUFFER, mesh.vboId[0]);
+ if (numVertex >= mesh.vertexCount) glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*numVertex, mesh.tangents, GL_DYNAMIC_DRAW);
+ else glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(float)*3*numVertex, mesh.tangents);
+ } break;
+ case 5: // Update texcoords2 (vertex second texture coordinates)
+ {
+ glBindBuffer(GL_ARRAY_BUFFER, mesh.vboId[1]);
+ if (numVertex >= mesh.vertexCount) glBufferData(GL_ARRAY_BUFFER, sizeof(float)*2*numVertex, mesh.texcoords2, GL_DYNAMIC_DRAW);
+ else glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(float)*2*numVertex, mesh.texcoords2);
+ } break;
+ default: break;
+ }
+
+ // Unbind the current VAO
+ if (vaoSupported) glBindVertexArray(0);
+
+ // Another option would be using buffer mapping...
+ //mesh.vertices = glMapBuffer(GL_ARRAY_BUFFER, GL_READ_WRITE);
+ // Now we can modify vertices
+ //glUnmapBuffer(GL_ARRAY_BUFFER);
+}
+
+// Draw a 3d mesh with material and transform
+void rlglDrawMesh(Mesh mesh, Material material, Matrix transform)
+{
+#if defined(GRAPHICS_API_OPENGL_11)
+ glEnable(GL_TEXTURE_2D);
+ glBindTexture(GL_TEXTURE_2D, material.texDiffuse.id);
+
+ // NOTE: On OpenGL 1.1 we use Vertex Arrays to draw model
+ glEnableClientState(GL_VERTEX_ARRAY); // Enable vertex array
+ glEnableClientState(GL_TEXTURE_COORD_ARRAY); // Enable texture coords array
+ if (mesh.normals != NULL) glEnableClientState(GL_NORMAL_ARRAY); // Enable normals array
+ if (mesh.colors != NULL) glEnableClientState(GL_COLOR_ARRAY); // Enable colors array
+
+ glVertexPointer(3, GL_FLOAT, 0, mesh.vertices); // Pointer to vertex coords array
+ glTexCoordPointer(2, GL_FLOAT, 0, mesh.texcoords); // Pointer to texture coords array
+ if (mesh.normals != NULL) glNormalPointer(GL_FLOAT, 0, mesh.normals); // Pointer to normals array
+ if (mesh.colors != NULL) glColorPointer(4, GL_UNSIGNED_BYTE, 0, mesh.colors); // Pointer to colors array
+
+ rlPushMatrix();
+ rlMultMatrixf(MatrixToFloat(transform));
+ rlColor4ub(material.colDiffuse.r, material.colDiffuse.g, material.colDiffuse.b, material.colDiffuse.a);
+
+ if (mesh.indices != NULL) glDrawElements(GL_TRIANGLES, mesh.triangleCount*3, GL_UNSIGNED_SHORT, mesh.indices);
+ else glDrawArrays(GL_TRIANGLES, 0, mesh.vertexCount);
+ rlPopMatrix();
+
+ glDisableClientState(GL_VERTEX_ARRAY); // Disable vertex array
+ glDisableClientState(GL_TEXTURE_COORD_ARRAY); // Disable texture coords array
+ if (mesh.normals != NULL) glDisableClientState(GL_NORMAL_ARRAY); // Disable normals array
+ if (mesh.colors != NULL) glDisableClientState(GL_NORMAL_ARRAY); // Disable colors array
- // Enable vertex attributes: position
- glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer[0]);
- glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*mesh.vertexCount, mesh.vertices, GL_STATIC_DRAW);
- glVertexAttribPointer(model.material.shader.vertexLoc, 3, GL_FLOAT, 0, 0, 0);
- glEnableVertexAttribArray(model.material.shader.vertexLoc);
+ glDisable(GL_TEXTURE_2D);
+ glBindTexture(GL_TEXTURE_2D, 0);
+#endif
+
+#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
+ glUseProgram(material.shader.id);
+
+ // At this point the modelview matrix just contains the view matrix (camera)
+ // That's because Begin3dMode() sets it an no model-drawing function modifies it, all use rlPushMatrix() and rlPopMatrix()
+ Matrix matView = modelview; // View matrix (camera)
+ Matrix matProjection = projection; // Projection matrix (perspective)
- // Enable vertex attributes: texcoords
- glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer[1]);
- glBufferData(GL_ARRAY_BUFFER, sizeof(float)*2*mesh.vertexCount, mesh.texcoords, GL_STATIC_DRAW);
- glVertexAttribPointer(model.material.shader.texcoordLoc, 2, GL_FLOAT, 0, 0, 0);
- glEnableVertexAttribArray(model.material.shader.texcoordLoc);
+ // Calculate model-view matrix combining matModel and matView
+ Matrix matModelView = MatrixMultiply(transform, matView); // Transform to camera-space coordinates
- // Enable vertex attributes: normals
- glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer[2]);
- glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*mesh.vertexCount, mesh.normals, GL_STATIC_DRAW);
- glVertexAttribPointer(model.material.shader.normalLoc, 3, GL_FLOAT, 0, 0, 0);
- glEnableVertexAttribArray(model.material.shader.normalLoc);
+ // Calculate model-view-projection matrix (MVP)
+ Matrix matMVP = MatrixMultiply(matModelView, matProjection); // Transform to screen-space coordinates
- model.mesh.vboId[0] = vertexBuffer[0]; // Vertex position VBO
- model.mesh.vboId[1] = vertexBuffer[1]; // Texcoords VBO
- model.mesh.vboId[2] = vertexBuffer[2]; // Normals VBO
+ // Send combined model-view-projection matrix to shader
+ glUniformMatrix4fv(material.shader.mvpLoc, 1, false, MatrixToFloat(matMVP));
+
+ // Setup shader uniforms for material related data
+ // TODO: Check if using standard shader to get location points
+
+ // Upload to shader material.colDiffuse
+ float vColorDiffuse[4] = { (float)material.colDiffuse.r/255, (float)material.colDiffuse.g/255, (float)material.colDiffuse.b/255, (float)material.colDiffuse.a/255 };
+ glUniform4fv(material.shader.tintColorLoc, 1, vColorDiffuse);
+
+ // TODO: Upload to shader material.colAmbient
+ // glUniform4f(???, (float)material.colAmbient.r/255, (float)material.colAmbient.g/255, (float)material.colAmbient.b/255, (float)material.colAmbient.a/255);
+
+ // TODO: Upload to shader material.colSpecular
+ // glUniform4f(???, (float)material.colSpecular.r/255, (float)material.colSpecular.g/255, (float)material.colSpecular.b/255, (float)material.colSpecular.a/255);
+
+ // Set shader textures (diffuse, normal, specular)
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_2D, material.texDiffuse.id);
+ glUniform1i(material.shader.mapDiffuseLoc, 0); // Texture fits in active texture unit 0
+
+ if ((material.texNormal.id != 0) && (material.shader.mapNormalLoc != -1))
+ {
+ glActiveTexture(GL_TEXTURE1);
+ glBindTexture(GL_TEXTURE_2D, material.texNormal.id);
+ glUniform1i(material.shader.mapNormalLoc, 1); // Texture fits in active texture unit 1
+
+ // TODO: Upload to shader normalDepth
+ //glUniform1f(???, material.normalDepth);
+ }
+
+ if ((material.texSpecular.id != 0) && (material.shader.mapSpecularLoc != -1))
+ {
+ glActiveTexture(GL_TEXTURE2);
+ glBindTexture(GL_TEXTURE_2D, material.texSpecular.id);
+ glUniform1i(material.shader.mapSpecularLoc, 2); // Texture fits in active texture unit 2
+
+ // TODO: Upload to shader glossiness
+ //glUniform1f(???, material.glossiness);
+ }
+
+ // Setup shader uniforms for lights
+ SetShaderLights(material.shader);
if (vaoSupported)
{
- if (vaoModel > 0)
+ glBindVertexArray(mesh.vaoId);
+ }
+ else
+ {
+ // Bind mesh VBO data: vertex position (shader-location = 0)
+ glBindBuffer(GL_ARRAY_BUFFER, mesh.vboId[0]);
+ glVertexAttribPointer(material.shader.vertexLoc, 3, GL_FLOAT, 0, 0, 0);
+ glEnableVertexAttribArray(material.shader.vertexLoc);
+
+ // Bind mesh VBO data: vertex texcoords (shader-location = 1)
+ glBindBuffer(GL_ARRAY_BUFFER, mesh.vboId[1]);
+ glVertexAttribPointer(material.shader.texcoordLoc, 2, GL_FLOAT, 0, 0, 0);
+ glEnableVertexAttribArray(material.shader.texcoordLoc);
+
+ // Bind mesh VBO data: vertex normals (shader-location = 2, if available)
+ if (material.shader.normalLoc != -1)
+ {
+ glBindBuffer(GL_ARRAY_BUFFER, mesh.vboId[2]);
+ glVertexAttribPointer(material.shader.normalLoc, 3, GL_FLOAT, 0, 0, 0);
+ glEnableVertexAttribArray(material.shader.normalLoc);
+ }
+
+ // Bind mesh VBO data: vertex colors (shader-location = 3, if available) , tangents, texcoords2 (if available)
+ if (material.shader.colorLoc != -1)
+ {
+ glBindBuffer(GL_ARRAY_BUFFER, mesh.vboId[3]);
+ glVertexAttribPointer(material.shader.colorLoc, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0);
+ glEnableVertexAttribArray(material.shader.colorLoc);
+ }
+
+ // Bind mesh VBO data: vertex tangents (shader-location = 4, if available)
+ if (material.shader.tangentLoc != -1)
+ {
+ glBindBuffer(GL_ARRAY_BUFFER, mesh.vboId[4]);
+ glVertexAttribPointer(material.shader.tangentLoc, 3, GL_FLOAT, 0, 0, 0);
+ glEnableVertexAttribArray(material.shader.tangentLoc);
+ }
+
+ // Bind mesh VBO data: vertex texcoords2 (shader-location = 5, if available)
+ if (material.shader.texcoord2Loc != -1)
{
- model.mesh.vaoId = vaoModel;
- TraceLog(INFO, "[VAO ID %i] Model uploaded successfully to VRAM (GPU)", vaoModel);
+ glBindBuffer(GL_ARRAY_BUFFER, mesh.vboId[5]);
+ glVertexAttribPointer(material.shader.texcoord2Loc, 2, GL_FLOAT, 0, 0, 0);
+ glEnableVertexAttribArray(material.shader.texcoord2Loc);
}
- else TraceLog(WARNING, "Model could not be uploaded to VRAM (GPU)");
+
+ if (mesh.indices != NULL) glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, quads.vboId[3]);
+ }
+
+ // Draw call!
+ if (mesh.indices != NULL) glDrawElements(GL_TRIANGLES, mesh.triangleCount*3, GL_UNSIGNED_SHORT, 0); // Indexed vertices draw
+ else glDrawArrays(GL_TRIANGLES, 0, mesh.vertexCount);
+
+ if (material.texNormal.id != 0)
+ {
+ glActiveTexture(GL_TEXTURE1);
+ glBindTexture(GL_TEXTURE_2D, 0);
}
+
+ if (material.texSpecular.id != 0)
+ {
+ glActiveTexture(GL_TEXTURE2);
+ glBindTexture(GL_TEXTURE_2D, 0);
+ }
+
+ glActiveTexture(GL_TEXTURE0); // Set shader active texture to default 0
+ glBindTexture(GL_TEXTURE_2D, 0); // Unbind textures
+
+ if (vaoSupported) glBindVertexArray(0); // Unbind VAO
else
{
- TraceLog(INFO, "[VBO ID %i][VBO ID %i][VBO ID %i] Model uploaded successfully to VRAM (GPU)", model.mesh.vboId[0], model.mesh.vboId[1], model.mesh.vboId[2]);
+ glBindBuffer(GL_ARRAY_BUFFER, 0); // Unbind VBOs
+ if (mesh.indices != NULL) glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}
+
+ glUseProgram(0); // Unbind shader program
#endif
+}
+
+// Unload mesh data from CPU and GPU
+void rlglUnloadMesh(Mesh *mesh)
+{
+ if (mesh->vertices != NULL) free(mesh->vertices);
+ if (mesh->texcoords != NULL) free(mesh->texcoords);
+ if (mesh->normals != NULL) free(mesh->normals);
+ if (mesh->colors != NULL) free(mesh->colors);
+ if (mesh->tangents != NULL) free(mesh->tangents);
+ if (mesh->texcoords2 != NULL) free(mesh->texcoords2);
+ if (mesh->indices != NULL) free(mesh->indices);
- return model;
+ rlDeleteBuffers(mesh->vboId[0]); // vertex
+ rlDeleteBuffers(mesh->vboId[1]); // texcoords
+ rlDeleteBuffers(mesh->vboId[2]); // normals
+ rlDeleteBuffers(mesh->vboId[3]); // colors
+ rlDeleteBuffers(mesh->vboId[4]); // tangents
+ rlDeleteBuffers(mesh->vboId[5]); // texcoords2
+ rlDeleteBuffers(mesh->vboId[6]); // indices
+
+ rlDeleteVertexArrays(mesh->vaoId);
}
// Read screen pixel data (color buffer)
@@ -2089,8 +1986,9 @@ void *rlglReadTexturePixels(Texture2D texture)
#endif
#if defined(GRAPHICS_API_OPENGL_ES2)
- FBO fbo = rlglLoadFBO(texture.width, texture.height);
-
+
+ RenderTexture2D fbo = rlglLoadRenderTexture(texture.width, texture.height);
+
// NOTE: Two possible Options:
// 1 - Bind texture to color fbo attachment and glReadPixels()
// 2 - Create an fbo, activate it, render quad with texture, glReadPixels()
@@ -2111,7 +2009,7 @@ void *rlglReadTexturePixels(Texture2D texture)
glReadPixels(0, 0, texture.width, texture.height, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
// Re-attach internal FBO color texture before deleting it
- glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fbo.colorTextureId, 0);
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fbo.texture.id, 0);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
@@ -2135,7 +2033,7 @@ void *rlglReadTexturePixels(Texture2D texture)
Model quad;
//quad.mesh = GenMeshQuad(width, height);
quad.transform = MatrixIdentity();
- quad.shader = simpleShader;
+ quad.shader = defaultShader;
DrawModel(quad, (Vector3){ 0.0f, 0.0f, 0.0f }, 1.0f, WHITE);
@@ -2150,7 +2048,8 @@ void *rlglReadTexturePixels(Texture2D texture)
#endif // GET_TEXTURE_FBO_OPTION
// Clean up temporal fbo
- rlglUnloadFBO(fbo);
+ rlDeleteRenderTextures(fbo);
+
#endif
return pixels;
@@ -2162,164 +2061,52 @@ void *rlglReadTexturePixels(Texture2D texture)
// NOTE: Those functions are exposed directly to the user in raylib.h
//----------------------------------------------------------------------------------
+// Get default internal texture (white texture)
+Texture2D GetDefaultTexture(void)
+{
+ Texture2D texture;
+
+ texture.id = whiteTexture;
+ texture.width = 1;
+ texture.height = 1;
+ texture.mipmaps = 1;
+ texture.format = UNCOMPRESSED_R8G8B8A8;
+
+ return texture;
+}
+
// Load a custom shader and bind default locations
Shader LoadShader(char *vsFileName, char *fsFileName)
{
- Shader shader;
-
- shader.id = 0; // Default value in case of loading failure
+ Shader shader = { 0 };
#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
// Shaders loading from external text file
- char *vShaderStr = TextFileRead(vsFileName);
- char *fShaderStr = TextFileRead(fsFileName);
+ char *vShaderStr = ReadTextFile(vsFileName);
+ char *fShaderStr = ReadTextFile(fsFileName);
if ((vShaderStr != NULL) && (fShaderStr != NULL))
{
shader.id = LoadShaderProgram(vShaderStr, fShaderStr);
- if (shader.id != 0)
- {
- TraceLog(INFO, "[SHDR ID %i] Custom shader loaded successfully", shader.id);
-
- // Get handles to GLSL input attibute locations
- //-------------------------------------------------------------------
- shader.vertexLoc = glGetAttribLocation(shader.id, "vertexPosition");
- shader.texcoordLoc = glGetAttribLocation(shader.id, "vertexTexCoord");
- shader.normalLoc = glGetAttribLocation(shader.id, "vertexNormal");
- // NOTE: custom shader does not use colorLoc
- shader.colorLoc = -1;
-
- // Get handles to GLSL uniform locations (vertex shader)
- shader.mvpLoc = glGetUniformLocation(shader.id, "mvpMatrix");
-
- // Get handles to GLSL uniform locations (fragment shader)
- shader.tintColorLoc = glGetUniformLocation(shader.id, "fragTintColor");
- shader.mapDiffuseLoc = glGetUniformLocation(shader.id, "texture0");
- shader.mapNormalLoc = -1; // It can be set later
- shader.mapSpecularLoc = -1; // It can be set later
- //--------------------------------------------------------------------
- }
- else
- {
- TraceLog(WARNING, "Custom shader could not be loaded");
- shader = simpleShader;
- }
+ // After shader loading, we try to load default location names
+ if (shader.id != 0) LoadDefaultShaderLocations(&shader);
// Shader strings must be freed
free(vShaderStr);
free(fShaderStr);
}
- else
+
+ if (shader.id == 0)
{
TraceLog(WARNING, "Custom shader could not be loaded");
- shader = simpleShader;
+ shader = defaultShader;
}
#endif
return shader;
}
-// Load custom shader strings and return program id
-unsigned int LoadShaderProgram(char *vShaderStr, char *fShaderStr)
-{
- unsigned int program = 0;
-
-#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
- GLuint vertexShader;
- GLuint fragmentShader;
-
- vertexShader = glCreateShader(GL_VERTEX_SHADER);
- fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
-
- const char *pvs = vShaderStr;
- const char *pfs = fShaderStr;
-
- glShaderSource(vertexShader, 1, &pvs, NULL);
- glShaderSource(fragmentShader, 1, &pfs, NULL);
-
- GLint success = 0;
-
- glCompileShader(vertexShader);
-
- glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
-
- if (success != GL_TRUE)
- {
- TraceLog(WARNING, "[VSHDR ID %i] Failed to compile vertex shader...", vertexShader);
-
- int maxLength = 0;
- int length;
-
- glGetShaderiv(vertexShader, GL_INFO_LOG_LENGTH, &maxLength);
-
- char log[maxLength];
-
- glGetShaderInfoLog(vertexShader, maxLength, &length, log);
-
- TraceLog(INFO, "%s", log);
- }
- else TraceLog(INFO, "[VSHDR ID %i] Vertex shader compiled successfully", vertexShader);
-
- glCompileShader(fragmentShader);
-
- glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
-
- if (success != GL_TRUE)
- {
- TraceLog(WARNING, "[FSHDR ID %i] Failed to compile fragment shader...", fragmentShader);
-
- int maxLength = 0;
- int length;
-
- glGetShaderiv(fragmentShader, GL_INFO_LOG_LENGTH, &maxLength);
-
- char log[maxLength];
-
- glGetShaderInfoLog(fragmentShader, maxLength, &length, log);
-
- TraceLog(INFO, "%s", log);
- }
- else TraceLog(INFO, "[FSHDR ID %i] Fragment shader compiled successfully", fragmentShader);
-
- program = glCreateProgram();
-
- glAttachShader(program, vertexShader);
- glAttachShader(program, fragmentShader);
-
- glLinkProgram(program);
-
- // NOTE: All uniform variables are intitialised to 0 when a program links
-
- glGetProgramiv(program, GL_LINK_STATUS, &success);
-
- if (success == GL_FALSE)
- {
- TraceLog(WARNING, "[SHDR ID %i] Failed to link shader program...", program);
-
- int maxLength = 0;
- int length;
-
- glGetProgramiv(program, GL_INFO_LOG_LENGTH, &maxLength);
-
- char log[maxLength];
-
- glGetProgramInfoLog(program, maxLength, &length, log);
-
- TraceLog(INFO, "%s", log);
-
- glDeleteProgram(program);
-
- program = 0;
- }
- else TraceLog(INFO, "[SHDR ID %i] Shader program loaded successfully", program);
-
- glDeleteShader(vertexShader);
- glDeleteShader(fragmentShader);
-#endif
- return program;
-}
-
// Unload a custom shader from memory
void UnloadShader(Shader shader)
{
@@ -2342,84 +2129,22 @@ void SetCustomShader(Shader shader)
#endif
}
-// Set postprocessing shader
-void SetPostproShader(Shader shader)
-{
-#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
- if (!enabledPostpro)
- {
- enabledPostpro = true;
- rlglInitPostpro(); // Lazy initialization on postprocessing usage
- }
-
- SetModelShader(&postproQuad, shader);
-
- Texture2D texture;
- texture.id = postproFbo.colorTextureId;
- texture.width = postproFbo.width;
- texture.height = postproFbo.height;
-
- postproQuad.material.texDiffuse = texture;
-
- //TraceLog(DEBUG, "Postproquad texture id: %i", postproQuad.texture.id);
- //TraceLog(DEBUG, "Postproquad shader diffuse map id: %i", postproQuad.shader.texDiffuseId);
- //TraceLog(DEBUG, "Shader diffuse map id: %i", shader.texDiffuseId);
-#elif defined(GRAPHICS_API_OPENGL_11)
- TraceLog(WARNING, "Shaders not supported on OpenGL 1.1");
-#endif
-}
-
// Set default shader to be used in batch draw
void SetDefaultShader(void)
{
#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
SetCustomShader(defaultShader);
-
- if (enabledPostpro)
- {
- SetPostproShader(defaultShader);
- enabledPostpro = false;
- }
#endif
}
-// Link shader to model
-void SetModelShader(Model *model, Shader shader)
+// Get default shader
+Shader GetDefaultShader(void)
{
#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
- model->material.shader = shader;
-
- if (vaoSupported) glBindVertexArray(model->mesh.vaoId);
-
- // Enable vertex attributes: position
- glBindBuffer(GL_ARRAY_BUFFER, model->mesh.vboId[0]);
- glEnableVertexAttribArray(shader.vertexLoc);
- glVertexAttribPointer(shader.vertexLoc, 3, GL_FLOAT, 0, 0, 0);
-
- // Enable vertex attributes: texcoords
- glBindBuffer(GL_ARRAY_BUFFER, model->mesh.vboId[1]);
- glEnableVertexAttribArray(shader.texcoordLoc);
- glVertexAttribPointer(shader.texcoordLoc, 2, GL_FLOAT, 0, 0, 0);
-
- // Enable vertex attributes: normals
- glBindBuffer(GL_ARRAY_BUFFER, model->mesh.vboId[2]);
- glEnableVertexAttribArray(shader.normalLoc);
- glVertexAttribPointer(shader.normalLoc, 3, GL_FLOAT, 0, 0, 0);
-
- if (vaoSupported) glBindVertexArray(0); // Unbind VAO
-
-#elif (GRAPHICS_API_OPENGL_11)
- TraceLog(WARNING, "Shaders not supported on OpenGL 1.1");
-#endif
-}
-
-// Check if postprocessing is enabled (used in module: core)
-bool IsPosproShaderEnabled(void)
-{
-#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
- return enabledPostpro;
-#elif defined(GRAPHICS_API_OPENGL_11)
- return false;
+ return defaultShader;
+#else
+ Shader shader = { 0 };
+ return shader;
#endif
}
@@ -2499,17 +2224,54 @@ void SetBlendMode(int mode)
}
}
-#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
-void PrintProjectionMatrix(void)
+// Create a new light, initialize it and add to pool
+// TODO: Review creation parameters (only generic ones)
+Light CreateLight(int type, Vector3 position, Color diffuse)
{
- PrintMatrix(projection);
+ // Allocate dynamic memory
+ Light light = (Light)malloc(sizeof(LightData));
+
+ // Initialize light values with generic values
+ light->id = lightsCount;
+ light->type = type;
+ light->enabled = true;
+
+ light->position = position;
+ light->direction = (Vector3){ 0.0f, 0.0f, 0.0f };
+ light->intensity = 1.0f;
+ light->diffuse = diffuse;
+ light->specular = WHITE;
+
+ // Add new light to the array
+ lights[lightsCount] = light;
+
+ // Increase enabled lights count
+ lightsCount++;
+
+ return light;
}
-void PrintModelviewMatrix(void)
+// Destroy a light and take it out of the list
+void DestroyLight(Light light)
{
- PrintMatrix(modelview);
+ // Free dynamic memory allocation
+ free(lights[light->id]);
+
+ // Remove *obj from the pointers array
+ for (int i = light->id; i < lightsCount; i++)
+ {
+ // Resort all the following pointers of the array
+ if ((i + 1) < lightsCount)
+ {
+ lights[i] = lights[i + 1];
+ lights[i]->id = lights[i + 1]->id;
+ }
+ else free(lights[i]);
+ }
+
+ // Decrease enabled physic objects count
+ lightsCount--;
}
-#endif
//----------------------------------------------------------------------------------
// Module specific Functions Definition
@@ -2552,7 +2314,118 @@ static void LoadCompressedTexture(unsigned char *data, int width, int height, in
}
}
-// Load Shader (Vertex and Fragment)
+// Load custom shader strings and return program id
+static unsigned int LoadShaderProgram(char *vShaderStr, char *fShaderStr)
+{
+ unsigned int program = 0;
+
+#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
+ GLuint vertexShader;
+ GLuint fragmentShader;
+
+ vertexShader = glCreateShader(GL_VERTEX_SHADER);
+ fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
+
+ const char *pvs = vShaderStr;
+ const char *pfs = fShaderStr;
+
+ glShaderSource(vertexShader, 1, &pvs, NULL);
+ glShaderSource(fragmentShader, 1, &pfs, NULL);
+
+ GLint success = 0;
+
+ glCompileShader(vertexShader);
+
+ glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
+
+ if (success != GL_TRUE)
+ {
+ TraceLog(WARNING, "[VSHDR ID %i] Failed to compile vertex shader...", vertexShader);
+
+ int maxLength = 0;
+ int length;
+
+ glGetShaderiv(vertexShader, GL_INFO_LOG_LENGTH, &maxLength);
+
+ char log[maxLength];
+
+ glGetShaderInfoLog(vertexShader, maxLength, &length, log);
+
+ TraceLog(INFO, "%s", log);
+ }
+ else TraceLog(INFO, "[VSHDR ID %i] Vertex shader compiled successfully", vertexShader);
+
+ glCompileShader(fragmentShader);
+
+ glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
+
+ if (success != GL_TRUE)
+ {
+ TraceLog(WARNING, "[FSHDR ID %i] Failed to compile fragment shader...", fragmentShader);
+
+ int maxLength = 0;
+ int length;
+
+ glGetShaderiv(fragmentShader, GL_INFO_LOG_LENGTH, &maxLength);
+
+ char log[maxLength];
+
+ glGetShaderInfoLog(fragmentShader, maxLength, &length, log);
+
+ TraceLog(INFO, "%s", log);
+ }
+ else TraceLog(INFO, "[FSHDR ID %i] Fragment shader compiled successfully", fragmentShader);
+
+ program = glCreateProgram();
+
+ glAttachShader(program, vertexShader);
+ glAttachShader(program, fragmentShader);
+
+ // NOTE: Default attribute shader locations must be binded before linking
+ glBindAttribLocation(program, 0, DEFAULT_ATTRIB_POSITION_NAME);
+ glBindAttribLocation(program, 1, DEFAULT_ATTRIB_TEXCOORD_NAME);
+ glBindAttribLocation(program, 2, DEFAULT_ATTRIB_NORMAL_NAME);
+ glBindAttribLocation(program, 3, DEFAULT_ATTRIB_COLOR_NAME);
+ glBindAttribLocation(program, 4, DEFAULT_ATTRIB_TANGENT_NAME);
+ glBindAttribLocation(program, 5, DEFAULT_ATTRIB_TEXCOORD2_NAME);
+
+ // NOTE: If some attrib name is no found on the shader, it locations becomes -1
+
+ glLinkProgram(program);
+
+ // NOTE: All uniform variables are intitialised to 0 when a program links
+
+ glGetProgramiv(program, GL_LINK_STATUS, &success);
+
+ if (success == GL_FALSE)
+ {
+ TraceLog(WARNING, "[SHDR ID %i] Failed to link shader program...", program);
+
+ int maxLength = 0;
+ int length;
+
+ glGetProgramiv(program, GL_INFO_LOG_LENGTH, &maxLength);
+
+ char log[maxLength];
+
+ glGetProgramInfoLog(program, maxLength, &length, log);
+
+ TraceLog(INFO, "%s", log);
+
+ glDeleteProgram(program);
+
+ program = 0;
+ }
+ else TraceLog(INFO, "[SHDR ID %i] Shader program loaded successfully", program);
+
+ glDeleteShader(vertexShader);
+ glDeleteShader(fragmentShader);
+#endif
+ return program;
+}
+
+
+// Load default shader (just vertex positioning and texture coloring)
// NOTE: This shader program is used for batch buffers (lines, triangles, quads)
static Shader LoadDefaultShader(void)
{
@@ -2565,20 +2438,20 @@ static Shader LoadDefaultShader(void)
"in vec2 vertexTexCoord; \n"
"in vec4 vertexColor; \n"
"out vec2 fragTexCoord; \n"
- "out vec4 fragTintColor; \n"
+ "out vec4 fragColor; \n"
#elif defined(GRAPHICS_API_OPENGL_ES2)
char vShaderStr[] = "#version 100 \n"
"attribute vec3 vertexPosition; \n"
"attribute vec2 vertexTexCoord; \n"
"attribute vec4 vertexColor; \n"
"varying vec2 fragTexCoord; \n"
- "varying vec4 fragTintColor; \n"
+ "varying vec4 fragColor; \n"
#endif
"uniform mat4 mvpMatrix; \n"
"void main() \n"
"{ \n"
" fragTexCoord = vertexTexCoord; \n"
- " fragTintColor = vertexColor; \n"
+ " fragColor = vertexColor; \n"
" gl_Position = mvpMatrix*vec4(vertexPosition, 1.0); \n"
"} \n";
@@ -2586,23 +2459,24 @@ static Shader LoadDefaultShader(void)
#if defined(GRAPHICS_API_OPENGL_33)
char fShaderStr[] = "#version 330 \n"
"in vec2 fragTexCoord; \n"
- "in vec4 fragTintColor; \n"
- "out vec4 fragColor; \n"
+ "in vec4 fragColor; \n"
+ "out vec4 finalColor; \n"
#elif defined(GRAPHICS_API_OPENGL_ES2)
char fShaderStr[] = "#version 100 \n"
"precision mediump float; \n" // precision required for OpenGL ES2 (WebGL)
"varying vec2 fragTexCoord; \n"
- "varying vec4 fragTintColor; \n"
+ "varying vec4 fragColor; \n"
#endif
"uniform sampler2D texture0; \n"
+ "uniform vec4 fragTintColor; \n"
"void main() \n"
"{ \n"
#if defined(GRAPHICS_API_OPENGL_33)
- " vec4 texelColor = texture(texture0, fragTexCoord); \n"
- " fragColor = texelColor*fragTintColor; \n"
+ " vec4 texelColor = texture(texture0, fragTexCoord); \n"
+ " finalColor = texelColor*fragTintColor*fragColor; \n"
#elif defined(GRAPHICS_API_OPENGL_ES2)
" vec4 texelColor = texture2D(texture0, fragTexCoord); \n" // NOTE: texture2D() is deprecated on OpenGL 3.3 and ES 3.0
- " gl_FragColor = texelColor*fragTintColor; \n"
+ " gl_FragColor = texelColor*fragTintColor*fragColor; \n"
#endif
"} \n";
@@ -2611,161 +2485,112 @@ static Shader LoadDefaultShader(void)
if (shader.id != 0) TraceLog(INFO, "[SHDR ID %i] Default shader loaded successfully", shader.id);
else TraceLog(WARNING, "[SHDR ID %i] Default shader could not be loaded", shader.id);
- // Get handles to GLSL input attibute locations
- //-------------------------------------------------------------------
- shader.vertexLoc = glGetAttribLocation(shader.id, "vertexPosition");
- shader.texcoordLoc = glGetAttribLocation(shader.id, "vertexTexCoord");
- shader.colorLoc = glGetAttribLocation(shader.id, "vertexColor");
- // NOTE: default shader does not use normalLoc
- shader.normalLoc = -1;
-
- // Get handles to GLSL uniform locations (vertex shader)
- shader.mvpLoc = glGetUniformLocation(shader.id, "mvpMatrix");
-
- // Get handles to GLSL uniform locations (fragment shader)
- shader.tintColorLoc = -1;
- shader.mapDiffuseLoc = glGetUniformLocation(shader.id, "texture0");
- shader.mapNormalLoc = -1; // It can be set later
- shader.mapSpecularLoc = -1; // It can be set later
- //--------------------------------------------------------------------
+ if (shader.id != 0) LoadDefaultShaderLocations(&shader);
return shader;
}
-// Load Simple Shader (Vertex and Fragment)
-// NOTE: This shader program is used to render models
-static Shader LoadSimpleShader(void)
+// Load standard shader
+// NOTE: This shader supports:
+// - Up to 3 different maps: diffuse, normal, specular
+// - Material properties: colDiffuse, colAmbient, colSpecular, glossiness, normalDepth
+// - Up to 8 lights: Point, Directional or Spot
+static Shader LoadStandardShader(void)
{
Shader shader;
+
+ char *vShaderStr;
+ char *fShaderStr;
+
+ // TODO: Implement standard uber-shader, supporting all features (GLSL 100 / GLSL 330)
+
+ // NOTE: Shader could be quite extensive so it could be implemented in external files (standard.vs/standard.fs)
+
+ shader.id = LoadShaderProgram(vShaderStr, fShaderStr);
- // Vertex shader directly defined, no external file required
-#if defined(GRAPHICS_API_OPENGL_33)
- char vShaderStr[] = "#version 330 \n"
- "in vec3 vertexPosition; \n"
- "in vec2 vertexTexCoord; \n"
- "in vec3 vertexNormal; \n"
- "out vec2 fragTexCoord; \n"
-#elif defined(GRAPHICS_API_OPENGL_ES2)
- char vShaderStr[] = "#version 100 \n"
- "attribute vec3 vertexPosition; \n"
- "attribute vec2 vertexTexCoord; \n"
- "attribute vec3 vertexNormal; \n"
- "varying vec2 fragTexCoord; \n"
-#endif
- "uniform mat4 mvpMatrix; \n"
- "void main() \n"
- "{ \n"
- " fragTexCoord = vertexTexCoord; \n"
- " gl_Position = mvpMatrix*vec4(vertexPosition, 1.0); \n"
- "} \n";
-
- // Fragment shader directly defined, no external file required
-#if defined(GRAPHICS_API_OPENGL_33)
- char fShaderStr[] = "#version 330 \n"
- "in vec2 fragTexCoord; \n"
- "out vec4 fragColor; \n"
-#elif defined(GRAPHICS_API_OPENGL_ES2)
- char fShaderStr[] = "#version 100 \n"
- "precision mediump float; \n" // precision required for OpenGL ES2 (WebGL)
- "varying vec2 fragTexCoord; \n"
-#endif
- "uniform sampler2D texture0; \n"
- "uniform vec4 fragTintColor; \n"
- "void main() \n"
- "{ \n"
-#if defined(GRAPHICS_API_OPENGL_33)
- " vec4 texelColor = texture(texture0, fragTexCoord); \n"
- " fragColor = texelColor*fragTintColor; \n"
-#elif defined(GRAPHICS_API_OPENGL_ES2)
- " vec4 texelColor = texture2D(texture0, fragTexCoord); \n"
- " gl_FragColor = texelColor*fragTintColor; \n"
-#endif
- "} \n";
+ if (shader.id != 0) TraceLog(INFO, "[SHDR ID %i] Standard shader loaded successfully", shader.id);
+ else TraceLog(WARNING, "[SHDR ID %i] Standard shader could not be loaded", shader.id);
- shader.id = LoadShaderProgram(vShaderStr, fShaderStr);
+ if (shader.id != 0) LoadDefaultShaderLocations(&shader); // TODO: Review locations fetching
- if (shader.id != 0) TraceLog(INFO, "[SHDR ID %i] Simple shader loaded successfully", shader.id);
- else TraceLog(WARNING, "[SHDR ID %i] Simple shader could not be loaded", shader.id);
+ return shader;
+}
+// Get location handlers to for shader attributes and uniforms
+// NOTE: If any location is not found, loc point becomes -1
+static void LoadDefaultShaderLocations(Shader *shader)
+{
+ // NOTE: Default shader attrib locations have been fixed before linking:
+ // vertex position location = 0
+ // vertex texcoord location = 1
+ // vertex normal location = 2
+ // vertex color location = 3
+ // vertex tangent location = 4
+ // vertex texcoord2 location = 5
+
// Get handles to GLSL input attibute locations
- //-------------------------------------------------------------------
- shader.vertexLoc = glGetAttribLocation(shader.id, "vertexPosition");
- shader.texcoordLoc = glGetAttribLocation(shader.id, "vertexTexCoord");
- shader.normalLoc = glGetAttribLocation(shader.id, "vertexNormal");
- // NOTE: simple shader does not use colorLoc
- shader.colorLoc = -1;
+ shader->vertexLoc = glGetAttribLocation(shader->id, DEFAULT_ATTRIB_POSITION_NAME);
+ shader->texcoordLoc = glGetAttribLocation(shader->id, DEFAULT_ATTRIB_TEXCOORD_NAME);
+ shader->normalLoc = glGetAttribLocation(shader->id, DEFAULT_ATTRIB_NORMAL_NAME);
+ shader->colorLoc = glGetAttribLocation(shader->id, DEFAULT_ATTRIB_COLOR_NAME);
+ shader->tangentLoc = glGetAttribLocation(shader->id, DEFAULT_ATTRIB_TANGENT_NAME);
+ shader->texcoord2Loc = glGetAttribLocation(shader->id, DEFAULT_ATTRIB_TEXCOORD2_NAME);
// Get handles to GLSL uniform locations (vertex shader)
- shader.mvpLoc = glGetUniformLocation(shader.id, "mvpMatrix");
+ shader->mvpLoc = glGetUniformLocation(shader->id, "mvpMatrix");
// Get handles to GLSL uniform locations (fragment shader)
- shader.tintColorLoc = glGetUniformLocation(shader.id, "fragTintColor");
- shader.mapDiffuseLoc = glGetUniformLocation(shader.id, "texture0");
- shader.mapNormalLoc = -1; // It can be set later
- shader.mapSpecularLoc = -1; // It can be set later
- //--------------------------------------------------------------------
-
- return shader;
+ shader->tintColorLoc = glGetUniformLocation(shader->id, "fragTintColor");
+ shader->mapDiffuseLoc = glGetUniformLocation(shader->id, "texture0");
+ shader->mapNormalLoc = glGetUniformLocation(shader->id, "texture1");
+ shader->mapSpecularLoc = glGetUniformLocation(shader->id, "texture2");
}
-// Read text file
-// NOTE: text chars array should be freed manually
-static char *TextFileRead(char *fileName)
+// Unload default shader
+static void UnloadDefaultShader(void)
{
- FILE *textFile;
- char *text = NULL;
-
- int count = 0;
-
- if (fileName != NULL)
- {
- textFile = fopen(fileName,"rt");
-
- if (textFile != NULL)
- {
- fseek(textFile, 0, SEEK_END);
- count = ftell(textFile);
- rewind(textFile);
-
- if (count > 0)
- {
- text = (char *)malloc(sizeof(char)*(count + 1));
- count = fread(text, sizeof(char), count, textFile);
- text[count] = '\0';
- }
-
- fclose(textFile);
- }
- else TraceLog(WARNING, "[%s] Text file could not be opened", fileName);
- }
+ glUseProgram(0);
- return text;
+ //glDetachShader(defaultShader, vertexShader);
+ //glDetachShader(defaultShader, fragmentShader);
+ //glDeleteShader(vertexShader); // Already deleted on shader compilation
+ //glDeleteShader(fragmentShader); // Already deleted on sahder compilation
+ glDeleteProgram(defaultShader.id);
}
-// Allocate and initialize float array buffers to store vertex data (lines, triangles, quads)
-static void InitializeBuffers(void)
+// Load default internal buffers (lines, triangles, quads)
+static void LoadDefaultBuffers(void)
{
- // Initialize lines arrays (vertex position and color data)
+ // [CPU] Allocate and initialize float array buffers to store vertex data (lines, triangles, quads)
+ //--------------------------------------------------------------------------------------------
+
+ // Lines - Initialize arrays (vertex position and color data)
lines.vertices = (float *)malloc(sizeof(float)*3*2*MAX_LINES_BATCH); // 3 float by vertex, 2 vertex by line
lines.colors = (unsigned char *)malloc(sizeof(unsigned char)*4*2*MAX_LINES_BATCH); // 4 float by color, 2 colors by line
+ lines.texcoords = NULL;
+ lines.indices = NULL;
for (int i = 0; i < (3*2*MAX_LINES_BATCH); i++) lines.vertices[i] = 0.0f;
for (int i = 0; i < (4*2*MAX_LINES_BATCH); i++) lines.colors[i] = 0;
lines.vCounter = 0;
lines.cCounter = 0;
+ lines.tcCounter = 0;
- // Initialize triangles arrays (vertex position and color data)
+ // Triangles - Initialize arrays (vertex position and color data)
triangles.vertices = (float *)malloc(sizeof(float)*3*3*MAX_TRIANGLES_BATCH); // 3 float by vertex, 3 vertex by triangle
triangles.colors = (unsigned char *)malloc(sizeof(unsigned char)*4*3*MAX_TRIANGLES_BATCH); // 4 float by color, 3 colors by triangle
+ triangles.texcoords = NULL;
+ triangles.indices = NULL;
for (int i = 0; i < (3*3*MAX_TRIANGLES_BATCH); i++) triangles.vertices[i] = 0.0f;
for (int i = 0; i < (4*3*MAX_TRIANGLES_BATCH); i++) triangles.colors[i] = 0;
triangles.vCounter = 0;
triangles.cCounter = 0;
+ triangles.tcCounter = 0;
- // Initialize quads arrays (vertex position, texcoord and color data... and indexes)
+ // Quads - Initialize arrays (vertex position, texcoord, color data and indexes)
quads.vertices = (float *)malloc(sizeof(float)*3*4*MAX_QUADS_BATCH); // 3 float by vertex, 4 vertex by quad
quads.texcoords = (float *)malloc(sizeof(float)*2*4*MAX_QUADS_BATCH); // 2 float by texcoord, 4 texcoord by quad
quads.colors = (unsigned char *)malloc(sizeof(unsigned char)*4*4*MAX_QUADS_BATCH); // 4 float by color, 4 colors by quad
@@ -2798,166 +2623,174 @@ static void InitializeBuffers(void)
quads.tcCounter = 0;
quads.cCounter = 0;
- TraceLog(INFO, "CPU buffers (lines, triangles, quads) initialized successfully");
-}
-
-// Initialize Vertex Array Objects (Contain VBO)
-// NOTE: lines, triangles and quads buffers use currentShader
-static void InitializeBuffersGPU(void)
-{
+ TraceLog(INFO, "[CPU] Default buffers initialized successfully (lines, triangles, quads)");
+ //--------------------------------------------------------------------------------------------
+
+ // [GPU] Upload vertex data and initialize VAOs/VBOs (lines, triangles, quads)
+ // NOTE: Default buffers are linked to use currentShader (defaultShader)
+ //--------------------------------------------------------------------------------------------
+
+ // Upload and link lines vertex buffers
if (vaoSupported)
{
// Initialize Lines VAO
- glGenVertexArrays(1, &vaoLines);
- glBindVertexArray(vaoLines);
+ glGenVertexArrays(1, &lines.vaoId);
+ glBindVertexArray(lines.vaoId);
}
- // Create buffers for our vertex data
- glGenBuffers(2, linesBuffer);
-
- // Lines - Vertex positions buffer binding and attributes enable
- glBindBuffer(GL_ARRAY_BUFFER, linesBuffer[0]);
+ // Lines - Vertex buffers binding and attributes enable
+ // Vertex position buffer (shader-location = 0)
+ glGenBuffers(2, &lines.vboId[0]);
+ glBindBuffer(GL_ARRAY_BUFFER, lines.vboId[0]);
glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*2*MAX_LINES_BATCH, lines.vertices, GL_DYNAMIC_DRAW);
glEnableVertexAttribArray(currentShader.vertexLoc);
glVertexAttribPointer(currentShader.vertexLoc, 3, GL_FLOAT, 0, 0, 0);
- // Lines - colors buffer
- glBindBuffer(GL_ARRAY_BUFFER, linesBuffer[1]);
+ // Vertex color buffer (shader-location = 3)
+ glGenBuffers(2, &lines.vboId[1]);
+ glBindBuffer(GL_ARRAY_BUFFER, lines.vboId[1]);
glBufferData(GL_ARRAY_BUFFER, sizeof(unsigned char)*4*2*MAX_LINES_BATCH, lines.colors, GL_DYNAMIC_DRAW);
glEnableVertexAttribArray(currentShader.colorLoc);
glVertexAttribPointer(currentShader.colorLoc, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0);
- if (vaoSupported) TraceLog(INFO, "[VAO ID %i] Lines VAO initialized successfully", vaoLines);
- else TraceLog(INFO, "[VBO ID %i][VBO ID %i] Lines VBOs initialized successfully", linesBuffer[0], linesBuffer[1]);
- //--------------------------------------------------------------
+ if (vaoSupported) TraceLog(INFO, "[VAO ID %i] Default buffers VAO initialized successfully (lines)", lines.vaoId);
+ else TraceLog(INFO, "[VBO ID %i][VBO ID %i] Default buffers VBOs initialized successfully (lines)", lines.vboId[0], lines.vboId[1]);
+ // Upload and link triangles vertex buffers
if (vaoSupported)
{
// Initialize Triangles VAO
- glGenVertexArrays(1, &vaoTriangles);
- glBindVertexArray(vaoTriangles);
+ glGenVertexArrays(1, &triangles.vaoId);
+ glBindVertexArray(triangles.vaoId);
}
- // Create buffers for our vertex data
- glGenBuffers(2, trianglesBuffer);
-
- // Enable vertex attributes
- glBindBuffer(GL_ARRAY_BUFFER, trianglesBuffer[0]);
+ // Triangles - Vertex buffers binding and attributes enable
+ // Vertex position buffer (shader-location = 0)
+ glGenBuffers(1, &triangles.vboId[0]);
+ glBindBuffer(GL_ARRAY_BUFFER, triangles.vboId[0]);
glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*3*MAX_TRIANGLES_BATCH, triangles.vertices, GL_DYNAMIC_DRAW);
glEnableVertexAttribArray(currentShader.vertexLoc);
glVertexAttribPointer(currentShader.vertexLoc, 3, GL_FLOAT, 0, 0, 0);
- glBindBuffer(GL_ARRAY_BUFFER, trianglesBuffer[1]);
+ // Vertex color buffer (shader-location = 3)
+ glGenBuffers(1, &triangles.vboId[1]);
+ glBindBuffer(GL_ARRAY_BUFFER, triangles.vboId[1]);
glBufferData(GL_ARRAY_BUFFER, sizeof(unsigned char)*4*3*MAX_TRIANGLES_BATCH, triangles.colors, GL_DYNAMIC_DRAW);
glEnableVertexAttribArray(currentShader.colorLoc);
glVertexAttribPointer(currentShader.colorLoc, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0);
- if (vaoSupported) TraceLog(INFO, "[VAO ID %i] Triangles VAO initialized successfully", vaoTriangles);
- else TraceLog(INFO, "[VBO ID %i][VBO ID %i] Triangles VBOs initialized successfully", trianglesBuffer[0], trianglesBuffer[1]);
- //--------------------------------------------------------------
+ if (vaoSupported) TraceLog(INFO, "[VAO ID %i] Default buffers VAO initialized successfully (triangles)", triangles.vaoId);
+ else TraceLog(INFO, "[VBO ID %i][VBO ID %i] Default buffers VBOs initialized successfully(triangles)", triangles.vboId[0], triangles.vboId[1]);
+ // Upload and link quads vertex buffers
if (vaoSupported)
{
// Initialize Quads VAO
- glGenVertexArrays(1, &vaoQuads);
- glBindVertexArray(vaoQuads);
+ glGenVertexArrays(1, &quads.vaoId);
+ glBindVertexArray(quads.vaoId);
}
- // Create buffers for our vertex data
- glGenBuffers(4, quadsBuffer);
-
- // Enable vertex attributes
- glBindBuffer(GL_ARRAY_BUFFER, quadsBuffer[0]);
+ // Quads - Vertex buffers binding and attributes enable
+ // Vertex position buffer (shader-location = 0)
+ glGenBuffers(1, &quads.vboId[0]);
+ glBindBuffer(GL_ARRAY_BUFFER, quads.vboId[0]);
glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*4*MAX_QUADS_BATCH, quads.vertices, GL_DYNAMIC_DRAW);
glEnableVertexAttribArray(currentShader.vertexLoc);
glVertexAttribPointer(currentShader.vertexLoc, 3, GL_FLOAT, 0, 0, 0);
- glBindBuffer(GL_ARRAY_BUFFER, quadsBuffer[1]);
+ // Vertex texcoord buffer (shader-location = 1)
+ glGenBuffers(1, &quads.vboId[1]);
+ glBindBuffer(GL_ARRAY_BUFFER, quads.vboId[1]);
glBufferData(GL_ARRAY_BUFFER, sizeof(float)*2*4*MAX_QUADS_BATCH, quads.texcoords, GL_DYNAMIC_DRAW);
glEnableVertexAttribArray(currentShader.texcoordLoc);
glVertexAttribPointer(currentShader.texcoordLoc, 2, GL_FLOAT, 0, 0, 0);
- glBindBuffer(GL_ARRAY_BUFFER, quadsBuffer[2]);
+ // Vertex color buffer (shader-location = 3)
+ glGenBuffers(1, &quads.vboId[2]);
+ glBindBuffer(GL_ARRAY_BUFFER, quads.vboId[2]);
glBufferData(GL_ARRAY_BUFFER, sizeof(unsigned char)*4*4*MAX_QUADS_BATCH, quads.colors, GL_DYNAMIC_DRAW);
glEnableVertexAttribArray(currentShader.colorLoc);
glVertexAttribPointer(currentShader.colorLoc, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0);
// Fill index buffer
- glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, quadsBuffer[3]);
+ glGenBuffers(1, &quads.vboId[3]);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, quads.vboId[3]);
#if defined(GRAPHICS_API_OPENGL_33)
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(int)*6*MAX_QUADS_BATCH, quads.indices, GL_STATIC_DRAW);
#elif defined(GRAPHICS_API_OPENGL_ES2)
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(short)*6*MAX_QUADS_BATCH, quads.indices, GL_STATIC_DRAW);
#endif
- if (vaoSupported) TraceLog(INFO, "[VAO ID %i] Quads VAO initialized successfully", vaoQuads);
- else TraceLog(INFO, "[VBO ID %i][VBO ID %i][VBO ID %i][VBO ID %i] Quads VBOs initialized successfully", quadsBuffer[0], quadsBuffer[1], quadsBuffer[2], quadsBuffer[3]);
+ if (vaoSupported) TraceLog(INFO, "[VAO ID %i] Default buffers VAO initialized successfully (quads)", quads.vaoId);
+ else TraceLog(INFO, "[VBO ID %i][VBO ID %i][VBO ID %i][VBO ID %i] Default buffers VBOs initialized successfully (quads)", quads.vboId[0], quads.vboId[1], quads.vboId[2], quads.vboId[3]);
// Unbind the current VAO
if (vaoSupported) glBindVertexArray(0);
+ //--------------------------------------------------------------------------------------------
}
-// Update VBOs with vertex array data
+// Update default internal buffers (VAOs/VBOs) with vertex array data
// NOTE: If there is not vertex data, buffers doesn't need to be updated (vertexCount > 0)
-// TODO: If no data changed on the CPU arrays --> No need to update GPU arrays (change flag required)
-static void UpdateBuffers(void)
+// TODO: If no data changed on the CPU arrays --> No need to re-update GPU arrays (change flag required)
+static void UpdateDefaultBuffers(void)
{
+ // Update lines vertex buffers
if (lines.vCounter > 0)
{
// Activate Lines VAO
- if (vaoSupported) glBindVertexArray(vaoLines);
+ if (vaoSupported) glBindVertexArray(lines.vaoId);
// Lines - vertex positions buffer
- glBindBuffer(GL_ARRAY_BUFFER, linesBuffer[0]);
+ glBindBuffer(GL_ARRAY_BUFFER, lines.vboId[0]);
//glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*2*MAX_LINES_BATCH, lines.vertices, GL_DYNAMIC_DRAW);
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(float)*3*lines.vCounter, lines.vertices); // target - offset (in bytes) - size (in bytes) - data pointer
// Lines - colors buffer
- glBindBuffer(GL_ARRAY_BUFFER, linesBuffer[1]);
+ glBindBuffer(GL_ARRAY_BUFFER, lines.vboId[1]);
//glBufferData(GL_ARRAY_BUFFER, sizeof(float)*4*2*MAX_LINES_BATCH, lines.colors, GL_DYNAMIC_DRAW);
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(unsigned char)*4*lines.cCounter, lines.colors);
}
- //--------------------------------------------------------------
+ // Update triangles vertex buffers
if (triangles.vCounter > 0)
{
// Activate Triangles VAO
- if (vaoSupported) glBindVertexArray(vaoTriangles);
+ if (vaoSupported) glBindVertexArray(triangles.vaoId);
// Triangles - vertex positions buffer
- glBindBuffer(GL_ARRAY_BUFFER, trianglesBuffer[0]);
+ glBindBuffer(GL_ARRAY_BUFFER, triangles.vboId[0]);
//glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*3*MAX_TRIANGLES_BATCH, triangles.vertices, GL_DYNAMIC_DRAW);
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(float)*3*triangles.vCounter, triangles.vertices);
// Triangles - colors buffer
- glBindBuffer(GL_ARRAY_BUFFER, trianglesBuffer[1]);
+ glBindBuffer(GL_ARRAY_BUFFER, triangles.vboId[1]);
//glBufferData(GL_ARRAY_BUFFER, sizeof(float)*4*3*MAX_TRIANGLES_BATCH, triangles.colors, GL_DYNAMIC_DRAW);
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(unsigned char)*4*triangles.cCounter, triangles.colors);
}
- //--------------------------------------------------------------
+ // Update quads vertex buffers
if (quads.vCounter > 0)
{
// Activate Quads VAO
- if (vaoSupported) glBindVertexArray(vaoQuads);
+ if (vaoSupported) glBindVertexArray(quads.vaoId);
// Quads - vertex positions buffer
- glBindBuffer(GL_ARRAY_BUFFER, quadsBuffer[0]);
+ glBindBuffer(GL_ARRAY_BUFFER, quads.vboId[0]);
//glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*4*MAX_QUADS_BATCH, quads.vertices, GL_DYNAMIC_DRAW);
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(float)*3*quads.vCounter, quads.vertices);
// Quads - texture coordinates buffer
- glBindBuffer(GL_ARRAY_BUFFER, quadsBuffer[1]);
+ glBindBuffer(GL_ARRAY_BUFFER, quads.vboId[1]);
//glBufferData(GL_ARRAY_BUFFER, sizeof(float)*2*4*MAX_QUADS_BATCH, quads.texcoords, GL_DYNAMIC_DRAW);
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(float)*2*quads.vCounter, quads.texcoords);
// Quads - colors buffer
- glBindBuffer(GL_ARRAY_BUFFER, quadsBuffer[2]);
+ glBindBuffer(GL_ARRAY_BUFFER, quads.vboId[2]);
//glBufferData(GL_ARRAY_BUFFER, sizeof(float)*4*4*MAX_QUADS_BATCH, quads.colors, GL_DYNAMIC_DRAW);
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(unsigned char)*4*quads.vCounter, quads.colors);
// Another option would be using buffer mapping...
- //triangles.vertices = glMapBuffer(GL_ARRAY_BUFFER, GL_READ_WRITE);
+ //quads.vertices = glMapBuffer(GL_ARRAY_BUFFER, GL_READ_WRITE);
// Now we can modify vertices
//glUnmapBuffer(GL_ARRAY_BUFFER);
}
@@ -2966,6 +2799,297 @@ static void UpdateBuffers(void)
// Unbind the current VAO
if (vaoSupported) glBindVertexArray(0);
}
+
+// Draw default internal buffers vertex data
+// NOTE: We draw in this order: lines, triangles, quads
+static void DrawDefaultBuffers(void)
+{
+ // Set current shader and upload current MVP matrix
+ if ((lines.vCounter > 0) || (triangles.vCounter > 0) || (quads.vCounter > 0))
+ {
+ glUseProgram(currentShader.id);
+
+ // Create modelview-projection matrix
+ Matrix matMVP = MatrixMultiply(modelview, projection);
+
+ glUniformMatrix4fv(currentShader.mvpLoc, 1, false, MatrixToFloat(matMVP));
+ glUniform1i(currentShader.mapDiffuseLoc, 0);
+ glUniform4f(currentShader.tintColorLoc, 1.0f, 1.0f, 1.0f, 1.0f);
+ }
+
+ // Draw lines buffers
+ if (lines.vCounter > 0)
+ {
+ glBindTexture(GL_TEXTURE_2D, whiteTexture);
+
+ if (vaoSupported)
+ {
+ glBindVertexArray(lines.vaoId);
+ }
+ else
+ {
+ // Bind vertex attrib: position (shader-location = 0)
+ glBindBuffer(GL_ARRAY_BUFFER, lines.vboId[0]);
+ glVertexAttribPointer(currentShader.vertexLoc, 3, GL_FLOAT, 0, 0, 0);
+ glEnableVertexAttribArray(currentShader.vertexLoc);
+
+ // Bind vertex attrib: color (shader-location = 3)
+ glBindBuffer(GL_ARRAY_BUFFER, lines.vboId[1]);
+ glVertexAttribPointer(currentShader.colorLoc, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0);
+ glEnableVertexAttribArray(currentShader.colorLoc);
+ }
+
+ glDrawArrays(GL_LINES, 0, lines.vCounter);
+
+ if (!vaoSupported) glBindBuffer(GL_ARRAY_BUFFER, 0);
+ glBindTexture(GL_TEXTURE_2D, 0);
+ }
+
+ // Draw triangles buffers
+ if (triangles.vCounter > 0)
+ {
+ glBindTexture(GL_TEXTURE_2D, whiteTexture);
+
+ if (vaoSupported)
+ {
+ glBindVertexArray(triangles.vaoId);
+ }
+ else
+ {
+ // Bind vertex attrib: position (shader-location = 0)
+ glBindBuffer(GL_ARRAY_BUFFER, triangles.vboId[0]);
+ glVertexAttribPointer(currentShader.vertexLoc, 3, GL_FLOAT, 0, 0, 0);
+ glEnableVertexAttribArray(currentShader.vertexLoc);
+
+ // Bind vertex attrib: color (shader-location = 3)
+ glBindBuffer(GL_ARRAY_BUFFER, triangles.vboId[1]);
+ glVertexAttribPointer(currentShader.colorLoc, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0);
+ glEnableVertexAttribArray(currentShader.colorLoc);
+ }
+
+ glDrawArrays(GL_TRIANGLES, 0, triangles.vCounter);
+
+ if (!vaoSupported) glBindBuffer(GL_ARRAY_BUFFER, 0);
+ glBindTexture(GL_TEXTURE_2D, 0);
+ }
+
+ // Draw quads buffers
+ if (quads.vCounter > 0)
+ {
+ int quadsCount = 0;
+ int numIndicesToProcess = 0;
+ int indicesOffset = 0;
+
+ if (vaoSupported)
+ {
+ glBindVertexArray(quads.vaoId);
+ }
+ else
+ {
+ // Bind vertex attrib: position (shader-location = 0)
+ glBindBuffer(GL_ARRAY_BUFFER, quads.vboId[0]);
+ glVertexAttribPointer(currentShader.vertexLoc, 3, GL_FLOAT, 0, 0, 0);
+ glEnableVertexAttribArray(currentShader.vertexLoc);
+
+ // Bind vertex attrib: texcoord (shader-location = 1)
+ glBindBuffer(GL_ARRAY_BUFFER, quads.vboId[1]);
+ glVertexAttribPointer(currentShader.texcoordLoc, 2, GL_FLOAT, 0, 0, 0);
+ glEnableVertexAttribArray(currentShader.texcoordLoc);
+
+ // Bind vertex attrib: color (shader-location = 3)
+ glBindBuffer(GL_ARRAY_BUFFER, quads.vboId[2]);
+ glVertexAttribPointer(currentShader.colorLoc, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0);
+ glEnableVertexAttribArray(currentShader.colorLoc);
+
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, quads.vboId[3]);
+ }
+
+ //TraceLog(DEBUG, "Draws required per frame: %i", drawsCounter);
+
+ for (int i = 0; i < drawsCounter; i++)
+ {
+ quadsCount = draws[i].vertexCount/4;
+ numIndicesToProcess = quadsCount*6; // Get number of Quads * 6 index by Quad
+
+ //TraceLog(DEBUG, "Quads to render: %i - Vertex Count: %i", quadsCount, draws[i].vertexCount);
+
+ glBindTexture(GL_TEXTURE_2D, draws[i].textureId);
+
+ // NOTE: The final parameter tells the GPU the offset in bytes from the start of the index buffer to the location of the first index to process
+#if defined(GRAPHICS_API_OPENGL_33)
+ glDrawElements(GL_TRIANGLES, numIndicesToProcess, GL_UNSIGNED_INT, (GLvoid *)(sizeof(GLuint)*indicesOffset));
+#elif defined(GRAPHICS_API_OPENGL_ES2)
+ glDrawElements(GL_TRIANGLES, numIndicesToProcess, GL_UNSIGNED_SHORT, (GLvoid *)(sizeof(GLushort)*indicesOffset));
+#endif
+ //GLenum err;
+ //if ((err = glGetError()) != GL_NO_ERROR) TraceLog(INFO, "OpenGL error: %i", (int)err); //GL_INVALID_ENUM!
+
+ indicesOffset += draws[i].vertexCount/4*6;
+ }
+
+ if (!vaoSupported)
+ {
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+ }
+
+ glBindTexture(GL_TEXTURE_2D, 0); // Unbind textures
+ }
+
+ if (vaoSupported) glBindVertexArray(0); // Unbind VAO
+
+ glUseProgram(0); // Unbind shader program
+
+ // Reset draws counter
+ drawsCounter = 1;
+ draws[0].textureId = whiteTexture;
+ draws[0].vertexCount = 0;
+
+ // Reset vertex counters for next frame
+ lines.vCounter = 0;
+ lines.cCounter = 0;
+ triangles.vCounter = 0;
+ triangles.cCounter = 0;
+ quads.vCounter = 0;
+ quads.tcCounter = 0;
+ quads.cCounter = 0;
+
+ // Reset depth for next draw
+ currentDepth = -1.0f;
+}
+
+// Unload default internal buffers vertex data from CPU and GPU
+static void UnloadDefaultBuffers(void)
+{
+ // Unbind everything
+ if (vaoSupported) glBindVertexArray(0);
+ glDisableVertexAttribArray(0);
+ glDisableVertexAttribArray(1);
+ glDisableVertexAttribArray(2);
+ glDisableVertexAttribArray(3);
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+
+ // Delete VBOs from GPU (VRAM)
+ glDeleteBuffers(1, &lines.vboId[0]);
+ glDeleteBuffers(1, &lines.vboId[1]);
+ glDeleteBuffers(1, &triangles.vboId[0]);
+ glDeleteBuffers(1, &triangles.vboId[1]);
+ glDeleteBuffers(1, &quads.vboId[0]);
+ glDeleteBuffers(1, &quads.vboId[1]);
+ glDeleteBuffers(1, &quads.vboId[2]);
+ glDeleteBuffers(1, &quads.vboId[3]);
+
+ if (vaoSupported)
+ {
+ // Delete VAOs from GPU (VRAM)
+ glDeleteVertexArrays(1, &lines.vaoId);
+ glDeleteVertexArrays(1, &triangles.vaoId);
+ glDeleteVertexArrays(1, &quads.vaoId);
+ }
+
+ // Free vertex arrays memory from CPU (RAM)
+ free(lines.vertices);
+ free(lines.colors);
+
+ free(triangles.vertices);
+ free(triangles.colors);
+
+ free(quads.vertices);
+ free(quads.texcoords);
+ free(quads.colors);
+ free(quads.indices);
+}
+
+// Sets shader uniform values for lights array
+// NOTE: It would be far easier with shader UBOs but are not supported on OpenGL ES 2.0f
+// TODO: Review memcpy() and parameters pass
+static void SetShaderLights(Shader shader)
+{
+ /*
+ // NOTE: Standard Shader must include the following data:
+
+ // Shader Light struct
+ struct Light {
+ vec3 position;
+ vec3 direction;
+
+ vec3 diffuse;
+ float intensity;
+ }
+
+ const int maxLights = 8;
+ uniform int lightsCount; // Number of lights
+ uniform Light lights[maxLights];
+ */
+
+ int locPoint;
+ char locName[32] = "lights[x].position\0";
+
+ glUseProgram(shader.id);
+
+ locPoint = glGetUniformLocation(shader.id, "lightsCount");
+ glUniform1i(locPoint, lightsCount);
+
+ for (int i = 0; i < lightsCount; i++)
+ {
+ locName[7] = '0' + i;
+
+ memcpy(&locName[10], "position\0", strlen("position\0"));
+ locPoint = glGetUniformLocation(shader.id, locName);
+ glUniform3f(locPoint, lights[i]->position.x, lights[i]->position.y, lights[i]->position.z);
+
+ memcpy(&locName[10], "direction\0", strlen("direction\0"));
+ locPoint = glGetUniformLocation(shader.id, locName);
+ glUniform3f(locPoint, lights[i]->direction.x, lights[i]->direction.y, lights[i]->direction.z);
+
+ memcpy(&locName[10], "diffuse\0", strlen("diffuse\0"));
+ locPoint = glGetUniformLocation(shader.id, locName);
+ glUniform4f(locPoint, (float)lights[i]->diffuse.r/255, (float)lights[i]->diffuse.g/255, (float)lights[i]->diffuse.b/255, (float)lights[i]->diffuse.a/255 );
+
+ memcpy(&locName[10], "intensity\0", strlen("intensity\0"));
+ locPoint = glGetUniformLocation(shader.id, locName);
+ glUniform1f(locPoint, lights[i]->intensity);
+
+ // TODO: Pass to the shader any other required data from LightData struct
+ }
+
+ glUseProgram(0);
+}
+
+// Read text data from file
+// NOTE: text chars array should be freed manually
+static char *ReadTextFile(const char *fileName)
+{
+ FILE *textFile;
+ char *text = NULL;
+
+ int count = 0;
+
+ if (fileName != NULL)
+ {
+ textFile = fopen(fileName,"rt");
+
+ if (textFile != NULL)
+ {
+ fseek(textFile, 0, SEEK_END);
+ count = ftell(textFile);
+ rewind(textFile);
+
+ if (count > 0)
+ {
+ text = (char *)malloc(sizeof(char)*(count + 1));
+ count = fread(text, sizeof(char), count, textFile);
+ text[count] = '\0';
+ }
+
+ fclose(textFile);
+ }
+ else TraceLog(WARNING, "[%s] Text file could not be opened", fileName);
+ }
+
+ return text;
+}
#endif //defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
#if defined(GRAPHICS_API_OPENGL_11)
@@ -3004,8 +3128,8 @@ static int GenerateMipmaps(unsigned char *data, int baseWidth, int baseHeight)
// Generate mipmaps
// NOTE: Every mipmap data is stored after data
- pixel *image = (pixel *)malloc(width*height*sizeof(pixel));
- pixel *mipmap = NULL;
+ Color *image = (Color *)malloc(width*height*sizeof(Color));
+ Color *mipmap = NULL;
int offset = 0;
int j = 0;
@@ -3053,15 +3177,15 @@ static int GenerateMipmaps(unsigned char *data, int baseWidth, int baseHeight)
}
// Manual mipmap generation (basic scaling algorithm)
-static pixel *GenNextMipmap(pixel *srcData, int srcWidth, int srcHeight)
+static Color *GenNextMipmap(Color *srcData, int srcWidth, int srcHeight)
{
int x2, y2;
- pixel prow, pcol;
+ Color prow, pcol;
int width = srcWidth/2;
int height = srcHeight/2;
- pixel *mipmap = (pixel *)malloc(width*height*sizeof(pixel));
+ Color *mipmap = (Color *)malloc(width*height*sizeof(Color));
// Scaling algorithm works perfectly (box-filter)
for (int y = 0; y < height; y++)
@@ -3096,7 +3220,6 @@ static pixel *GenNextMipmap(pixel *srcData, int srcWidth, int srcHeight)
#endif
#if defined(RLGL_STANDALONE)
-
// Output a trace log message
// NOTE: Expected msgType: (0)Info, (1)Error, (2)Warning
static void TraceLog(int msgType, const char *text, ...)
diff --git a/src/rlgl.h b/src/rlgl.h
index 1a5260eb..39941b33 100644
--- a/src/rlgl.h
+++ b/src/rlgl.h
@@ -137,37 +137,41 @@ typedef enum { OPENGL_11 = 1, OPENGL_33, OPENGL_ES_20 } GlVersion;
Vector3 max;
} BoundingBox;
- // Mesh with vertex data type
- // NOTE: If using OpenGL 1.1, data loaded in CPU; if OpenGL 3.3+ data loaded in GPU (vaoId)
+ // Vertex data definning a mesh
typedef struct Mesh {
- int vertexCount; // num vertices
- float *vertices; // vertex position (XYZ - 3 components per vertex)
- float *texcoords; // vertex texture coordinates (UV - 2 components per vertex)
- float *texcoords2; // vertex second texture coordinates (useful for lightmaps)
- float *normals; // vertex normals (XYZ - 3 components per vertex)
- float *tangents; // vertex tangents (XYZ - 3 components per vertex)
- unsigned char *colors; // vertex colors (RGBA - 4 components per vertex)
+ int vertexCount; // number of vertices stored in arrays
+ 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 *normals; // vertex normals (XYZ - 3 components per vertex) (shader-location = 2)
+ float *tangents; // vertex tangents (XYZ - 3 components per vertex) (shader-location = 4)
+ unsigned char *colors; // vertex colors (RGBA - 4 components per vertex) (shader-location = 3)
+ unsigned short *indices; // vertex indices (in case vertex data comes indexed)
+ int triangleCount; // number of triangles stored (indexed or not)
BoundingBox bounds; // mesh limits defined by min and max points
unsigned int vaoId; // OpenGL Vertex Array Object id
- unsigned int vboId[6]; // OpenGL Vertex Buffer Objects id (6 types of vertex data)
+ unsigned int vboId[7]; // OpenGL Vertex Buffer Objects id (7 types of vertex data)
} Mesh;
- // Shader type
+ // Shader type (generic shader)
typedef struct Shader {
- unsigned int id; // Shader program id
-
- // Variable attributes
- int vertexLoc; // Vertex attribute location point (vertex shader)
- int texcoordLoc; // Texcoord attribute location point (vertex shader)
- int normalLoc; // Normal attribute location point (vertex shader)
- int colorLoc; // Color attibute location point (vertex shader)
-
- // Uniforms
+ unsigned int id; // Shader program id
+
+ // Vertex attributes locations (default locations)
+ int vertexLoc; // Vertex attribute location point (default-location = 0)
+ int texcoordLoc; // Texcoord attribute location point (default-location = 1)
+ int normalLoc; // Normal attribute location point (default-location = 2)
+ int colorLoc; // Color attibute location point (default-location = 3)
+ int tangentLoc; // Tangent attribute location point (default-location = 4)
+ int texcoord2Loc; // Texcoord2 attribute location point (default-location = 5)
+
+ // Uniform locations
int mvpLoc; // ModelView-Projection matrix uniform location point (vertex shader)
int tintColorLoc; // Color uniform location point (fragment shader)
+ // Texture map locations
int mapDiffuseLoc; // Diffuse map texture uniform location point (fragment shader)
int mapNormalLoc; // Normal map texture uniform location point (fragment shader)
int mapSpecularLoc; // Specular map texture uniform location point (fragment shader)
@@ -183,6 +187,13 @@ typedef enum { OPENGL_11 = 1, OPENGL_33, OPENGL_ES_20 } GlVersion;
int format; // Data format (TextureFormat)
} Texture2D;
+ // RenderTexture2D type, for texture rendering
+ typedef struct RenderTexture2D {
+ unsigned int id; // Render texture (fbo) id
+ Texture2D texture; // Color buffer attachment texture
+ Texture2D depth; // Depth buffer attachment texture
+ } RenderTexture2D;
+
// Material type
typedef struct Material {
Shader shader;
@@ -198,13 +209,28 @@ typedef enum { OPENGL_11 = 1, OPENGL_33, OPENGL_ES_20 } GlVersion;
float glossiness;
float normalDepth;
} Material;
+
+ // Light type
+ // TODO: Review contained data to support different light types and features
+ typedef struct LightData {
+ int id;
+ int type; // LIGHT_POINT, LIGHT_DIRECTIONAL, LIGHT_SPOT
+ bool enabled;
+
+ Vector3 position;
+ Vector3 direction; // Used on LIGHT_DIRECTIONAL and LIGHT_SPOT (cone direction)
+ float attenuation; // Lost of light intensity with distance (use radius?)
+
+ Color diffuse; // Use Vector3 diffuse (including intensities)?
+ float intensity;
+
+ Color specular;
+ //float specFactor; // Specular intensity ?
- // 3d Model type
- typedef struct Model {
- Mesh mesh;
- Matrix transform;
- Material material;
- } Model;
+ //Color ambient; // Required?
+
+ float coneAngle; // SpotLight
+ } LightData, *Light;
// Color blending modes (pre-defined)
typedef enum { BLEND_ALPHA = 0, BLEND_ADDITIVE, BLEND_MULTIPLIED } BlendMode;
@@ -248,16 +274,20 @@ void rlColor4f(float x, float y, float z, float w); // Define one vertex (color)
//------------------------------------------------------------------------------------
void rlEnableTexture(unsigned int id); // Enable texture usage
void rlDisableTexture(void); // Disable texture usage
+void rlEnableRenderTexture(unsigned int id); // Enable render texture (fbo)
+void rlDisableRenderTexture(void); // Disable render texture (fbo), return to default framebuffer
void rlEnableDepthTest(void); // Enable depth test
void rlDisableDepthTest(void); // Disable depth test
+void rlEnableWireMode(void); // Enable wire mode
+void rlDisableWireMode(void); // Disable wire mode
void rlDeleteTextures(unsigned int id); // Delete OpenGL texture from GPU
+void rlDeleteRenderTextures(RenderTexture2D target); // Delete render textures (fbo) from GPU
void rlDeleteShader(unsigned int id); // Delete OpenGL shader program from GPU
void rlDeleteVertexArrays(unsigned int id); // Unload vertex data (VAO) from GPU memory
void rlDeleteBuffers(unsigned int id); // Unload vertex data (VBO) from GPU memory
void rlClearColor(byte r, byte g, byte b, byte a); // Clear color buffer with color
void rlClearScreenBuffers(void); // Clear used screen buffers (color and depth)
int rlGetVersion(void); // Returns current OpenGL version
-void rlEnablePostproFBO(void); // Enable rendering to postprocessing FBO
//------------------------------------------------------------------------------------
// Functions Declaration - rlgl functionality
@@ -268,27 +298,22 @@ void rlglDraw(void); // Draw VAO/VBO
void rlglInitGraphics(int offsetX, int offsetY, int width, int height); // Initialize Graphics (OpenGL stuff)
unsigned int rlglLoadTexture(void *data, int width, int height, int textureFormat, int mipmapCount); // Load texture in GPU
+RenderTexture2D rlglLoadRenderTexture(int width, int height); // Load a texture to be used for rendering (fbo with color and depth attachments)
void rlglUpdateTexture(unsigned int id, int width, int height, int format, void *data); // Update GPU texture with new data
void rlglGenerateMipmaps(Texture2D texture); // Generate mipmap data for selected texture
-// NOTE: There is a set of shader related functions that are available to end user,
-// to avoid creating function wrappers through core module, they have been directly declared in raylib.h
-
-void rlglInitPostpro(void); // Initialize postprocessing system
-void rlglDrawPostpro(void); // Draw with postprocessing shader
-
-Model rlglLoadModel(Mesh mesh); // Upload vertex data into GPU and provided VAO/VBO ids
-void rlglDrawModel(Model model, Vector3 position, Vector3 rotationAxis, float rotationAngle, Vector3 scale, Color color, bool wires);
+void rlglLoadMesh(Mesh *mesh, bool dynamic); // Upload vertex data into GPU and provided VAO/VBO ids
+void rlglUpdateMesh(Mesh mesh, int buffer, int numVertex); // Update vertex data on GPU (upload new data to one buffer)
+void rlglDrawMesh(Mesh mesh, Material material, Matrix transform); // Draw a 3d mesh with material and transform
+void rlglUnloadMesh(Mesh *mesh); // Unload mesh data from CPU and GPU
Vector3 rlglUnproject(Vector3 source, Matrix proj, Matrix view); // Get world coordinates from screen coordinates
unsigned char *rlglReadScreenPixels(int width, int height); // Read screen pixel data (color buffer)
void *rlglReadTexturePixels(Texture2D texture); // Read texture pixel data
-#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
-void PrintProjectionMatrix(void); // DEBUG: Print projection matrix
-void PrintModelviewMatrix(void); // DEBUG: Print modelview matrix
-#endif
+// NOTE: There is a set of shader related functions that are available to end user,
+// to avoid creating function wrappers through core module, they have been directly declared in raylib.h
#if defined(RLGL_STANDALONE)
//------------------------------------------------------------------------------------
@@ -296,23 +321,21 @@ void PrintModelviewMatrix(void); // DEBUG: Print modelview matrix
// NOTE: This functions are useless when using OpenGL 1.1
//------------------------------------------------------------------------------------
Shader LoadShader(char *vsFileName, char *fsFileName); // Load a custom shader and bind default locations
-unsigned int LoadShaderProgram(char *vShaderStr, char *fShaderStr); // Load custom shader strings and return program id
void UnloadShader(Shader shader); // Unload a custom shader from memory
-void SetPostproShader(Shader shader); // Set fullscreen postproduction shader
void SetCustomShader(Shader shader); // Set custom shader to be used in batch draw
void SetDefaultShader(void); // Set default shader to be used in batch draw
-void SetModelShader(Model *model, Shader shader); // Link a shader to a model
-bool IsPosproShaderEnabled(void); // Check if postprocessing shader is enabled
+Shader GetDefaultShader(void); // Get default shader
+Texture2D GetDefaultTexture(void); // Get default texture
-int GetShaderLocation(Shader shader, const char *uniformName); // Get shader uniform location
-void SetShaderValue(Shader shader, int uniformLoc, float *value, int size); // Set shader uniform value (float)
-void SetShaderValuei(Shader shader, int uniformLoc, int *value, int size); // Set shader uniform value (int)
-void SetShaderMapDiffuse(Shader *shader, Texture2D texture); // Default diffuse shader map texture assignment
-void SetShaderMapNormal(Shader *shader, const char *uniformName, Texture2D texture); // Normal map texture shader assignment
-void SetShaderMapSpecular(Shader *shader, const char *uniformName, Texture2D texture); // Specular map texture shader assignment
-void SetShaderMap(Shader *shader, int mapLocation, Texture2D texture, int textureUnit); // TODO: Generic shader map assignment
+int GetShaderLocation(Shader shader, const char *uniformName); // Get shader uniform location
+void SetShaderValue(Shader shader, int uniformLoc, float *value, int size); // Set shader uniform value (float)
+void SetShaderValuei(Shader shader, int uniformLoc, int *value, int size); // Set shader uniform value (int)
+void SetShaderValueMatrix(Shader shader, int uniformLoc, Matrix mat); // Set shader uniform value (matrix 4x4)
void SetBlendMode(int mode); // Set blending mode (alpha, additive, multiplied)
+
+Light CreateLight(int type, Vector3 position, Color diffuse); // Create a new light, initialize it and add to pool
+void DestroyLight(Light light); // Destroy a light and take it out of the list
#endif
#ifdef __cplusplus
diff --git a/src/shapes.c b/src/shapes.c
index 14d11315..5b66e5ef 100644
--- a/src/shapes.c
+++ b/src/shapes.c
@@ -446,7 +446,6 @@ bool CheckCollisionCircleRec(Vector2 center, float radius, Rectangle rec)
}
// Get collision rectangle for two rectangles collision
-// TODO: Depending on rec1 and rec2 order, it fails -> Review!
Rectangle GetCollisionRec(Rectangle rec1, Rectangle rec2)
{
Rectangle retRec = { 0, 0, 0, 0 };
diff --git a/src/stb_image.h b/src/stb_image.h
index c28b5286..ce87646d 100644
--- a/src/stb_image.h
+++ b/src/stb_image.h
@@ -1,4 +1,4 @@
-/* stb_image - v2.09 - public domain image loader - http://nothings.org/stb_image.h
+/* stb_image - v2.12 - public domain image loader - http://nothings.org/stb_image.h
no warranty implied; use at your own risk
Do this:
@@ -146,6 +146,12 @@
Latest revision history:
+ 2.12 (2016-04-02) fix typo in 2.11 PSD fix that caused crashes
+ 2.11 (2016-04-02) 16-bit PNGS; enable SSE2 in non-gcc x64
+ RGB-format JPEG; remove white matting in PSD;
+ allocate large structures on the stack;
+ correct channel count for PNG & BMP
+ 2.10 (2016-01-22) avoid warning introduced in 2.09
2.09 (2016-01-16) 16-bit TGA; comments in PNM files; STBI_REALLOC_SIZED
2.08 (2015-09-13) fix to 2.07 cleanup, reading RGB PSD as RGBA
2.07 (2015-09-13) partial animated GIF support
@@ -166,10 +172,6 @@
STBI_MALLOC,STBI_REALLOC,STBI_FREE
STBI_NO_*, STBI_ONLY_*
GIF bugfix
- 1.48 (2014-12-14) fix incorrectly-named assert()
- 1.47 (2014-12-14) 1/2/4-bit PNG support (both grayscale and paletted)
- optimize PNG
- fix bug in interlaced PNG with user-specified channel count
See end of file for full revision history.
@@ -200,17 +202,17 @@
Janez Zemva John Bartholomew Michal Cichon svdijk@github
Jonathan Blow Ken Hamada Tero Hanninen Baldur Karlsson
Laurent Gomila Cort Stratton Sergio Gonzalez romigrou@github
- Aruelien Pocheville Thibault Reuille Cass Everitt
- Ryamond Barbiero Paul Du Bois Engin Manap
+ Aruelien Pocheville Thibault Reuille Cass Everitt Matthew Gregan
+ Ryamond Barbiero Paul Du Bois Engin Manap snagar@github
+ Michaelangel007@github Oriol Ferrer Mesia socks-the-fox
Blazej Dariusz Roszkowski
- Michaelangel007@github
LICENSE
-This software is in the public domain. Where that dedication is not
-recognized, you are granted a perpetual, irrevocable license to copy,
-distribute, and modify this file as you see fit.
+This software is dual-licensed to the public domain and under the following
+license: you are granted a perpetual, irrevocable license to copy, modify,
+publish, and distribute this file as you see fit.
*/
@@ -679,7 +681,7 @@ typedef unsigned char validate_uint32[sizeof(stbi__uint32)==4 ? 1 : -1];
#define STBI_NO_SIMD
#endif
-#if !defined(STBI_NO_SIMD) && defined(STBI__X86_TARGET)
+#if !defined(STBI_NO_SIMD) && (defined(STBI__X86_TARGET) || defined(STBI__X64_TARGET))
#define STBI_SSE2
#include <emmintrin.h>
@@ -1513,6 +1515,7 @@ typedef struct
int succ_high;
int succ_low;
int eob_run;
+ int rgb;
int scan_n, order[4];
int restart_interval, todo;
@@ -2724,11 +2727,17 @@ static int stbi__process_frame_header(stbi__jpeg *z, int scan)
if (Lf != 8+3*s->img_n) return stbi__err("bad SOF len","Corrupt JPEG");
+ z->rgb = 0;
for (i=0; i < s->img_n; ++i) {
+ static unsigned char rgb[3] = { 'R', 'G', 'B' };
z->img_comp[i].id = stbi__get8(s);
if (z->img_comp[i].id != i+1) // JFIF requires
- if (z->img_comp[i].id != i) // some version of jpegtran outputs non-JFIF-compliant files!
- return stbi__err("bad component ID","Corrupt JPEG");
+ if (z->img_comp[i].id != i) { // some version of jpegtran outputs non-JFIF-compliant files!
+ // somethings output this (see http://fileformats.archiveteam.org/wiki/JPEG#Color_format)
+ if (z->img_comp[i].id != rgb[i])
+ return stbi__err("bad component ID","Corrupt JPEG");
+ ++z->rgb;
+ }
q = stbi__get8(s);
z->img_comp[i].h = (q >> 4); if (!z->img_comp[i].h || z->img_comp[i].h > 4) return stbi__err("bad H","Corrupt JPEG");
z->img_comp[i].v = q & 15; if (!z->img_comp[i].v || z->img_comp[i].v > 4) return stbi__err("bad V","Corrupt JPEG");
@@ -3390,7 +3399,17 @@ static stbi_uc *load_jpeg_image(stbi__jpeg *z, int *out_x, int *out_y, int *comp
if (n >= 3) {
stbi_uc *y = coutput[0];
if (z->s->img_n == 3) {
- z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n);
+ if (z->rgb == 3) {
+ for (i=0; i < z->s->img_x; ++i) {
+ out[0] = y[i];
+ out[1] = coutput[1][i];
+ out[2] = coutput[2][i];
+ out[3] = 255;
+ out += n;
+ }
+ } else {
+ z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n);
+ }
} else
for (i=0; i < z->s->img_x; ++i) {
out[0] = out[1] = out[2] = y[i];
@@ -3415,10 +3434,13 @@ static stbi_uc *load_jpeg_image(stbi__jpeg *z, int *out_x, int *out_y, int *comp
static unsigned char *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp)
{
- stbi__jpeg j;
- j.s = s;
- stbi__setup_jpeg(&j);
- return load_jpeg_image(&j, x,y,comp,req_comp);
+ unsigned char* result;
+ stbi__jpeg* j = (stbi__jpeg*) stbi__malloc(sizeof(stbi__jpeg));
+ j->s = s;
+ stbi__setup_jpeg(j);
+ result = load_jpeg_image(j, x,y,comp,req_comp);
+ STBI_FREE(j);
+ return result;
}
static int stbi__jpeg_test(stbi__context *s)
@@ -3446,9 +3468,12 @@ static int stbi__jpeg_info_raw(stbi__jpeg *j, int *x, int *y, int *comp)
static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp)
{
- stbi__jpeg j;
- j.s = s;
- return stbi__jpeg_info_raw(&j, x, y, comp);
+ int result;
+ stbi__jpeg* j = (stbi__jpeg*) (stbi__malloc(sizeof(stbi__jpeg)));
+ j->s = s;
+ result = stbi__jpeg_info_raw(j, x, y, comp);
+ STBI_FREE(j);
+ return result;
}
#endif
@@ -3629,6 +3654,7 @@ static int stbi__zexpand(stbi__zbuf *z, char *zout, int n) // need to make room
while (cur + n > limit)
limit *= 2;
q = (char *) STBI_REALLOC_SIZED(z->zout_start, old_limit, limit);
+ STBI_NOTUSED(old_limit);
if (q == NULL) return stbi__err("outofmem", "Out of memory");
z->zout_start = q;
z->zout = q + cur;
@@ -3738,7 +3764,7 @@ static int stbi__compute_huffman_codes(stbi__zbuf *a)
return 1;
}
-static int stbi__parse_uncomperssed_block(stbi__zbuf *a)
+static int stbi__parse_uncompressed_block(stbi__zbuf *a)
{
stbi_uc header[4];
int len,nlen,k;
@@ -3804,7 +3830,7 @@ static int stbi__parse_zlib(stbi__zbuf *a, int parse_header)
final = stbi__zreceive(a,1);
type = stbi__zreceive(a,2);
if (type == 0) {
- if (!stbi__parse_uncomperssed_block(a)) return 0;
+ if (!stbi__parse_uncompressed_block(a)) return 0;
} else if (type == 3) {
return 0;
} else {
@@ -3946,6 +3972,7 @@ typedef struct
{
stbi__context *s;
stbi_uc *idata, *expanded, *out;
+ int depth;
} stbi__png;
@@ -3985,14 +4012,19 @@ static stbi_uc stbi__depth_scale_table[9] = { 0, 0xff, 0x55, 0, 0x11, 0,0,0, 0x0
// create the png data from post-deflated data
static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 raw_len, int out_n, stbi__uint32 x, stbi__uint32 y, int depth, int color)
{
+ int bytes = (depth == 16? 2 : 1);
stbi__context *s = a->s;
- stbi__uint32 i,j,stride = x*out_n;
+ stbi__uint32 i,j,stride = x*out_n*bytes;
stbi__uint32 img_len, img_width_bytes;
int k;
int img_n = s->img_n; // copy it into a local for later
+ int output_bytes = out_n*bytes;
+ int filter_bytes = img_n*bytes;
+ int width = x;
+
STBI_ASSERT(out_n == s->img_n || out_n == s->img_n+1);
- a->out = (stbi_uc *) stbi__malloc(x * y * out_n); // extra bytes to write off the end into
+ a->out = (stbi_uc *) stbi__malloc(x * y * output_bytes); // extra bytes to write off the end into
if (!a->out) return stbi__err("outofmem", "Out of memory");
img_width_bytes = (((img_n * x * depth) + 7) >> 3);
@@ -4007,8 +4039,7 @@ static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 r
stbi_uc *cur = a->out + stride*j;
stbi_uc *prior = cur - stride;
int filter = *raw++;
- int filter_bytes = img_n;
- int width = x;
+
if (filter > 4)
return stbi__err("invalid filter","Corrupt PNG");
@@ -4041,6 +4072,14 @@ static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 r
raw += img_n;
cur += out_n;
prior += out_n;
+ } else if (depth == 16) {
+ if (img_n != out_n) {
+ cur[filter_bytes] = 255; // first pixel top byte
+ cur[filter_bytes+1] = 255; // first pixel bottom byte
+ }
+ raw += filter_bytes;
+ cur += output_bytes;
+ prior += output_bytes;
} else {
raw += 1;
cur += 1;
@@ -4049,7 +4088,7 @@ static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 r
// this is a little gross, so that we don't switch per-pixel or per-component
if (depth < 8 || img_n == out_n) {
- int nk = (width - 1)*img_n;
+ int nk = (width - 1)*filter_bytes;
#define CASE(f) \
case f: \
for (k=0; k < nk; ++k)
@@ -4069,18 +4108,27 @@ static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 r
STBI_ASSERT(img_n+1 == out_n);
#define CASE(f) \
case f: \
- for (i=x-1; i >= 1; --i, cur[img_n]=255,raw+=img_n,cur+=out_n,prior+=out_n) \
- for (k=0; k < img_n; ++k)
+ for (i=x-1; i >= 1; --i, cur[filter_bytes]=255,raw+=filter_bytes,cur+=output_bytes,prior+=output_bytes) \
+ for (k=0; k < filter_bytes; ++k)
switch (filter) {
CASE(STBI__F_none) cur[k] = raw[k]; break;
- CASE(STBI__F_sub) cur[k] = STBI__BYTECAST(raw[k] + cur[k-out_n]); break;
+ CASE(STBI__F_sub) cur[k] = STBI__BYTECAST(raw[k] + cur[k- output_bytes]); break;
CASE(STBI__F_up) cur[k] = STBI__BYTECAST(raw[k] + prior[k]); break;
- CASE(STBI__F_avg) cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k-out_n])>>1)); break;
- CASE(STBI__F_paeth) cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-out_n],prior[k],prior[k-out_n])); break;
- CASE(STBI__F_avg_first) cur[k] = STBI__BYTECAST(raw[k] + (cur[k-out_n] >> 1)); break;
- CASE(STBI__F_paeth_first) cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-out_n],0,0)); break;
+ CASE(STBI__F_avg) cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k- output_bytes])>>1)); break;
+ CASE(STBI__F_paeth) cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k- output_bytes],prior[k],prior[k- output_bytes])); break;
+ CASE(STBI__F_avg_first) cur[k] = STBI__BYTECAST(raw[k] + (cur[k- output_bytes] >> 1)); break;
+ CASE(STBI__F_paeth_first) cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k- output_bytes],0,0)); break;
}
#undef CASE
+
+ // the loop above sets the high byte of the pixels' alpha, but for
+ // 16 bit png files we also need the low byte set. we'll do that here.
+ if (depth == 16) {
+ cur = a->out + stride*j; // start at the beginning of the row again
+ for (i=0; i < x; ++i,cur+=output_bytes) {
+ cur[filter_bytes+1] = 255;
+ }
+ }
}
}
@@ -4156,6 +4204,17 @@ static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 r
}
}
}
+ } else if (depth == 16) {
+ // force the image data from big-endian to platform-native.
+ // this is done in a separate pass due to the decoding relying
+ // on the data being untouched, but could probably be done
+ // per-line during decode if care is taken.
+ stbi_uc *cur = a->out;
+ stbi__uint16 *cur16 = (stbi__uint16*)cur;
+
+ for(i=0; i < x*y*out_n; ++i,cur16++,cur+=2) {
+ *cur16 = (cur[0] << 8) | cur[1];
+ }
}
return 1;
@@ -4228,6 +4287,31 @@ static int stbi__compute_transparency(stbi__png *z, stbi_uc tc[3], int out_n)
return 1;
}
+static int stbi__compute_transparency16(stbi__png *z, stbi__uint16 tc[3], int out_n)
+{
+ stbi__context *s = z->s;
+ stbi__uint32 i, pixel_count = s->img_x * s->img_y;
+ stbi__uint16 *p = (stbi__uint16*) z->out;
+
+ // compute color-based transparency, assuming we've
+ // already got 65535 as the alpha value in the output
+ STBI_ASSERT(out_n == 2 || out_n == 4);
+
+ if (out_n == 2) {
+ for (i = 0; i < pixel_count; ++i) {
+ p[1] = (p[0] == tc[0] ? 0 : 65535);
+ p += 2;
+ }
+ } else {
+ for (i = 0; i < pixel_count; ++i) {
+ if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2])
+ p[3] = 0;
+ p += 4;
+ }
+ }
+ return 1;
+}
+
static int stbi__expand_png_palette(stbi__png *a, stbi_uc *palette, int len, int pal_img_n)
{
stbi__uint32 i, pixel_count = a->s->img_x * a->s->img_y;
@@ -4265,6 +4349,26 @@ static int stbi__expand_png_palette(stbi__png *a, stbi_uc *palette, int len, int
return 1;
}
+static int stbi__reduce_png(stbi__png *p)
+{
+ int i;
+ int img_len = p->s->img_x * p->s->img_y * p->s->img_out_n;
+ stbi_uc *reduced;
+ stbi__uint16 *orig = (stbi__uint16*)p->out;
+
+ if (p->depth != 16) return 1; // don't need to do anything if not 16-bit data
+
+ reduced = (stbi_uc *)stbi__malloc(img_len);
+ if (p == NULL) return stbi__err("outofmem", "Out of memory");
+
+ for (i = 0; i < img_len; ++i) reduced[i] = (stbi_uc)((orig[i] >> 8) & 0xFF); // top half of each byte is a decent approx of 16->8 bit scaling
+
+ p->out = reduced;
+ STBI_FREE(orig);
+
+ return 1;
+}
+
static int stbi__unpremultiply_on_load = 0;
static int stbi__de_iphone_flag = 0;
@@ -4326,8 +4430,9 @@ static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp)
{
stbi_uc palette[1024], pal_img_n=0;
stbi_uc has_trans=0, tc[3];
+ stbi__uint16 tc16[3];
stbi__uint32 ioff=0, idata_limit=0, i, pal_len=0;
- int first=1,k,interlace=0, color=0, depth=0, is_iphone=0;
+ int first=1,k,interlace=0, color=0, is_iphone=0;
stbi__context *s = z->s;
z->expanded = NULL;
@@ -4352,8 +4457,9 @@ static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp)
if (c.length != 13) return stbi__err("bad IHDR len","Corrupt PNG");
s->img_x = stbi__get32be(s); if (s->img_x > (1 << 24)) return stbi__err("too large","Very large image (corrupt?)");
s->img_y = stbi__get32be(s); if (s->img_y > (1 << 24)) return stbi__err("too large","Very large image (corrupt?)");
- depth = stbi__get8(s); if (depth != 1 && depth != 2 && depth != 4 && depth != 8) return stbi__err("1/2/4/8-bit only","PNG not supported: 1/2/4/8-bit only");
+ z->depth = stbi__get8(s); if (z->depth != 1 && z->depth != 2 && z->depth != 4 && z->depth != 8 && z->depth != 16) return stbi__err("1/2/4/8/16-bit only","PNG not supported: 1/2/4/8/16-bit only");
color = stbi__get8(s); if (color > 6) return stbi__err("bad ctype","Corrupt PNG");
+ if (color == 3 && z->depth == 16) return stbi__err("bad ctype","Corrupt PNG");
if (color == 3) pal_img_n = 3; else if (color & 1) return stbi__err("bad ctype","Corrupt PNG");
comp = stbi__get8(s); if (comp) return stbi__err("bad comp method","Corrupt PNG");
filter= stbi__get8(s); if (filter) return stbi__err("bad filter method","Corrupt PNG");
@@ -4401,8 +4507,11 @@ static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp)
if (!(s->img_n & 1)) return stbi__err("tRNS with alpha","Corrupt PNG");
if (c.length != (stbi__uint32) s->img_n*2) return stbi__err("bad tRNS len","Corrupt PNG");
has_trans = 1;
- for (k=0; k < s->img_n; ++k)
- tc[k] = (stbi_uc) (stbi__get16be(s) & 255) * stbi__depth_scale_table[depth]; // non 8-bit images will be larger
+ if (z->depth == 16) {
+ for (k = 0; k < s->img_n; ++k) tc16[k] = stbi__get16be(s); // copy the values as-is
+ } else {
+ for (k = 0; k < s->img_n; ++k) tc[k] = (stbi_uc)(stbi__get16be(s) & 255) * stbi__depth_scale_table[z->depth]; // non 8-bit images will be larger
+ }
}
break;
}
@@ -4418,6 +4527,7 @@ static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp)
if (idata_limit == 0) idata_limit = c.length > 4096 ? c.length : 4096;
while (ioff + c.length > idata_limit)
idata_limit *= 2;
+ STBI_NOTUSED(idata_limit_old);
p = (stbi_uc *) STBI_REALLOC_SIZED(z->idata, idata_limit_old, idata_limit); if (p == NULL) return stbi__err("outofmem", "Out of memory");
z->idata = p;
}
@@ -4432,7 +4542,7 @@ static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp)
if (scan != STBI__SCAN_load) return 1;
if (z->idata == NULL) return stbi__err("no IDAT","Corrupt PNG");
// initial guess for decoded data size to avoid unnecessary reallocs
- bpl = (s->img_x * depth + 7) / 8; // bytes per line, per component
+ bpl = (s->img_x * z->depth + 7) / 8; // bytes per line, per component
raw_len = bpl * s->img_y * s->img_n /* pixels */ + s->img_y /* filter mode per row */;
z->expanded = (stbi_uc *) stbi_zlib_decode_malloc_guesssize_headerflag((char *) z->idata, ioff, raw_len, (int *) &raw_len, !is_iphone);
if (z->expanded == NULL) return 0; // zlib should set error
@@ -4441,9 +4551,14 @@ static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp)
s->img_out_n = s->img_n+1;
else
s->img_out_n = s->img_n;
- if (!stbi__create_png_image(z, z->expanded, raw_len, s->img_out_n, depth, color, interlace)) return 0;
- if (has_trans)
- if (!stbi__compute_transparency(z, tc, s->img_out_n)) return 0;
+ if (!stbi__create_png_image(z, z->expanded, raw_len, s->img_out_n, z->depth, color, interlace)) return 0;
+ if (has_trans) {
+ if (z->depth == 16) {
+ if (!stbi__compute_transparency16(z, tc16, s->img_out_n)) return 0;
+ } else {
+ if (!stbi__compute_transparency(z, tc, s->img_out_n)) return 0;
+ }
+ }
if (is_iphone && stbi__de_iphone_flag && s->img_out_n > 2)
stbi__de_iphone(z);
if (pal_img_n) {
@@ -4485,6 +4600,11 @@ static unsigned char *stbi__do_png(stbi__png *p, int *x, int *y, int *n, int req
unsigned char *result=NULL;
if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error");
if (stbi__parse_png_file(p, STBI__SCAN_load, req_comp)) {
+ if (p->depth == 16) {
+ if (!stbi__reduce_png(p)) {
+ return result;
+ }
+ }
result = p->out;
p->out = NULL;
if (req_comp && req_comp != p->s->img_out_n) {
@@ -4494,7 +4614,7 @@ static unsigned char *stbi__do_png(stbi__png *p, int *x, int *y, int *n, int req
}
*x = p->s->img_x;
*y = p->s->img_y;
- if (n) *n = p->s->img_out_n;
+ if (n) *n = p->s->img_n;
}
STBI_FREE(p->out); p->out = NULL;
STBI_FREE(p->expanded); p->expanded = NULL;
@@ -4619,6 +4739,7 @@ static void *stbi__bmp_parse_header(stbi__context *s, stbi__bmp_data *info)
stbi__get16le(s); // discard reserved
info->offset = stbi__get32le(s);
info->hsz = hsz = stbi__get32le(s);
+ info->mr = info->mg = info->mb = info->ma = 0;
if (hsz != 12 && hsz != 40 && hsz != 56 && hsz != 108 && hsz != 124) return stbi__errpuc("unknown BMP", "BMP type not supported: unknown");
if (hsz == 12) {
@@ -4647,7 +4768,6 @@ static void *stbi__bmp_parse_header(stbi__context *s, stbi__bmp_data *info)
stbi__get32le(s);
}
if (info->bpp == 16 || info->bpp == 32) {
- info->mr = info->mg = info->mb = 0;
if (compress == 0) {
if (info->bpp == 32) {
info->mr = 0xffu << 16;
@@ -5342,6 +5462,21 @@ static stbi_uc *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int
}
}
+ if (channelCount >= 4) {
+ for (i=0; i < w*h; ++i) {
+ unsigned char *pixel = out + 4*i;
+ if (pixel[3] != 0 && pixel[3] != 255) {
+ // remove weird white matte from PSD
+ float a = pixel[3] / 255.0f;
+ float ra = 1.0f / a;
+ float inv_a = 255.0f * (1 - ra);
+ pixel[0] = (unsigned char) (pixel[0]*ra + inv_a);
+ pixel[1] = (unsigned char) (pixel[1]*ra + inv_a);
+ pixel[2] = (unsigned char) (pixel[2]*ra + inv_a);
+ }
+ }
+ }
+
if (req_comp && req_comp != 4) {
out = stbi__convert_format(out, 4, req_comp, w, h);
if (out == NULL) return out; // stbi__convert_format frees input on failure
@@ -5654,13 +5789,15 @@ static int stbi__gif_header(stbi__context *s, stbi__gif *g, int *comp, int is_in
static int stbi__gif_info_raw(stbi__context *s, int *x, int *y, int *comp)
{
- stbi__gif g;
- if (!stbi__gif_header(s, &g, comp, 1)) {
+ stbi__gif* g = (stbi__gif*) stbi__malloc(sizeof(stbi__gif));
+ if (!stbi__gif_header(s, g, comp, 1)) {
+ STBI_FREE(g);
stbi__rewind( s );
return 0;
}
- if (x) *x = g.w;
- if (y) *y = g.h;
+ if (x) *x = g->w;
+ if (y) *y = g->h;
+ STBI_FREE(g);
return 1;
}
@@ -5913,20 +6050,20 @@ static stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, i
static stbi_uc *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp)
{
stbi_uc *u = 0;
- stbi__gif g;
- memset(&g, 0, sizeof(g));
+ stbi__gif* g = (stbi__gif*) stbi__malloc(sizeof(stbi__gif));
+ memset(g, 0, sizeof(*g));
- u = stbi__gif_load_next(s, &g, comp, req_comp);
+ u = stbi__gif_load_next(s, g, comp, req_comp);
if (u == (stbi_uc *) s) u = 0; // end of animated gif marker
if (u) {
- *x = g.w;
- *y = g.h;
+ *x = g->w;
+ *y = g->h;
if (req_comp && req_comp != 4)
- u = stbi__convert_format(u, 4, req_comp, g.w, g.h);
+ u = stbi__convert_format(u, 4, req_comp, g->w, g->h);
}
- else if (g.out)
- STBI_FREE(g.out);
-
+ else if (g->out)
+ STBI_FREE(g->out);
+ STBI_FREE(g);
return u;
}
@@ -6464,6 +6601,14 @@ STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *c, void *user, int
/*
revision history:
+ 2.12 (2016-04-02) fix typo in 2.11 PSD fix that caused crashes
+ 2.11 (2016-04-02) allocate large structures on the stack
+ remove white matting for transparent PSD
+ fix reported channel count for PNG & BMP
+ re-enable SSE2 in non-gcc 64-bit
+ support RGB-formatted JPEG
+ read 16-bit PNGs (only as 8-bit)
+ 2.10 (2016-01-22) avoid warning introduced in 2.09 by STBI_REALLOC_SIZED
2.09 (2016-01-16) allow comments in PNM files
16-bit-per-pixel TGA (not bit-per-component)
info() for TGA could break due to .hdr handling
diff --git a/src/stb_image_resize.h b/src/stb_image_resize.h
index 4ce7ddbf..4cabe540 100644
--- a/src/stb_image_resize.h
+++ b/src/stb_image_resize.h
@@ -1,4 +1,4 @@
-/* stb_image_resize - v0.90 - public domain image resizing
+/* stb_image_resize - v0.91 - public domain image resizing
by Jorge L Rodriguez (@VinoBS) - 2014
http://github.com/nothings/stb
@@ -156,13 +156,14 @@
Sean Barrett: API design, optimizations
REVISIONS
+ 0.91 (2016-04-02) fix warnings; fix handling of subpixel regions
0.90 (2014-09-17) first released version
LICENSE
- This software is in the public domain. Where that dedication is not
- recognized, you are granted a perpetual, irrevocable license to copy,
- distribute, and modify this file as you see fit.
+ This software is dual-licensed to the public domain and under the following
+ license: you are granted a perpetual, irrevocable license to copy, modify,
+ publish, and distribute this file as you see fit.
TODO
Don't decode all of the image data when only processing a partial tile
@@ -383,15 +384,6 @@ STBIRDEF int stbir_resize_region( const void *input_pixels , int input_w , int
#define STBIR_ASSERT(x) assert(x)
#endif
-#ifdef STBIR_DEBUG
-#define STBIR__DEBUG_ASSERT STBIR_ASSERT
-#else
-#define STBIR__DEBUG_ASSERT
-#endif
-
-// If you hit this it means I haven't done it yet.
-#define STBIR__UNIMPLEMENTED(x) STBIR_ASSERT(!(x))
-
// For memset
#include <string.h>
@@ -758,7 +750,7 @@ static float stbir__filter_trapezoid(float x, float scale)
{
float halfscale = scale / 2;
float t = 0.5f + halfscale;
- STBIR__DEBUG_ASSERT(scale <= 1);
+ STBIR_ASSERT(scale <= 1);
x = (float)fabs(x);
@@ -776,7 +768,7 @@ static float stbir__filter_trapezoid(float x, float scale)
static float stbir__support_trapezoid(float scale)
{
- STBIR__DEBUG_ASSERT(scale <= 1);
+ STBIR_ASSERT(scale <= 1);
return 0.5f + scale / 2;
}
@@ -990,7 +982,7 @@ static int stbir__edge_wrap_slow(stbir_edge edge, int n, int max)
return n; // NOTREACHED
default:
- STBIR__UNIMPLEMENTED("Unimplemented edge type");
+ STBIR_ASSERT(!"Unimplemented edge type");
return 0;
}
}
@@ -1039,12 +1031,12 @@ static void stbir__calculate_coefficients_upsample(stbir__info* stbir_info, stbi
float total_filter = 0;
float filter_scale;
- STBIR__DEBUG_ASSERT(in_last_pixel - in_first_pixel <= (int)ceil(stbir__filter_info_table[filter].support(1/scale) * 2)); // Taken directly from stbir__get_coefficient_width() which we can't call because we don't know if we're horizontal or vertical.
+ STBIR_ASSERT(in_last_pixel - in_first_pixel <= (int)ceil(stbir__filter_info_table[filter].support(1/scale) * 2)); // Taken directly from stbir__get_coefficient_width() which we can't call because we don't know if we're horizontal or vertical.
contributor->n0 = in_first_pixel;
contributor->n1 = in_last_pixel;
- STBIR__DEBUG_ASSERT(contributor->n1 >= contributor->n0);
+ STBIR_ASSERT(contributor->n1 >= contributor->n0);
for (i = 0; i <= in_last_pixel - in_first_pixel; i++)
{
@@ -1062,10 +1054,10 @@ static void stbir__calculate_coefficients_upsample(stbir__info* stbir_info, stbi
total_filter += coefficient_group[i];
}
- STBIR__DEBUG_ASSERT(stbir__filter_info_table[filter].kernel((float)(in_last_pixel + 1) + 0.5f - in_center_of_out, 1/scale) == 0);
+ STBIR_ASSERT(stbir__filter_info_table[filter].kernel((float)(in_last_pixel + 1) + 0.5f - in_center_of_out, 1/scale) == 0);
- STBIR__DEBUG_ASSERT(total_filter > 0.9);
- STBIR__DEBUG_ASSERT(total_filter < 1.1f); // Make sure it's not way off.
+ STBIR_ASSERT(total_filter > 0.9);
+ STBIR_ASSERT(total_filter < 1.1f); // Make sure it's not way off.
// Make sure the sum of all coefficients is 1.
filter_scale = 1 / total_filter;
@@ -1087,12 +1079,12 @@ static void stbir__calculate_coefficients_downsample(stbir__info* stbir_info, st
{
int i;
- STBIR__DEBUG_ASSERT(out_last_pixel - out_first_pixel <= (int)ceil(stbir__filter_info_table[filter].support(scale_ratio) * 2)); // Taken directly from stbir__get_coefficient_width() which we can't call because we don't know if we're horizontal or vertical.
+ STBIR_ASSERT(out_last_pixel - out_first_pixel <= (int)ceil(stbir__filter_info_table[filter].support(scale_ratio) * 2)); // Taken directly from stbir__get_coefficient_width() which we can't call because we don't know if we're horizontal or vertical.
contributor->n0 = out_first_pixel;
contributor->n1 = out_last_pixel;
- STBIR__DEBUG_ASSERT(contributor->n1 >= contributor->n0);
+ STBIR_ASSERT(contributor->n1 >= contributor->n0);
for (i = 0; i <= out_last_pixel - out_first_pixel; i++)
{
@@ -1101,7 +1093,7 @@ static void stbir__calculate_coefficients_downsample(stbir__info* stbir_info, st
coefficient_group[i] = stbir__filter_info_table[filter].kernel(x, scale_ratio) * scale_ratio;
}
- STBIR__DEBUG_ASSERT(stbir__filter_info_table[filter].kernel((float)(out_last_pixel + 1) + 0.5f - out_center_of_in, scale_ratio) == 0);
+ STBIR_ASSERT(stbir__filter_info_table[filter].kernel((float)(out_last_pixel + 1) + 0.5f - out_center_of_in, scale_ratio) == 0);
for (i = out_last_pixel - out_first_pixel; i >= 0; i--)
{
@@ -1136,8 +1128,8 @@ static void stbir__normalize_downsample_coefficients(stbir__info* stbir_info, st
break;
}
- STBIR__DEBUG_ASSERT(total > 0.9f);
- STBIR__DEBUG_ASSERT(total < 1.1f);
+ STBIR_ASSERT(total > 0.9f);
+ STBIR_ASSERT(total < 1.1f);
scale = 1 / total;
@@ -1364,7 +1356,7 @@ static void stbir__decode_scanline(stbir__info* stbir_info, int n)
break;
default:
- STBIR__UNIMPLEMENTED("Unknown type/colorspace/channels combination.");
+ STBIR_ASSERT(!"Unknown type/colorspace/channels combination.");
break;
}
@@ -1425,7 +1417,7 @@ static float* stbir__add_empty_ring_buffer_entry(stbir__info* stbir_info, int n)
else
{
ring_buffer_index = (stbir_info->ring_buffer_begin_index + (stbir_info->ring_buffer_last_scanline - stbir_info->ring_buffer_first_scanline) + 1) % stbir_info->vertical_filter_pixel_width;
- STBIR__DEBUG_ASSERT(ring_buffer_index != stbir_info->ring_buffer_begin_index);
+ STBIR_ASSERT(ring_buffer_index != stbir_info->ring_buffer_begin_index);
}
ring_buffer = stbir__get_ring_buffer_entry(stbir_info->ring_buffer, ring_buffer_index, stbir_info->ring_buffer_length_bytes / sizeof(float));
@@ -1457,11 +1449,11 @@ static void stbir__resample_horizontal_upsample(stbir__info* stbir_info, int n,
int coefficient_group = coefficient_width * x;
int coefficient_counter = 0;
- STBIR__DEBUG_ASSERT(n1 >= n0);
- STBIR__DEBUG_ASSERT(n0 >= -stbir_info->horizontal_filter_pixel_margin);
- STBIR__DEBUG_ASSERT(n1 >= -stbir_info->horizontal_filter_pixel_margin);
- STBIR__DEBUG_ASSERT(n0 < stbir_info->input_w + stbir_info->horizontal_filter_pixel_margin);
- STBIR__DEBUG_ASSERT(n1 < stbir_info->input_w + stbir_info->horizontal_filter_pixel_margin);
+ STBIR_ASSERT(n1 >= n0);
+ STBIR_ASSERT(n0 >= -stbir_info->horizontal_filter_pixel_margin);
+ STBIR_ASSERT(n1 >= -stbir_info->horizontal_filter_pixel_margin);
+ STBIR_ASSERT(n0 < stbir_info->input_w + stbir_info->horizontal_filter_pixel_margin);
+ STBIR_ASSERT(n1 < stbir_info->input_w + stbir_info->horizontal_filter_pixel_margin);
switch (channels) {
case 1:
@@ -1469,7 +1461,7 @@ static void stbir__resample_horizontal_upsample(stbir__info* stbir_info, int n,
{
int in_pixel_index = k * 1;
float coefficient = horizontal_coefficients[coefficient_group + coefficient_counter++];
- STBIR__DEBUG_ASSERT(coefficient != 0);
+ STBIR_ASSERT(coefficient != 0);
output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient;
}
break;
@@ -1478,7 +1470,7 @@ static void stbir__resample_horizontal_upsample(stbir__info* stbir_info, int n,
{
int in_pixel_index = k * 2;
float coefficient = horizontal_coefficients[coefficient_group + coefficient_counter++];
- STBIR__DEBUG_ASSERT(coefficient != 0);
+ STBIR_ASSERT(coefficient != 0);
output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient;
output_buffer[out_pixel_index + 1] += decode_buffer[in_pixel_index + 1] * coefficient;
}
@@ -1488,7 +1480,7 @@ static void stbir__resample_horizontal_upsample(stbir__info* stbir_info, int n,
{
int in_pixel_index = k * 3;
float coefficient = horizontal_coefficients[coefficient_group + coefficient_counter++];
- STBIR__DEBUG_ASSERT(coefficient != 0);
+ STBIR_ASSERT(coefficient != 0);
output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient;
output_buffer[out_pixel_index + 1] += decode_buffer[in_pixel_index + 1] * coefficient;
output_buffer[out_pixel_index + 2] += decode_buffer[in_pixel_index + 2] * coefficient;
@@ -1499,7 +1491,7 @@ static void stbir__resample_horizontal_upsample(stbir__info* stbir_info, int n,
{
int in_pixel_index = k * 4;
float coefficient = horizontal_coefficients[coefficient_group + coefficient_counter++];
- STBIR__DEBUG_ASSERT(coefficient != 0);
+ STBIR_ASSERT(coefficient != 0);
output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient;
output_buffer[out_pixel_index + 1] += decode_buffer[in_pixel_index + 1] * coefficient;
output_buffer[out_pixel_index + 2] += decode_buffer[in_pixel_index + 2] * coefficient;
@@ -1512,7 +1504,7 @@ static void stbir__resample_horizontal_upsample(stbir__info* stbir_info, int n,
int in_pixel_index = k * channels;
float coefficient = horizontal_coefficients[coefficient_group + coefficient_counter++];
int c;
- STBIR__DEBUG_ASSERT(coefficient != 0);
+ STBIR_ASSERT(coefficient != 0);
for (c = 0; c < channels; c++)
output_buffer[out_pixel_index + c] += decode_buffer[in_pixel_index + c] * coefficient;
}
@@ -1535,7 +1527,7 @@ static void stbir__resample_horizontal_downsample(stbir__info* stbir_info, int n
int filter_pixel_margin = stbir_info->horizontal_filter_pixel_margin;
int max_x = input_w + filter_pixel_margin * 2;
- STBIR__DEBUG_ASSERT(!stbir__use_width_upsampling(stbir_info));
+ STBIR_ASSERT(!stbir__use_width_upsampling(stbir_info));
switch (channels) {
case 1:
@@ -1553,7 +1545,7 @@ static void stbir__resample_horizontal_downsample(stbir__info* stbir_info, int n
{
int out_pixel_index = k * 1;
float coefficient = horizontal_coefficients[coefficient_group + k - n0];
- STBIR__DEBUG_ASSERT(coefficient != 0);
+ STBIR_ASSERT(coefficient != 0);
output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient;
}
}
@@ -1574,7 +1566,7 @@ static void stbir__resample_horizontal_downsample(stbir__info* stbir_info, int n
{
int out_pixel_index = k * 2;
float coefficient = horizontal_coefficients[coefficient_group + k - n0];
- STBIR__DEBUG_ASSERT(coefficient != 0);
+ STBIR_ASSERT(coefficient != 0);
output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient;
output_buffer[out_pixel_index + 1] += decode_buffer[in_pixel_index + 1] * coefficient;
}
@@ -1596,7 +1588,7 @@ static void stbir__resample_horizontal_downsample(stbir__info* stbir_info, int n
{
int out_pixel_index = k * 3;
float coefficient = horizontal_coefficients[coefficient_group + k - n0];
- STBIR__DEBUG_ASSERT(coefficient != 0);
+ STBIR_ASSERT(coefficient != 0);
output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient;
output_buffer[out_pixel_index + 1] += decode_buffer[in_pixel_index + 1] * coefficient;
output_buffer[out_pixel_index + 2] += decode_buffer[in_pixel_index + 2] * coefficient;
@@ -1619,7 +1611,7 @@ static void stbir__resample_horizontal_downsample(stbir__info* stbir_info, int n
{
int out_pixel_index = k * 4;
float coefficient = horizontal_coefficients[coefficient_group + k - n0];
- STBIR__DEBUG_ASSERT(coefficient != 0);
+ STBIR_ASSERT(coefficient != 0);
output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient;
output_buffer[out_pixel_index + 1] += decode_buffer[in_pixel_index + 1] * coefficient;
output_buffer[out_pixel_index + 2] += decode_buffer[in_pixel_index + 2] * coefficient;
@@ -1644,7 +1636,7 @@ static void stbir__resample_horizontal_downsample(stbir__info* stbir_info, int n
int c;
int out_pixel_index = k * channels;
float coefficient = horizontal_coefficients[coefficient_group + k - n0];
- STBIR__DEBUG_ASSERT(coefficient != 0);
+ STBIR_ASSERT(coefficient != 0);
for (c = 0; c < channels; c++)
output_buffer[out_pixel_index + c] += decode_buffer[in_pixel_index + c] * coefficient;
}
@@ -1856,7 +1848,7 @@ static void stbir__encode_scanline(stbir__info* stbir_info, int num_pixels, void
break;
default:
- STBIR__UNIMPLEMENTED("Unknown type/colorspace/channels combination.");
+ STBIR_ASSERT(!"Unknown type/colorspace/channels combination.");
break;
}
}
@@ -1893,7 +1885,7 @@ static void stbir__resample_vertical_upsample(stbir__info* stbir_info, int n, in
output_row_start = n * stbir_info->output_stride_bytes;
- STBIR__DEBUG_ASSERT(stbir__use_height_upsampling(stbir_info));
+ STBIR_ASSERT(stbir__use_height_upsampling(stbir_info));
memset(encode_buffer, 0, output_w * sizeof(float) * channels);
@@ -2003,7 +1995,7 @@ static void stbir__resample_vertical_downsample(stbir__info* stbir_info, int n,
n0 = vertical_contributors[contributor].n0;
n1 = vertical_contributors[contributor].n1;
- STBIR__DEBUG_ASSERT(!stbir__use_height_upsampling(stbir_info));
+ STBIR_ASSERT(!stbir__use_height_upsampling(stbir_info));
for (k = n0; k <= n1; k++)
{
@@ -2068,7 +2060,7 @@ static void stbir__buffer_loop_upsample(stbir__info* stbir_info)
float scale_ratio = stbir_info->vertical_scale;
float out_scanlines_radius = stbir__filter_info_table[stbir_info->vertical_filter].support(1/scale_ratio) * scale_ratio;
- STBIR__DEBUG_ASSERT(stbir__use_height_upsampling(stbir_info));
+ STBIR_ASSERT(stbir__use_height_upsampling(stbir_info));
for (y = 0; y < stbir_info->output_h; y++)
{
@@ -2077,7 +2069,7 @@ static void stbir__buffer_loop_upsample(stbir__info* stbir_info)
stbir__calculate_sample_range_upsample(y, out_scanlines_radius, scale_ratio, stbir_info->vertical_shift, &in_first_scanline, &in_last_scanline, &in_center_of_out);
- STBIR__DEBUG_ASSERT(in_last_scanline - in_first_scanline <= stbir_info->vertical_filter_pixel_width);
+ STBIR_ASSERT(in_last_scanline - in_first_scanline <= stbir_info->vertical_filter_pixel_width);
if (stbir_info->ring_buffer_begin_index >= 0)
{
@@ -2169,7 +2161,7 @@ static void stbir__buffer_loop_downsample(stbir__info* stbir_info)
int pixel_margin = stbir_info->vertical_filter_pixel_margin;
int max_y = stbir_info->input_h + pixel_margin;
- STBIR__DEBUG_ASSERT(!stbir__use_height_upsampling(stbir_info));
+ STBIR_ASSERT(!stbir__use_height_upsampling(stbir_info));
for (y = -pixel_margin; y < max_y; y++)
{
@@ -2178,7 +2170,7 @@ static void stbir__buffer_loop_downsample(stbir__info* stbir_info)
stbir__calculate_sample_range_downsample(y, in_pixels_radius, scale_ratio, stbir_info->vertical_shift, &out_first_scanline, &out_last_scanline, &out_center_of_in);
- STBIR__DEBUG_ASSERT(out_last_scanline - out_first_scanline <= stbir_info->vertical_filter_pixel_width);
+ STBIR_ASSERT(out_last_scanline - out_first_scanline <= stbir_info->vertical_filter_pixel_width);
if (out_last_scanline < 0 || out_first_scanline >= output_h)
continue;
@@ -2229,8 +2221,8 @@ static void stbir__calculate_transform(stbir__info *info, float s0, float t0, fl
info->horizontal_scale = ((float)info->output_w / info->input_w) / (s1 - s0);
info->vertical_scale = ((float)info->output_h / info->input_h) / (t1 - t0);
- info->horizontal_shift = s0 * info->input_w / (s1 - s0);
- info->vertical_shift = t0 * info->input_h / (t1 - t0);
+ info->horizontal_shift = s0 * info->output_w / (s1 - s0);
+ info->vertical_shift = t0 * info->output_h / (t1 - t0);
}
}
@@ -2380,7 +2372,7 @@ static int stbir__resize_allocated(stbir__info *info,
info->ring_buffer = STBIR__NEXT_MEMPTR(info->decode_buffer, float);
info->encode_buffer = STBIR__NEXT_MEMPTR(info->ring_buffer, float);
- STBIR__DEBUG_ASSERT((size_t)STBIR__NEXT_MEMPTR(info->encode_buffer, unsigned char) == (size_t)tempmem + tempmem_size_in_bytes);
+ STBIR_ASSERT((size_t)STBIR__NEXT_MEMPTR(info->encode_buffer, unsigned char) == (size_t)tempmem + tempmem_size_in_bytes);
}
else
{
@@ -2388,7 +2380,7 @@ static int stbir__resize_allocated(stbir__info *info,
info->ring_buffer = STBIR__NEXT_MEMPTR(info->horizontal_buffer, float);
info->encode_buffer = NULL;
- STBIR__DEBUG_ASSERT((size_t)STBIR__NEXT_MEMPTR(info->ring_buffer, unsigned char) == (size_t)tempmem + tempmem_size_in_bytes);
+ STBIR_ASSERT((size_t)STBIR__NEXT_MEMPTR(info->ring_buffer, unsigned char) == (size_t)tempmem + tempmem_size_in_bytes);
}
#undef STBIR__NEXT_MEMPTR
@@ -2409,10 +2401,10 @@ static int stbir__resize_allocated(stbir__info *info,
STBIR_PROGRESS_REPORT(1);
#ifdef STBIR_DEBUG_OVERWRITE_TEST
- STBIR__DEBUG_ASSERT(memcmp(overwrite_output_before_pre, &((unsigned char*)output_data)[-OVERWRITE_ARRAY_SIZE], OVERWRITE_ARRAY_SIZE) == 0);
- STBIR__DEBUG_ASSERT(memcmp(overwrite_output_after_pre, &((unsigned char*)output_data)[begin_forbidden], OVERWRITE_ARRAY_SIZE) == 0);
- STBIR__DEBUG_ASSERT(memcmp(overwrite_tempmem_before_pre, &((unsigned char*)tempmem)[-OVERWRITE_ARRAY_SIZE], OVERWRITE_ARRAY_SIZE) == 0);
- STBIR__DEBUG_ASSERT(memcmp(overwrite_tempmem_after_pre, &((unsigned char*)tempmem)[tempmem_size_in_bytes], OVERWRITE_ARRAY_SIZE) == 0);
+ STBIR_ASSERT(memcmp(overwrite_output_before_pre, &((unsigned char*)output_data)[-OVERWRITE_ARRAY_SIZE], OVERWRITE_ARRAY_SIZE) == 0);
+ STBIR_ASSERT(memcmp(overwrite_output_after_pre, &((unsigned char*)output_data)[begin_forbidden], OVERWRITE_ARRAY_SIZE) == 0);
+ STBIR_ASSERT(memcmp(overwrite_tempmem_before_pre, &((unsigned char*)tempmem)[-OVERWRITE_ARRAY_SIZE], OVERWRITE_ARRAY_SIZE) == 0);
+ STBIR_ASSERT(memcmp(overwrite_tempmem_after_pre, &((unsigned char*)tempmem)[tempmem_size_in_bytes], OVERWRITE_ARRAY_SIZE) == 0);
#endif
return 1;
diff --git a/src/stb_image_write.h b/src/stb_image_write.h
index 98fa4103..4319c0de 100644
--- a/src/stb_image_write.h
+++ b/src/stb_image_write.h
@@ -1,4 +1,4 @@
-/* stb_image_write - v1.01 - public domain - http://nothings.org/stb/stb_image_write.h
+/* stb_image_write - v1.02 - public domain - http://nothings.org/stb/stb_image_write.h
writes out PNG/BMP/TGA images to C stdio - Sean Barrett 2010-2015
no warranty implied; use at your own risk
@@ -102,12 +102,13 @@ CREDITS:
Sergio Gonzalez
Jonas Karlsson
Filip Wasil
+ Thatcher Ulrich
LICENSE
-This software is in the public domain. Where that dedication is not
-recognized, you are granted a perpetual, irrevocable license to copy,
-distribute, and modify this file as you see fit.
+This software is dual-licensed to the public domain and under the following
+license: you are granted a perpetual, irrevocable license to copy, modify,
+publish, and distribute this file as you see fit.
*/
@@ -736,7 +737,7 @@ unsigned char * stbi_zlib_compress(unsigned char *data, int data_len, int *out_l
unsigned int bitbuf=0;
int i,j, bitcount=0;
unsigned char *out = NULL;
- unsigned char **hash_table[stbiw__ZHASH]; // 64KB on the stack!
+ unsigned char ***hash_table = (unsigned char***) STBIW_MALLOC(stbiw__ZHASH * sizeof(char**));
if (quality < 5) quality = 5;
stbiw__sbpush(out, 0x78); // DEFLATE 32K window
@@ -808,6 +809,7 @@ unsigned char * stbi_zlib_compress(unsigned char *data, int data_len, int *out_l
for (i=0; i < stbiw__ZHASH; ++i)
(void) stbiw__sbfree(hash_table[i]);
+ STBIW_FREE(hash_table);
{
// compute adler32 on input
@@ -1015,6 +1017,8 @@ STBIWDEF int stbi_write_png_to_func(stbi_write_func *func, void *context, int x,
#endif // STB_IMAGE_WRITE_IMPLEMENTATION
/* Revision history
+ 1.02 (2016-04-02)
+ avoid allocating large structures on the stack
1.01 (2016-01-16)
STBIW_REALLOC_SIZED: support allocators with no realloc support
avoid race-condition in crc initialization
diff --git a/src/stb_rect_pack.h b/src/stb_rect_pack.h
index 63a5b159..bd1cfc60 100644
--- a/src/stb_rect_pack.h
+++ b/src/stb_rect_pack.h
@@ -1,4 +1,4 @@
-// stb_rect_pack.h - v0.06 - public domain - rectangle packing
+// stb_rect_pack.h - v0.08 - public domain - rectangle packing
// Sean Barrett 2014
//
// Useful for e.g. packing rectangular textures into an atlas.
@@ -28,14 +28,22 @@
// Minor features
// Martins Mozeiko
// Bugfixes / warning fixes
-// [your name could be here]
+// Jeremy Jaussaud
//
// Version history:
//
+// 0.08 (2015-09-13) really fix bug with empty rects (w=0 or h=0)
+// 0.07 (2015-09-13) fix bug with empty rects (w=0 or h=0)
// 0.06 (2015-04-15) added STBRP_SORT to allow replacing qsort
// 0.05: added STBRP_ASSERT to allow replacing assert
// 0.04: fixed minor bug in STBRP_LARGE_RECTS support
// 0.01: initial release
+//
+// LICENSE
+//
+// This software is dual-licensed to the public domain and under the following
+// license: you are granted a perpetual, irrevocable license to copy, modify,
+// publish, and distribute this file as you see fit.
//////////////////////////////////////////////////////////////////////////////
//
@@ -541,12 +549,16 @@ STBRP_DEF void stbrp_pack_rects(stbrp_context *context, stbrp_rect *rects, int n
STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_height_compare);
for (i=0; i < num_rects; ++i) {
- stbrp__findresult fr = stbrp__skyline_pack_rectangle(context, rects[i].w, rects[i].h);
- if (fr.prev_link) {
- rects[i].x = (stbrp_coord) fr.x;
- rects[i].y = (stbrp_coord) fr.y;
+ if (rects[i].w == 0 || rects[i].h == 0) {
+ rects[i].x = rects[i].y = 0; // empty rect needs no space
} else {
- rects[i].x = rects[i].y = STBRP__MAXVAL;
+ stbrp__findresult fr = stbrp__skyline_pack_rectangle(context, rects[i].w, rects[i].h);
+ if (fr.prev_link) {
+ rects[i].x = (stbrp_coord) fr.x;
+ rects[i].y = (stbrp_coord) fr.y;
+ } else {
+ rects[i].x = rects[i].y = STBRP__MAXVAL;
+ }
}
}
diff --git a/src/stb_truetype.h b/src/stb_truetype.h
index bfb1841f..d360d609 100644
--- a/src/stb_truetype.h
+++ b/src/stb_truetype.h
@@ -1,4 +1,4 @@
-// stb_truetype.h - v1.09 - public domain
+// stb_truetype.h - v1.11 - public domain
// authored from 2009-2015 by Sean Barrett / RAD Game Tools
//
// This library processes TrueType files:
@@ -21,6 +21,10 @@
// Mikko Mononen: compound shape support, more cmap formats
// Tor Andersson: kerning, subpixel rendering
//
+// Misc other:
+// Ryan Gordon
+// Simon Glass
+//
// Bug/warning reports/fixes:
// "Zer" on mollyrocket (with fix)
// Cass Everitt
@@ -45,11 +49,10 @@
// Thomas Fields
// Derek Vinyard
//
-// Misc other:
-// Ryan Gordon
-//
// VERSION HISTORY
//
+// 1.11 (2016-04-02) fix unused-variable warning
+// 1.10 (2016-04-02) user-defined fabs(); rare memory leak; remove duplicate typedef
// 1.09 (2016-01-16) warning fix; avoid crash on outofmem; use allocation userdata properly
// 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges
// 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints;
@@ -68,9 +71,9 @@
//
// LICENSE
//
-// This software is in the public domain. Where that dedication is not
-// recognized, you are granted a perpetual, irrevocable license to copy,
-// distribute, and modify this file as you see fit.
+// This software is dual-licensed to the public domain and under the following
+// license: you are granted a perpetual, irrevocable license to copy, modify,
+// publish, and distribute this file as you see fit.
//
// USAGE
//
@@ -406,6 +409,11 @@ int main(int arg, char **argv)
#define STBTT_sqrt(x) sqrt(x)
#endif
+ #ifndef STBTT_fabs
+ #include <math.h>
+ #define STBTT_fabs(x) fabs(x)
+ #endif
+
// #define your own functions "STBTT_malloc" / "STBTT_free" to avoid malloc.h
#ifndef STBTT_malloc
#include <stdlib.h>
@@ -629,7 +637,7 @@ STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index);
// The following structure is defined publically so you can declare one on
// the stack or as a global or etc, but you should treat it as opaque.
-typedef struct stbtt_fontinfo
+struct stbtt_fontinfo
{
void * userdata;
unsigned char * data; // pointer to .ttf file
@@ -640,7 +648,7 @@ typedef struct stbtt_fontinfo
int loca,head,glyf,hhea,hmtx,kern; // table locations as offset from start of .ttf
int index_map; // a cmap mapping for our chosen character encoding
int indexToLocFormat; // format needed to map from glyph index to glyph
-} stbtt_fontinfo;
+};
STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset);
// Given an offset into the file that defines a font, this function builds
@@ -942,6 +950,12 @@ typedef int stbtt__test_oversample_pow2[(STBTT_MAX_OVERSAMPLE & (STBTT_MAX_OVERS
#define STBTT_RASTERIZER_VERSION 2
#endif
+#ifdef _MSC_VER
+#define STBTT__NOTUSED(v) (void)(v)
+#else
+#define STBTT__NOTUSED(v) (void)sizeof(v)
+#endif
+
//////////////////////////////////////////////////////////////////////////
//
// accessors to parse data from file
@@ -1993,7 +2007,7 @@ static void stbtt__fill_active_edges_new(float *scanline, float *scanline_fill,
}
y_crossing += dy * (x2 - (x1+1));
- STBTT_assert(fabs(area) <= 1.01f);
+ STBTT_assert(STBTT_fabs(area) <= 1.01f);
scanline[x2] += area + sign * (1-((x2-x2)+(x_bottom-x2))/2) * (sy1-y_crossing);
@@ -2071,6 +2085,8 @@ static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e,
int y,j=0, i;
float scanline_data[129], *scanline, *scanline2;
+ STBTT__NOTUSED(vsubsample);
+
if (result->w > 64)
scanline = (float *) STBTT_malloc((result->w*2+1) * sizeof(float), userdata);
else
@@ -2129,7 +2145,7 @@ static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e,
int m;
sum += scanline2[i];
k = scanline[i] + sum;
- k = (float) fabs(k)*255 + 0.5f;
+ k = (float) STBTT_fabs(k)*255 + 0.5f;
m = (int) k;
if (m > 255) m = 255;
result->pixels[j*result->stride + i] = (unsigned char) m;
@@ -2430,7 +2446,10 @@ STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info
if (scale_x == 0) scale_x = scale_y;
if (scale_y == 0) {
- if (scale_x == 0) return NULL;
+ if (scale_x == 0) {
+ STBTT_free(vertices, info->userdata);
+ return NULL;
+ }
scale_y = scale_x;
}
@@ -2586,11 +2605,6 @@ STBTT_DEF void stbtt_GetBakedQuad(stbtt_bakedchar *chardata, int pw, int ph, int
//
#ifndef STB_RECT_PACK_VERSION
-#ifdef _MSC_VER
-#define STBTT__NOTUSED(v) (void)(v)
-#else
-#define STBTT__NOTUSED(v) (void)sizeof(v)
-#endif
typedef int stbrp_coord;
@@ -3205,6 +3219,10 @@ STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *font_collection, const
// FULL VERSION HISTORY
//
+// 1.11 (2016-04-02) fix unused-variable warning
+// 1.10 (2016-04-02) allow user-defined fabs() replacement
+// fix memory leak if fontsize=0.0
+// fix warning from duplicate typedef
// 1.09 (2016-01-16) warning fix; avoid crash on outofmem; use alloc userdata for PackFontRanges
// 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges
// 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints;
diff --git a/src/stb_vorbis.c b/src/stb_vorbis.c
index 0510edc7..07d79318 100644
--- a/src/stb_vorbis.c
+++ b/src/stb_vorbis.c
@@ -168,6 +168,9 @@
#include <math.h>
#if !(defined(__APPLE__) || defined(MACOSX) || defined(macintosh) || defined(Macintosh))
#include <malloc.h>
+#if defined(__linux__) || defined(__linux) || defined(__EMSCRIPTEN__)
+#include <alloca.h>
+#endif
#endif
#else // STB_VORBIS_NO_CRT
#define NULL 0
@@ -1484,85 +1487,6 @@ static int codebook_decode_deinterleave_repeat(vorb *f, Codebook *c, float **out
return TRUE;
}
-#ifndef STB_VORBIS_DIVIDES_IN_CODEBOOK
-static int codebook_decode_deinterleave_repeat_2(vorb *f, Codebook *c, float **outputs, int *c_inter_p, int *p_inter_p, int len, int total_decode)
-{
- int c_inter = *c_inter_p;
- int p_inter = *p_inter_p;
- int i,z, effective = c->dimensions;
-
- // type 0 is only legal in a scalar context
- if (c->lookup_type == 0) return error(f, VORBIS_invalid_stream);
-
- while (total_decode > 0) {
- float last = CODEBOOK_ELEMENT_BASE(c);
- DECODE_VQ(z,f,c);
-
- if (z < 0) {
- if (!f->bytes_in_seg)
- if (f->last_seg) return FALSE;
- return error(f, VORBIS_invalid_stream);
- }
-
- // if this will take us off the end of the buffers, stop short!
- // we check by computing the length of the virtual interleaved
- // buffer (len*ch), our current offset within it (p_inter*ch)+(c_inter),
- // and the length we'll be using (effective)
- if (c_inter + p_inter*2 + effective > len * 2) {
- effective = len*2 - (p_inter*2 - c_inter);
- }
-
- {
- z *= c->dimensions;
- if (c->sequence_p) {
- // haven't optimized this case because I don't have any examples
- for (i=0; i < effective; ++i) {
- float val = CODEBOOK_ELEMENT_FAST(c,z+i) + last;
- if (outputs[c_inter])
- outputs[c_inter][p_inter] += val;
- if (++c_inter == 2) { c_inter = 0; ++p_inter; }
- last = val;
- }
- } else {
- i=0;
- if (c_inter == 1 && i < effective) {
- float val = CODEBOOK_ELEMENT_FAST(c,z+i) + last;
- if (outputs[c_inter])
- outputs[c_inter][p_inter] += val;
- c_inter = 0; ++p_inter;
- ++i;
- }
- {
- float *z0 = outputs[0];
- float *z1 = outputs[1];
- for (; i+1 < effective;) {
- float v0 = CODEBOOK_ELEMENT_FAST(c,z+i) + last;
- float v1 = CODEBOOK_ELEMENT_FAST(c,z+i+1) + last;
- if (z0)
- z0[p_inter] += v0;
- if (z1)
- z1[p_inter] += v1;
- ++p_inter;
- i += 2;
- }
- }
- if (i < effective) {
- float val = CODEBOOK_ELEMENT_FAST(c,z+i) + last;
- if (outputs[c_inter])
- outputs[c_inter][p_inter] += val;
- if (++c_inter == 2) { c_inter = 0; ++p_inter; }
- }
- }
- }
-
- total_decode -= effective;
- }
- *c_inter_p = c_inter;
- *p_inter_p = p_inter;
- return TRUE;
-}
-#endif
-
static int predict_point(int x, int x0, int x1, int y0, int y1)
{
int dy = y1 - y0;
@@ -1941,6 +1865,11 @@ static void decode_residue(vorb *f, float *residue_buffers[], int ch, int n, int
}
done:
CHECK(f);
+ #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE
+ temp_free(f,part_classdata);
+ #else
+ temp_free(f,classifications);
+ #endif
temp_alloc_restore(f,temp_alloc_point);
}
@@ -2586,6 +2515,7 @@ static void inverse_mdct(float *buffer, int n, vorb *f, int blocktype)
}
}
+ temp_free(f,buf2);
temp_alloc_restore(f,save_point);
}
@@ -3499,7 +3429,6 @@ static int start_decoder(vorb *f)
}
}
}
- setup_temp_free(f, mults,sizeof(mults[0])*c->lookup_values);
c->lookup_type = 2;
}
else
@@ -3515,11 +3444,11 @@ static int start_decoder(vorb *f)
if (c->sequence_p)
last = val;
}
- setup_temp_free(f, mults,sizeof(mults[0])*c->lookup_values);
}
#ifndef STB_VORBIS_DIVIDES_IN_CODEBOOK
skip:;
#endif
+ setup_temp_free(f, mults, sizeof(mults[0])*c->lookup_values);
CHECK(f);
}
@@ -4045,7 +3974,7 @@ int stb_vorbis_decode_frame_pushdata(
while (get8_packet(f) != EOP)
if (f->eof) break;
*samples = 0;
- return f->stream - data;
+ return (int) (f->stream - data);
}
if (error == VORBIS_continued_packet_flag_invalid) {
if (f->previous_length == 0) {
@@ -4055,7 +3984,7 @@ int stb_vorbis_decode_frame_pushdata(
while (get8_packet(f) != EOP)
if (f->eof) break;
*samples = 0;
- return f->stream - data;
+ return (int) (f->stream - data);
}
}
// if we get an error while parsing, what to do?
@@ -4075,7 +4004,7 @@ int stb_vorbis_decode_frame_pushdata(
if (channels) *channels = f->channels;
*samples = len;
*output = f->outputs;
- return f->stream - data;
+ return (int) (f->stream - data);
}
stb_vorbis *stb_vorbis_open_pushdata(
@@ -4098,7 +4027,7 @@ stb_vorbis *stb_vorbis_open_pushdata(
f = vorbis_alloc(&p);
if (f) {
*f = p;
- *data_used = f->stream - data;
+ *data_used = (int) (f->stream - data);
*error = 0;
return f;
} else {
@@ -4113,9 +4042,9 @@ unsigned int stb_vorbis_get_file_offset(stb_vorbis *f)
#ifndef STB_VORBIS_NO_PUSHDATA_API
if (f->push_mode) return 0;
#endif
- if (USE_MEMORY(f)) return f->stream - f->stream_start;
+ if (USE_MEMORY(f)) return (unsigned int) (f->stream - f->stream_start);
#ifndef STB_VORBIS_NO_STDIO
- return ftell(f->f) - f->f_start;
+ return (unsigned int) (ftell(f->f) - f->f_start);
#endif
}
@@ -4611,7 +4540,7 @@ stb_vorbis * stb_vorbis_open_file_section(FILE *file, int close_on_free, int *er
stb_vorbis *f, p;
vorbis_init(&p, alloc);
p.f = file;
- p.f_start = ftell(file);
+ p.f_start = (uint32) ftell(file);
p.stream_len = length;
p.close_on_free = close_on_free;
if (start_decoder(&p)) {
@@ -4630,9 +4559,9 @@ stb_vorbis * stb_vorbis_open_file_section(FILE *file, int close_on_free, int *er
stb_vorbis * stb_vorbis_open_file(FILE *file, int close_on_free, int *error, const stb_vorbis_alloc *alloc)
{
unsigned int len, start;
- start = ftell(file);
+ start = (unsigned int) ftell(file);
fseek(file, 0, SEEK_END);
- len = ftell(file) - start;
+ len = (unsigned int) (ftell(file) - start);
fseek(file, start, SEEK_SET);
return stb_vorbis_open_file_section(file, close_on_free, error, alloc, len);
}
@@ -5027,6 +4956,9 @@ int stb_vorbis_get_samples_float(stb_vorbis *f, int channels, float **buffer, in
#endif // STB_VORBIS_NO_PULLDATA_API
/* Version history
+ 1.09 - 2016/04/04 - back out 'avoid discarding last frame' fix from previous version
+ 1.08 - 2016/04/02 - fixed multiple warnings; fix setup memory leaks;
+ avoid discarding last frame of audio data
1.07 - 2015/01/16 - fixed some warnings, fix mingw, const-correct API
some more crash fixes when out of memory or with corrupt files
1.06 - 2015/08/31 - full, correct support for seeking API (Dougall Johnson)
diff --git a/src/stb_vorbis.h b/src/stb_vorbis.h
index bd318972..624ce4bc 100644
--- a/src/stb_vorbis.h
+++ b/src/stb_vorbis.h
@@ -1,4 +1,4 @@
-// Ogg Vorbis audio decoder - v1.07 - public domain
+// Ogg Vorbis audio decoder - v1.09 - public domain
// http://nothings.org/stb_vorbis/
//
// Original version written by Sean Barrett in 2007.
@@ -9,9 +9,9 @@
//
// LICENSE
//
-// This software is in the public domain. Where that dedication is not
-// recognized, you are granted a perpetual, irrevocable license to copy,
-// distribute, and modify this file as you see fit.
+// This software is dual-licensed to the public domain and under the following
+// license: you are granted a perpetual, irrevocable license to copy, modify,
+// publish, and distribute this file as you see fit.
//
// No warranty for any purpose is expressed or implied by the author (nor
// by RAD Game Tools). Report bugs and send enhancements to the author.
@@ -31,11 +31,14 @@
// Terje Mathisen Niklas Frykholm Andy Hill
// Casey Muratori John Bolton Gargaj
// Laurent Gomila Marc LeBlanc Ronny Chevalier
-// Bernhard Wodo Evan Balster "alxprd"@github
+// Bernhard Wodo Evan Balster alxprd@github
// Tom Beaumont Ingo Leitgeb Nicolas Guillemot
-// Phillip Bennefall Rohit
+// Phillip Bennefall Rohit Thiago Goulart
+// manxorist@github saga musix
//
// Partial history:
+// 1.09 - 2016/04/04 - back out 'truncation of last frame' fix from previous version
+// 1.08 - 2016/04/02 - warnings; setup memory leaks; truncation of last frame
// 1.07 - 2015/01/16 - fixes for crashes on invalid files; warning fixes; const
// 1.06 - 2015/08/31 - full, correct support for seeking API (Dougall Johnson)
// some crash fixes when out of memory or with corrupt files
@@ -72,11 +75,6 @@
#include "utils.h" // Android fopen function map
#endif
-// RaySan: Added for Linux
-#ifdef __linux
- #include <alloca.h>
-#endif
-
#ifdef __cplusplus
extern "C" {
#endif
diff --git a/src/text.c b/src/text.c
index f89d5e4e..7bb06f44 100644
--- a/src/text.c
+++ b/src/text.c
@@ -69,8 +69,7 @@ static SpriteFont defaultFont; // Default font provided by raylib
//----------------------------------------------------------------------------------
// Module specific Functions Declaration
//----------------------------------------------------------------------------------
-static bool PixelIsMagenta(Color p); // Check if a pixel is magenta
-static int ParseImageData(Image image, int **charValues, Rectangle **charSet); // Parse image pixel data to obtain characters data
+static SpriteFont LoadImageFont(Image image, Color key, int firstChar); // Load a Image font file (XNA style)
static SpriteFont LoadRBMF(const char *fileName); // Load a rBMF font file (raylib BitMap Font)
static SpriteFont LoadBMFont(const char *fileName); // Load a BMFont file (AngelCode font file)
static SpriteFont LoadTTF(const char *fileName, int fontSize); // Generate a sprite font image from TTF data (font size required)
@@ -210,7 +209,7 @@ extern void LoadDefaultFont(void)
if (testPosX >= defaultFont.texture.width)
{
currentLine++;
- currentPosX = 2 * charsDivisor + charsWidth[i];
+ currentPosX = 2*charsDivisor + charsWidth[i];
testPosX = currentPosX;
defaultFont.charRecs[i].x = charsDivisor;
@@ -246,7 +245,7 @@ SpriteFont GetDefaultFont()
// Load a SpriteFont image into GPU memory
SpriteFont LoadSpriteFont(const char *fileName)
{
- SpriteFont spriteFont;
+ SpriteFont spriteFont = { 0 };
// Check file extension
if (strcmp(GetExtension(fileName),"rbmf") == 0) spriteFont = LoadRBMF(fileName);
@@ -255,36 +254,7 @@ SpriteFont LoadSpriteFont(const char *fileName)
else
{
Image image = LoadImage(fileName);
-
- if (image.data != NULL)
- {
- // Process bitmap font pixel data to get characters measures
- // spriteFont chars data is filled inside the function and memory is allocated!
- int numChars = ParseImageData(image, &spriteFont.charValues, &spriteFont.charRecs);
-
- TraceLog(DEBUG, "[%s] SpriteFont data parsed correctly", fileName);
- TraceLog(DEBUG, "[%s] SpriteFont num chars detected: %i", fileName, numChars);
-
- spriteFont.numChars = numChars;
- spriteFont.texture = LoadTextureFromImage(image); // Convert loaded image to OpenGL texture
- spriteFont.size = spriteFont.charRecs[0].height;
-
- spriteFont.charOffsets = (Vector2 *)malloc(spriteFont.numChars*sizeof(Vector2));
- spriteFont.charAdvanceX = (int *)malloc(spriteFont.numChars*sizeof(int));
-
- for (int i = 0; i < spriteFont.numChars; i++)
- {
- // NOTE: On image based fonts (XNA style), character offsets and xAdvance are not required (set to 0)
- spriteFont.charOffsets[i] = (Vector2){ 0.0f, 0.0f };
- spriteFont.charAdvanceX[i] = 0;
- }
- }
- else
- {
- TraceLog(WARNING, "[%s] SpriteFont could not be loaded, using default font", fileName);
- spriteFont = GetDefaultFont();
- }
-
+ if (image.data != NULL) spriteFont = LoadImageFont(image, MAGENTA, 32);
UnloadImage(image);
}
@@ -309,7 +279,7 @@ void UnloadSpriteFont(SpriteFont spriteFont)
free(spriteFont.charOffsets);
free(spriteFont.charAdvanceX);
- TraceLog(INFO, "Unloaded sprite font data");
+ TraceLog(DEBUG, "Unloaded sprite font data");
}
}
@@ -517,15 +487,11 @@ void DrawFPS(int posX, int posY)
// Module specific Functions Definition
//----------------------------------------------------------------------------------
-// Check if a pixel is magenta
-static bool PixelIsMagenta(Color p)
-{
- return ((p.r == 255) && (p.g == 0) && (p.b == 255) && (p.a == 255));
-}
-
-// Parse image pixel data to obtain characters data (measures)
-static int ParseImageData(Image image, int **charValues, Rectangle **charRecs)
+// Load a Image font file (XNA style)
+static SpriteFont LoadImageFont(Image image, Color key, int firstChar)
{
+ #define COLOR_EQUAL(col1, col2) ((col1.r == col2.r)&&(col1.g == col2.g)&&(col1.b == col2.b)&&(col1.a == col2.a))
+
int charSpacing = 0;
int lineSpacing = 0;
@@ -539,13 +505,14 @@ static int ParseImageData(Image image, int **charValues, Rectangle **charRecs)
Color *pixels = GetImageData(image);
+ // Parse image data to get charSpacing and lineSpacing
for(y = 0; y < image.height; y++)
{
for(x = 0; x < image.width; x++)
{
- if (!PixelIsMagenta(pixels[y*image.width + x])) break;
+ if (!COLOR_EQUAL(pixels[y*image.width + x], key)) break;
}
- if (!PixelIsMagenta(pixels[y*image.width + x])) break;
+ if (!COLOR_EQUAL(pixels[y*image.width + x], key)) break;
}
charSpacing = x;
@@ -554,7 +521,7 @@ static int ParseImageData(Image image, int **charValues, Rectangle **charRecs)
int charHeight = 0;
int j = 0;
- while(!PixelIsMagenta(pixels[(lineSpacing + j)*image.width + charSpacing])) j++;
+ while(!COLOR_EQUAL(pixels[(lineSpacing + j)*image.width + charSpacing], key)) j++;
charHeight = j;
@@ -563,12 +530,13 @@ static int ParseImageData(Image image, int **charValues, Rectangle **charRecs)
int lineToRead = 0;
int xPosToRead = charSpacing;
+ // Parse image data to get rectangle sizes
while((lineSpacing + lineToRead * (charHeight + lineSpacing)) < image.height)
{
while((xPosToRead < image.width) &&
- !PixelIsMagenta((pixels[(lineSpacing + (charHeight+lineSpacing)*lineToRead)*image.width + xPosToRead])))
+ !COLOR_EQUAL((pixels[(lineSpacing + (charHeight+lineSpacing)*lineToRead)*image.width + xPosToRead]), key))
{
- tempCharValues[index] = FONT_FIRST_CHAR + index;
+ tempCharValues[index] = firstChar + index;
tempCharRecs[index].x = xPosToRead;
tempCharRecs[index].y = lineSpacing + lineToRead * (charHeight + lineSpacing);
@@ -576,7 +544,7 @@ static int ParseImageData(Image image, int **charValues, Rectangle **charRecs)
int charWidth = 0;
- while(!PixelIsMagenta(pixels[(lineSpacing + (charHeight+lineSpacing)*lineToRead)*image.width + xPosToRead + charWidth])) charWidth++;
+ while(!COLOR_EQUAL(pixels[(lineSpacing + (charHeight+lineSpacing)*lineToRead)*image.width + xPosToRead + charWidth], key)) charWidth++;
tempCharRecs[index].width = charWidth;
@@ -590,20 +558,35 @@ static int ParseImageData(Image image, int **charValues, Rectangle **charRecs)
}
free(pixels);
+
+ TraceLog(DEBUG, "SpriteFont data parsed correctly from image");
+
+ // Create spritefont with all data parsed from image
+ SpriteFont spriteFont = { 0 };
+
+ spriteFont.texture = LoadTextureFromImage(image); // Convert loaded image to OpenGL texture
+ spriteFont.numChars = index;
// We got tempCharValues and tempCharsRecs populated with chars data
- // Now we move temp data to sized charValues and charRecs arrays (passed as parameter to the function)
- // NOTE: This memory should be freed!
- (*charRecs) = (Rectangle *)malloc(index*sizeof(Rectangle));
- (*charValues) = (int *)malloc(index*sizeof(int));
+ // Now we move temp data to sized charValues and charRecs arrays
+ spriteFont.charRecs = (Rectangle *)malloc(spriteFont.numChars*sizeof(Rectangle));
+ spriteFont.charValues = (int *)malloc(spriteFont.numChars*sizeof(int));
+ spriteFont.charOffsets = (Vector2 *)malloc(spriteFont.numChars*sizeof(Vector2));
+ spriteFont.charAdvanceX = (int *)malloc(spriteFont.numChars*sizeof(int));
- for (int i = 0; i < index; i++)
+ for (int i = 0; i < spriteFont.numChars; i++)
{
- (*charValues)[i] = tempCharValues[i];
- (*charRecs)[i] = tempCharRecs[i];
+ spriteFont.charValues[i] = tempCharValues[i];
+ spriteFont.charRecs[i] = tempCharRecs[i];
+
+ // NOTE: On image based fonts (XNA style), character offsets and xAdvance are not required (set to 0)
+ spriteFont.charOffsets[i] = (Vector2){ 0.0f, 0.0f };
+ spriteFont.charAdvanceX[i] = 0;
}
+
+ spriteFont.size = spriteFont.charRecs[0].height;
- return index;
+ return spriteFont;
}
// Load a rBMF font file (raylib BitMap Font)
@@ -687,6 +670,7 @@ static SpriteFont LoadRBMF(const char *fileName)
TraceLog(DEBUG, "[%s] Image reconstructed correctly, now converting it to texture", fileName);
+ // Create spritefont with all data read from rbmf file
spriteFont.texture = LoadTextureFromImage(image);
UnloadImage(image); // Unload image data
diff --git a/src/textures.c b/src/textures.c
index e649df57..79047ab7 100644
--- a/src/textures.c
+++ b/src/textures.c
@@ -29,21 +29,21 @@
#include "raylib.h"
-#include <stdlib.h> // Declares malloc() and free() for memory management
-#include <string.h> // Required for strcmp(), strrchr(), strncmp()
+#include <stdlib.h> // Declares malloc() and free() for memory management
+#include <string.h> // Required for strcmp(), strrchr(), strncmp()
-#include "rlgl.h" // raylib OpenGL abstraction layer to OpenGL 1.1, 3.3 or ES2
- // Required: rlglLoadTexture() rlDeleteTextures(),
- // rlglGenerateMipmaps(), some funcs for DrawTexturePro()
+#include "rlgl.h" // raylib OpenGL abstraction layer to OpenGL 1.1, 3.3 or ES2
+ // Required: rlglLoadTexture() rlDeleteTextures(),
+ // rlglGenerateMipmaps(), some funcs for DrawTexturePro()
-#include "utils.h" // rRES data decompression utility function
- // NOTE: Includes Android fopen function map
+#include "utils.h" // rRES data decompression utility function
+ // NOTE: Includes Android fopen function map
#define STB_IMAGE_IMPLEMENTATION
-#include "stb_image.h" // Used to read image data (multiple formats support)
+#include "stb_image.h" // Used to read image data (multiple formats support)
#define STB_IMAGE_RESIZE_IMPLEMENTATION
-#include "stb_image_resize.h"
+#include "stb_image_resize.h" // Used on image scaling function: ImageResize()
//----------------------------------------------------------------------------------
// Defines and Macros
@@ -130,6 +130,7 @@ Image LoadImage(const char *fileName)
}
// Load image data from Color array data (RGBA - 32bit)
+// NOTE: Creates a copy of pixels data array
Image LoadImageEx(Color *pixels, int width, int height)
{
Image image;
@@ -388,6 +389,14 @@ Texture2D LoadTextureFromImage(Image image)
return texture;
}
+// Load a texture to be used for rendering
+RenderTexture2D LoadRenderTexture(int width, int height)
+{
+ RenderTexture2D target = rlglLoadRenderTexture(width, height);
+
+ return target;
+}
+
// Unload image from CPU memory (RAM)
void UnloadImage(Image image)
{
@@ -408,6 +417,17 @@ void UnloadTexture(Texture2D texture)
}
}
+// Unload render texture from GPU memory
+void UnloadRenderTexture(RenderTexture2D target)
+{
+ if (target.id != 0)
+ {
+ rlDeleteRenderTextures(target);
+
+ TraceLog(INFO, "[FBO ID %i] Unloaded render texture data from VRAM (GPU)", target.id);
+ }
+}
+
// Get pixel data from image in the form of Color struct array
Color *GetImageData(Image image)
{
@@ -1365,6 +1385,10 @@ void DrawTextureEx(Texture2D texture, Vector2 position, float rotation, float sc
void DrawTextureRec(Texture2D texture, Rectangle sourceRec, Vector2 position, Color tint)
{
Rectangle destRec = { (int)position.x, (int)position.y, abs(sourceRec.width), abs(sourceRec.height) };
+
+ if (sourceRec.width < 0) sourceRec.x -= sourceRec.width;
+ if (sourceRec.height < 0) sourceRec.y -= sourceRec.height;
+
Vector2 origin = { 0, 0 };
DrawTexturePro(texture, sourceRec, destRec, origin, 0.0f, tint);
diff --git a/src/windows_compile.bat b/src/windows_compile.bat
new file mode 100644
index 00000000..f1d0fb29
--- /dev/null
+++ b/src/windows_compile.bat
@@ -0,0 +1,2 @@
+set PATH=C:\raylib\MinGW\bin;%PATH%
+mingw32-make \ No newline at end of file