- Primer commit de l'esperada seqüela de Cacaus
This commit is contained in:
493
source/japi/audio.cpp
Normal file
493
source/japi/audio.cpp
Normal file
@@ -0,0 +1,493 @@
|
||||
#include "audio.h"
|
||||
#include "file.h"
|
||||
#include "stb_vorbis.h"
|
||||
#include <SDL3/SDL.h>
|
||||
#include <stdio.h>
|
||||
|
||||
namespace audio {
|
||||
|
||||
#define MAX_CHANNELS 5
|
||||
|
||||
struct sound
|
||||
{
|
||||
SDL_AudioSpec spec { SDL_AUDIO_S16, 2, 48000 };
|
||||
Uint32 length { 0 };
|
||||
Uint8 *buffer { NULL };
|
||||
};
|
||||
|
||||
struct channel
|
||||
{
|
||||
audio::sound *sound { nullptr };
|
||||
int pos { 0 };
|
||||
int times { 0 };
|
||||
SDL_AudioStream *stream { nullptr };
|
||||
audio::state state { audio::state::available };
|
||||
};
|
||||
|
||||
struct music
|
||||
{
|
||||
SDL_AudioSpec spec { SDL_AUDIO_S16, 2, 48000 };
|
||||
Uint32 length { 0 };
|
||||
Uint8 *buffer { nullptr };
|
||||
char *filename { nullptr };
|
||||
|
||||
int pos { 0 };
|
||||
int times { 0 };
|
||||
SDL_AudioStream *stream { nullptr };
|
||||
audio::state state { audio::state::invalid };
|
||||
};
|
||||
|
||||
audio::music *current_music { nullptr };
|
||||
audio::channel channels[MAX_CHANNELS];
|
||||
|
||||
SDL_AudioSpec audio_spec { SDL_AUDIO_S16, 2, 48000 };
|
||||
float music_volume { 1.0f };
|
||||
float sound_volume { 0.5f };
|
||||
bool music_enabled { true };
|
||||
bool sound_enabled { true };
|
||||
SDL_AudioDeviceID audio_device { 0 };
|
||||
SDL_TimerID timer_id { 0 };
|
||||
|
||||
bool fading { false };
|
||||
int fade_start_time;
|
||||
int fade_duration;
|
||||
int fade_initial_volume;
|
||||
|
||||
Uint32 updateCallback(void *userdata, SDL_TimerID timer_id, Uint32 interval)
|
||||
{
|
||||
if (music_enabled && current_music && current_music->state == audio::state::playing)
|
||||
{
|
||||
if (fading)
|
||||
{
|
||||
const int time = SDL_GetTicks();
|
||||
if (time > (fade_start_time+fade_duration)) {
|
||||
fading = false;
|
||||
stopMusic();
|
||||
return 30;
|
||||
} else {
|
||||
const int time_passed = time - fade_start_time;
|
||||
const float percent = (float)time_passed / (float)fade_duration;
|
||||
SDL_SetAudioStreamGain(current_music->stream, music_volume*(1.0 - percent));
|
||||
}
|
||||
}
|
||||
|
||||
if (current_music->times != 0)
|
||||
{
|
||||
if (SDL_GetAudioStreamAvailable(current_music->stream) < int(current_music->length/2))
|
||||
{
|
||||
SDL_PutAudioStreamData(current_music->stream, current_music->buffer, current_music->length);
|
||||
}
|
||||
if (current_music->times>0) current_music->times--;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (SDL_GetAudioStreamAvailable(current_music->stream) == 0) stopMusic();
|
||||
}
|
||||
}
|
||||
|
||||
if (sound_enabled)
|
||||
{
|
||||
for (int i=0; i < MAX_CHANNELS; ++i)
|
||||
if (channels[i].state == audio::state::playing)
|
||||
{
|
||||
if (channels[i].times != 0)
|
||||
{
|
||||
if (SDL_GetAudioStreamAvailable(channels[i].stream) < int(channels[i].sound->length/2))
|
||||
{
|
||||
SDL_PutAudioStreamData(channels[i].stream, channels[i].sound->buffer, channels[i].sound->length);
|
||||
if (channels[i].times>0) channels[i].times--;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (SDL_GetAudioStreamAvailable(channels[i].stream) == 0) stopChannel(i);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return 30;
|
||||
}
|
||||
|
||||
void init(const int freq, const SDL_AudioFormat format, const int num_channels)
|
||||
{
|
||||
#ifdef DEBUG
|
||||
SDL_SetLogPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_DEBUG);
|
||||
#endif
|
||||
|
||||
SDL_Log("Iniciant JailAudio...");
|
||||
audio_spec = {format, num_channels, freq };
|
||||
if (!audio_device) SDL_CloseAudioDevice(audio_device);
|
||||
audio_device = SDL_OpenAudioDevice(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &audio_spec);
|
||||
SDL_Log( (audio_device==0) ? "Failed to initialize SDL audio!\n" : "OK!\n");
|
||||
for (int i=0;i<MAX_CHANNELS;++i) channels[i].state = audio::state::available;
|
||||
timer_id = SDL_AddTimer(30, updateCallback, nullptr);
|
||||
}
|
||||
|
||||
void quit()
|
||||
{
|
||||
if (timer_id) SDL_RemoveTimer(timer_id);
|
||||
|
||||
if (!audio_device) SDL_CloseAudioDevice(audio_device);
|
||||
audio_device = 0;
|
||||
}
|
||||
|
||||
audio::music *loadMusic(Uint8* buffer, Uint32 length)
|
||||
{
|
||||
audio::music *music = new audio::music();
|
||||
|
||||
int chan, samplerate;
|
||||
short *output;
|
||||
music->length = stb_vorbis_decode_memory(buffer, length, &chan, &samplerate, &output) * chan * 2;
|
||||
|
||||
music->spec.channels = chan;
|
||||
music->spec.freq = samplerate;
|
||||
music->spec.format = SDL_AUDIO_S16;
|
||||
music->buffer = (Uint8*)SDL_malloc(music->length);
|
||||
SDL_memcpy(music->buffer, output, music->length);
|
||||
free(output);
|
||||
music->pos = 0;
|
||||
music->state = audio::state::stopped;
|
||||
|
||||
return music;
|
||||
}
|
||||
|
||||
audio::music *loadMusic(const char* filename)
|
||||
{
|
||||
int size;
|
||||
char *buffer = file::getFileBuffer(filename, size);
|
||||
if (buffer == nullptr) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_ERROR, "ERROR (audio::loadMusic): No s'ha trobat l'arxiu %s\n", filename);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
audio::music *music = loadMusic((Uint8*)buffer, size);
|
||||
|
||||
music->filename = (char*)malloc(strlen(filename)+1);
|
||||
strcpy(music->filename, filename);
|
||||
|
||||
free(buffer);
|
||||
|
||||
return music;
|
||||
}
|
||||
|
||||
void playMusic(audio::music *music, const int loop)
|
||||
{
|
||||
if (!music_enabled) return;
|
||||
|
||||
stopMusic();
|
||||
|
||||
current_music = music;
|
||||
current_music->pos = 0;
|
||||
current_music->state = audio::state::playing;
|
||||
current_music->times = loop;
|
||||
|
||||
current_music->stream = SDL_CreateAudioStream(¤t_music->spec, &audio_spec);
|
||||
if (!current_music->stream) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_ERROR, "ERROR (audio::playMusic): SDL_CreateAudioStream: %s\n", SDL_GetError());
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (!SDL_PutAudioStreamData(current_music->stream, current_music->buffer, current_music->length)) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_ERROR, "ERROR (audio::playMusic): SDL_PutAudioStreamData: %s\n", SDL_GetError());
|
||||
exit(1);
|
||||
}
|
||||
|
||||
SDL_SetAudioStreamGain(current_music->stream, music_volume);
|
||||
|
||||
if (!SDL_BindAudioStream(audio_device, current_music->stream)) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_ERROR, "ERROR (audio::playMusic): SDL_BindAudioStream: %s\n", SDL_GetError());
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
void loadAndPlayMusic(const char* filename, const bool force_reinit)
|
||||
{
|
||||
if ( (getMusicState() != audio::state::playing) || (strcmp(getMusicFilename(), filename)!=0) || force_reinit )
|
||||
{
|
||||
playMusic(loadMusic(filename));
|
||||
}
|
||||
}
|
||||
|
||||
char *getMusicFilename(audio::music *music)
|
||||
{
|
||||
if (!music) music = current_music;
|
||||
return music->filename;
|
||||
}
|
||||
|
||||
void pauseMusic()
|
||||
{
|
||||
if (!music_enabled) return;
|
||||
if (!current_music || current_music->state == audio::state::invalid) return;
|
||||
|
||||
current_music->state = audio::state::paused;
|
||||
SDL_UnbindAudioStream(current_music->stream);
|
||||
}
|
||||
|
||||
void resumeMusic()
|
||||
{
|
||||
if (!music_enabled) return;
|
||||
if (!current_music || current_music->state == audio::state::invalid) return;
|
||||
|
||||
current_music->state = audio::state::playing;
|
||||
SDL_BindAudioStream(audio_device, current_music->stream);
|
||||
}
|
||||
|
||||
void stopMusic()
|
||||
{
|
||||
if (!music_enabled) return;
|
||||
if (!current_music || current_music->state == audio::state::invalid) return;
|
||||
|
||||
current_music->pos = 0;
|
||||
current_music->state = audio::state::stopped;
|
||||
SDL_DestroyAudioStream(current_music->stream);
|
||||
current_music->stream = nullptr;
|
||||
free(current_music->filename);
|
||||
current_music->filename = nullptr;
|
||||
}
|
||||
|
||||
void fadeOutMusic(const int milliseconds)
|
||||
{
|
||||
if (!music_enabled) return;
|
||||
if (current_music == NULL || current_music->state == audio::state::invalid) return;
|
||||
|
||||
fading = true;
|
||||
fade_start_time = SDL_GetTicks();
|
||||
fade_duration = milliseconds;
|
||||
fade_initial_volume = music_volume;
|
||||
}
|
||||
|
||||
audio::state getMusicState()
|
||||
{
|
||||
if (!music_enabled) return audio::state::disabled;
|
||||
if (!current_music) return audio::state::invalid;
|
||||
|
||||
return current_music->state;
|
||||
}
|
||||
|
||||
void deleteMusic(audio::music *music)
|
||||
{
|
||||
if (current_music == music) current_music = nullptr;
|
||||
SDL_free(music->buffer);
|
||||
if (music->stream) SDL_DestroyAudioStream(music->stream);
|
||||
delete music;
|
||||
}
|
||||
|
||||
float setMusicVolume(float volume)
|
||||
{
|
||||
music_volume = SDL_clamp( volume, 0.0f, 1.0f );
|
||||
if (current_music) SDL_SetAudioStreamGain(current_music->stream, music_volume);
|
||||
return music_volume;
|
||||
}
|
||||
|
||||
void setMusicPosition(float value)
|
||||
{
|
||||
if (!current_music) return;
|
||||
current_music->pos = value * current_music->spec.freq;
|
||||
}
|
||||
|
||||
float getMusicPosition()
|
||||
{
|
||||
if (!current_music) return 0;
|
||||
return float(current_music->pos)/float(current_music->spec.freq);
|
||||
}
|
||||
|
||||
void enableMusic(const bool value)
|
||||
{
|
||||
if ( !value && current_music && (current_music->state==audio::state::playing) ) stopMusic();
|
||||
|
||||
music_enabled = value;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
audio::sound *newSound(Uint8* buffer, Uint32 length)
|
||||
{
|
||||
audio::sound *sound = new audio::sound();
|
||||
sound->buffer = buffer;
|
||||
sound->length = length;
|
||||
return sound;
|
||||
}
|
||||
|
||||
audio::sound *loadSound(uint8_t* buffer, uint32_t size)
|
||||
{
|
||||
audio::sound *sound = new audio::sound();
|
||||
SDL_LoadWAV_IO(SDL_IOFromMem(buffer, size),1, &sound->spec, &sound->buffer, &sound->length);
|
||||
|
||||
return sound;
|
||||
}
|
||||
|
||||
audio::sound *loadSound(const char* filename)
|
||||
{
|
||||
int size;
|
||||
uint8_t *buffer = (uint8_t *)file::getFileBuffer(filename, size);
|
||||
|
||||
if (buffer == nullptr) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_ERROR, "ERROR (audio::loadSound): No s'ha trobat l'arxiu %s\n", filename);
|
||||
exit(1);
|
||||
}
|
||||
audio::sound *sound = loadSound(buffer, size);
|
||||
free(buffer);
|
||||
return sound;
|
||||
}
|
||||
|
||||
int playSound(audio::sound *sound, const int loop)
|
||||
{
|
||||
if (!sound_enabled) return -1;
|
||||
|
||||
int channel = 0;
|
||||
while (channel < MAX_CHANNELS && channels[channel].state != audio::state::available) { channel++; }
|
||||
if (channel == MAX_CHANNELS) channel = 0;
|
||||
stopChannel(channel);
|
||||
|
||||
channels[channel].sound = sound;
|
||||
channels[channel].times = loop;
|
||||
channels[channel].pos = 0;
|
||||
channels[channel].state = audio::state::playing;
|
||||
channels[channel].stream = SDL_CreateAudioStream(&channels[channel].sound->spec, &audio_spec);
|
||||
SDL_PutAudioStreamData(channels[channel].stream, channels[channel].sound->buffer, channels[channel].sound->length);
|
||||
SDL_SetAudioStreamGain(channels[channel].stream, sound_volume);
|
||||
SDL_BindAudioStream(audio_device, channels[channel].stream);
|
||||
|
||||
return channel;
|
||||
}
|
||||
|
||||
int playSoundOnChannel(audio::sound *sound, const int channel, const int loop)
|
||||
{
|
||||
if (!sound_enabled) return -1;
|
||||
|
||||
if (channel < 0 || channel >= MAX_CHANNELS) return -1;
|
||||
stopChannel(channel);
|
||||
|
||||
channels[channel].sound = sound;
|
||||
channels[channel].times = loop;
|
||||
channels[channel].pos = 0;
|
||||
channels[channel].state = audio::state::playing;
|
||||
channels[channel].stream = SDL_CreateAudioStream(&channels[channel].sound->spec, &audio_spec);
|
||||
SDL_PutAudioStreamData(channels[channel].stream, channels[channel].sound->buffer, channels[channel].sound->length);
|
||||
SDL_SetAudioStreamGain(channels[channel].stream, sound_volume);
|
||||
SDL_BindAudioStream(audio_device, channels[channel].stream);
|
||||
|
||||
return channel;
|
||||
}
|
||||
|
||||
void deleteSound(audio::sound *sound)
|
||||
{
|
||||
for (int i = 0; i < MAX_CHANNELS; i++) {
|
||||
if (channels[i].sound == sound) stopChannel(i);
|
||||
}
|
||||
SDL_free(sound->buffer);
|
||||
delete sound;
|
||||
}
|
||||
|
||||
void pauseChannel(const int channel)
|
||||
{
|
||||
if (!sound_enabled) return;
|
||||
|
||||
if (channel == -1)
|
||||
{
|
||||
for (int i = 0; i < MAX_CHANNELS; i++)
|
||||
if (channels[i].state == audio::state::playing)
|
||||
{
|
||||
channels[i].state = audio::state::paused;
|
||||
SDL_UnbindAudioStream(channels[i].stream);
|
||||
}
|
||||
}
|
||||
else if (channel >= 0 && channel < MAX_CHANNELS)
|
||||
{
|
||||
if (channels[channel].state == audio::state::playing)
|
||||
{
|
||||
channels[channel].state = audio::state::paused;
|
||||
SDL_UnbindAudioStream(channels[channel].stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void resumeChannel(const int channel)
|
||||
{
|
||||
if (!sound_enabled) return;
|
||||
|
||||
if (channel == -1)
|
||||
{
|
||||
for (int i = 0; i < MAX_CHANNELS; i++)
|
||||
if (channels[i].state == audio::state::paused)
|
||||
{
|
||||
channels[i].state = audio::state::playing;
|
||||
SDL_BindAudioStream(audio_device, channels[i].stream);
|
||||
}
|
||||
}
|
||||
else if (channel >= 0 && channel < MAX_CHANNELS)
|
||||
{
|
||||
if (channels[channel].state == audio::state::paused)
|
||||
{
|
||||
channels[channel].state = audio::state::playing;
|
||||
SDL_BindAudioStream(audio_device, channels[channel].stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void stopChannel(const int channel)
|
||||
{
|
||||
if (!sound_enabled) return;
|
||||
|
||||
if (channel == -1)
|
||||
{
|
||||
for (int i = 0; i < MAX_CHANNELS; i++) {
|
||||
if (channels[i].state != audio::state::available) SDL_DestroyAudioStream(channels[i].stream);
|
||||
channels[i].stream = nullptr;
|
||||
channels[i].state = audio::state::available;
|
||||
channels[i].pos = 0;
|
||||
channels[i].sound = NULL;
|
||||
}
|
||||
}
|
||||
else if (channel >= 0 && channel < MAX_CHANNELS)
|
||||
{
|
||||
if (channels[channel].state != audio::state::available) SDL_DestroyAudioStream(channels[channel].stream);
|
||||
channels[channel].stream = nullptr;
|
||||
channels[channel].state = audio::state::available;
|
||||
channels[channel].pos = 0;
|
||||
channels[channel].sound = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
audio::state getChannelState(const int channel)
|
||||
{
|
||||
if (!sound_enabled) return audio::state::disabled;
|
||||
|
||||
if (channel < 0 || channel >= MAX_CHANNELS) return audio::state::invalid;
|
||||
|
||||
return channels[channel].state;
|
||||
}
|
||||
|
||||
float setSoundVolume(float volume)
|
||||
{
|
||||
sound_volume = SDL_clamp( volume, 0.0f, 1.0f );
|
||||
|
||||
for (int i = 0; i < MAX_CHANNELS; i++)
|
||||
if ( (channels[i].state == audio::state::playing) || (channels[i].state == audio::state::paused) )
|
||||
SDL_SetAudioStreamGain(channels[i].stream, sound_volume);
|
||||
|
||||
return sound_volume;
|
||||
}
|
||||
|
||||
void enableSound(const bool value)
|
||||
{
|
||||
for (int i = 0; i < MAX_CHANNELS; i++)
|
||||
{
|
||||
if (channels[i].state == audio::state::playing) stopChannel(i);
|
||||
}
|
||||
sound_enabled = value;
|
||||
}
|
||||
|
||||
float setVolume(float volume)
|
||||
{
|
||||
setSoundVolume(setMusicVolume(volume) / 2.0f);
|
||||
|
||||
return music_volume;
|
||||
}
|
||||
|
||||
#undef MAX_CHANNELS
|
||||
|
||||
}
|
||||
44
source/japi/audio.h
Normal file
44
source/japi/audio.h
Normal file
@@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
namespace audio
|
||||
{
|
||||
enum state { invalid, available, playing, paused, stopped, disabled };
|
||||
|
||||
struct sound;
|
||||
struct music;
|
||||
|
||||
void init(const int freq, const SDL_AudioFormat format, const int num_channels);
|
||||
void quit();
|
||||
|
||||
audio::music *loadMusic(const char* filename);
|
||||
audio::music *loadMusic(Uint8* buffer, Uint32 length);
|
||||
void playMusic(audio::music *music, const int loop = -1);
|
||||
void loadAndPlayMusic(const char* filename, const bool force_reinit=false);
|
||||
char *getMusicFilename(audio::music *music = nullptr);
|
||||
void pauseMusic();
|
||||
void resumeMusic();
|
||||
void stopMusic();
|
||||
void fadeOutMusic(const int milliseconds=250);
|
||||
audio::state getMusicState();
|
||||
void deleteMusic(audio::music *music);
|
||||
float setMusicVolume(float volume);
|
||||
void setMusicPosition(float value);
|
||||
float getMusicPosition();
|
||||
void enableMusic(const bool value);
|
||||
|
||||
audio::sound *newSound(Uint8* buffer, Uint32 length);
|
||||
audio::sound *loadSound(Uint8* buffer, Uint32 length);
|
||||
audio::sound *loadSound(const char* filename);
|
||||
int playSound(audio::sound *sound, const int loop = 0);
|
||||
int playSoundOnChannel(audio::sound *sound, const int channel, const int loop = 0);
|
||||
void pauseChannel(const int channel);
|
||||
void resumeChannel(const int channel);
|
||||
void stopChannel(const int channel);
|
||||
audio::state getChannelState(const int channel);
|
||||
void deleteSound(audio::sound *sound);
|
||||
float setSoundVolume(float volume);
|
||||
void enableSound(const bool value);
|
||||
|
||||
float setVolume(float volume);
|
||||
}
|
||||
715
source/japi/draw.cpp
Normal file
715
source/japi/draw.cpp
Normal file
@@ -0,0 +1,715 @@
|
||||
#include "draw.h"
|
||||
#include <SDL3/SDL.h>
|
||||
#include "gif.h"
|
||||
#include "file.h"
|
||||
#include "shader.h"
|
||||
|
||||
namespace draw
|
||||
{
|
||||
// La idea de esta unitat es usar "superficies", que no son mes que arrays de bytes, per a anar pintant.
|
||||
// El resultat final s'ha de pintar en algun moment a la superficie "screen" (o siga, especificar nullptr en setDestination)
|
||||
// Aleshores, en "render" el contingut de screen se volca a la textura SDL que crearem,
|
||||
// i eixa textura se pintarà a pantalla com se sol fer amb SDL. Ho anirem veient en el codi.
|
||||
|
||||
SDL_Window *sdl_window {nullptr}; // La finestra de SDL
|
||||
SDL_Renderer *sdl_renderer {nullptr}; // El renderer de SDL
|
||||
SDL_Texture *sdl_texture {nullptr}; // La textura de SDL a la que pintarem la nostra superficie "screen" i que despres volcarem a pantalla
|
||||
SDL_Texture *sdl_shadertex {nullptr}; // La textura de SDL per al shader
|
||||
|
||||
static int screen_zoom = 1;
|
||||
static bool screen_fullscreen = false;
|
||||
static bool screen_cursor = true;
|
||||
static char* screen_shader = nullptr;
|
||||
static bool shader_enabled = false;
|
||||
static float window_ratio = 1;
|
||||
static int canvas_width;
|
||||
static int canvas_height;
|
||||
static int desktop_width;
|
||||
static int desktop_height;
|
||||
static int window_width;
|
||||
static int window_height;
|
||||
static int offset_x = 0;
|
||||
static int offset_y = 0;
|
||||
char window_title[256];
|
||||
|
||||
surface *screen {nullptr}; // La superficie screen, que representa la pantalla. Se crea i destrueix internament
|
||||
surface *destination {nullptr}; // Punter a la actual superficie de destí
|
||||
surface *source {nullptr}; // Punter a la actual superficie d'oritge
|
||||
surface *pushed_source {nullptr}; // Punter a la superficie d'oritge que s'ha pushat
|
||||
|
||||
uint32_t palette[256]; // La paleta de colors
|
||||
uint32_t aux_palette[256]; // La paleta de colors, para els fadein
|
||||
uint8_t color_indices[256]; // Indices dels colors per defecte
|
||||
uint8_t sel_color {0}; // Color seleccionat per defecte
|
||||
uint8_t transparent {0}; // El color transparent
|
||||
|
||||
//surface *textsurf = nullptr; // Surface on guardar el gif amb la font
|
||||
|
||||
SDL_Rect viewport;
|
||||
|
||||
bool fading_out = false;
|
||||
bool fading_in = false;
|
||||
|
||||
void createDisplay()
|
||||
{
|
||||
// Ajustem el zoom i el ratio, per si tenen valors locs
|
||||
if (screen_zoom <= 0) screen_zoom = 1;
|
||||
if (window_ratio <= 0) window_ratio = 1;
|
||||
|
||||
// Ajustem el tamany de la finestra, segons el zoom i el ratio
|
||||
window_width = canvas_width*screen_zoom;
|
||||
window_height = window_ratio != 1 ? int(float(canvas_width)*window_ratio*float(screen_zoom)) : canvas_height*screen_zoom;
|
||||
|
||||
// Mentres no càpiga en la pantalla, reduïm el zoom
|
||||
while (window_width > desktop_width || window_height > desktop_height) {
|
||||
screen_zoom--;
|
||||
window_width = canvas_width*screen_zoom;
|
||||
window_height = window_ratio != 1 ? int(float(canvas_width)*window_ratio*float(screen_zoom)) : canvas_height*screen_zoom;
|
||||
}
|
||||
|
||||
if (screen_fullscreen) {
|
||||
if (desktop_width * window_ratio > desktop_height) {
|
||||
offset_y = 0;
|
||||
window_height = desktop_height;
|
||||
window_width = desktop_height/window_ratio;
|
||||
offset_x = (desktop_width - window_width)/2;
|
||||
} else {
|
||||
offset_x = 0;
|
||||
window_width = desktop_width;
|
||||
window_height = desktop_width*window_ratio;
|
||||
offset_y = (desktop_height - window_height)/2;
|
||||
}
|
||||
} else {
|
||||
offset_x = offset_y = 0;
|
||||
}
|
||||
|
||||
sdl_window = SDL_CreateWindow(window_title, window_width, window_height, SDL_WINDOW_OPENGL|(screen_fullscreen?SDL_WINDOW_FULLSCREEN:0));
|
||||
if (!sdl_window) {
|
||||
SDL_LogCritical(SDL_LOG_CATEGORY_VIDEO, "ERROR (draw::init): Failed to initialize window!\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
sdl_renderer = SDL_CreateRenderer(sdl_window, NULL);
|
||||
if (!sdl_renderer) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "ERROR (draw::init): Failed to initialize renderer!\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (screen_cursor) SDL_ShowCursor(); else SDL_HideCursor();
|
||||
|
||||
sdl_texture = SDL_CreateTexture(sdl_renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, canvas_width, canvas_height);
|
||||
SDL_SetTextureScaleMode(sdl_texture, SDL_SCALEMODE_NEAREST);
|
||||
if (!sdl_texture) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "ERROR (draw::init): Failed to initialize texture!\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
sdl_shadertex = SDL_CreateTexture(sdl_renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_TARGET, window_width, window_height);
|
||||
SDL_SetTextureScaleMode(sdl_shadertex, SDL_SCALEMODE_NEAREST);
|
||||
|
||||
loadShader();
|
||||
}
|
||||
|
||||
void destroyDisplay()
|
||||
{
|
||||
SDL_DestroyTexture(sdl_texture);
|
||||
SDL_DestroyRenderer(sdl_renderer);
|
||||
SDL_DestroyWindow(sdl_window);
|
||||
}
|
||||
|
||||
// Inicialització de tot el que fa falta per a carregar gràfics i pintar en pantalla
|
||||
void init(const char *titol, const uint16_t width, const uint16_t height, const int zoom, const bool fullscreen, const float ratio)
|
||||
{
|
||||
screen_zoom = file::getConfigValueInteger("zoom", zoom);
|
||||
screen_fullscreen = file::getConfigValueBool("fullscreen", fullscreen);
|
||||
window_ratio = ratio;
|
||||
canvas_width = width;
|
||||
canvas_height = height;
|
||||
strcpy(window_title, titol);
|
||||
|
||||
const SDL_DisplayMode *dm = SDL_GetDesktopDisplayMode(SDL_GetPrimaryDisplay());
|
||||
if (!dm)
|
||||
{
|
||||
SDL_Log("SDL_GetDesktopDisplayMode failed: %s", SDL_GetError());
|
||||
exit(1);
|
||||
}
|
||||
desktop_width = dm->w;
|
||||
desktop_height = dm->h;
|
||||
|
||||
createDisplay();
|
||||
// Inicialització de les estructures de SDL
|
||||
/*
|
||||
sdl_window = SDL_CreateWindow(titol, width * zoom, height * zoom, 0);
|
||||
if (!sdl_window) {
|
||||
SDL_LogCritical(SDL_LOG_CATEGORY_VIDEO, "ERROR (draw::init): Failed to initialize window!\n");
|
||||
exit(1);
|
||||
}
|
||||
sdl_renderer = SDL_CreateRenderer(sdl_window, NULL);
|
||||
if (!sdl_renderer) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "ERROR (draw::init): Failed to initialize renderer!\n");
|
||||
exit(1);
|
||||
}
|
||||
sdl_texture = SDL_CreateTexture(sdl_renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, width, height);
|
||||
SDL_SetTextureScaleMode(sdl_texture, SDL_SCALEMODE_NEAREST);
|
||||
if (!sdl_texture) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "ERROR (draw::init): Failed to initialize texture!\n");
|
||||
exit(1);
|
||||
}
|
||||
*/
|
||||
|
||||
// Creem la superficie "screen" i la establim com a superficie destinació
|
||||
screen = createSurface(width, height);
|
||||
destination = screen;
|
||||
viewport.x = viewport.y = 0;
|
||||
viewport.w = width;
|
||||
viewport.h = height;
|
||||
sel_color = transparent = 0;
|
||||
for (int i=0;i<256;++i) color_indices[i] = i;
|
||||
|
||||
//SDL_HideCursor();
|
||||
//textsurf = loadSurface("font.gif");
|
||||
}
|
||||
|
||||
// Finalització del sistema
|
||||
void quit()
|
||||
{
|
||||
//if (textsurf) freeSurface(textsurf);
|
||||
|
||||
// Si la superficie "screen" existia, alliberem la seua memòria
|
||||
if (screen != nullptr)
|
||||
{
|
||||
freeSurface(screen);
|
||||
}
|
||||
|
||||
// Destruim tot el relacionat amb SDL
|
||||
destroyDisplay();
|
||||
|
||||
// Fiquem tots els punters a nullptr, per si de cas no estem eixint del programa
|
||||
// i anem a tornar a inicialitzar el sistema
|
||||
sdl_window = nullptr;
|
||||
sdl_renderer = nullptr;
|
||||
sdl_texture = nullptr;
|
||||
screen = destination = source = nullptr;
|
||||
}
|
||||
|
||||
void setZoom(const int value) {
|
||||
screen_zoom = value;
|
||||
destroyDisplay();
|
||||
createDisplay();
|
||||
file::setConfigValueInteger("zoom", screen_zoom);
|
||||
}
|
||||
|
||||
const int getZoom()
|
||||
{
|
||||
return screen_zoom;
|
||||
}
|
||||
|
||||
const float getScaleX()
|
||||
{
|
||||
return float(window_width) / float(canvas_width);
|
||||
}
|
||||
|
||||
const float getScaleY()
|
||||
{
|
||||
return float(window_height) / float(canvas_height);
|
||||
}
|
||||
|
||||
const int getOffsetX()
|
||||
{
|
||||
return offset_x;
|
||||
}
|
||||
|
||||
const int getOffsetY()
|
||||
{
|
||||
return offset_y;
|
||||
}
|
||||
|
||||
bool getFullscreen() {
|
||||
return screen_fullscreen;
|
||||
}
|
||||
|
||||
void setFullscreen(const bool value) {
|
||||
screen_fullscreen=value;
|
||||
destroyDisplay();
|
||||
createDisplay();
|
||||
file::setConfigValueBool("fullscreen", screen_fullscreen);
|
||||
}
|
||||
|
||||
void loadShader()
|
||||
{
|
||||
char *buffer = nullptr;
|
||||
if (screen_shader) {
|
||||
int size;
|
||||
buffer = file::getFileBuffer(screen_shader, size, true);
|
||||
}
|
||||
shader::setAspectRatio(3.0f/4.0f);
|
||||
shader::init(sdl_window, sdl_shadertex, buffer);
|
||||
if (buffer) free(buffer);
|
||||
}
|
||||
|
||||
void setShader(const char* shader_file)
|
||||
{
|
||||
if (screen_shader) free(screen_shader);
|
||||
screen_shader = (char*)malloc(strlen(shader_file)+1);
|
||||
strcpy(screen_shader, shader_file);
|
||||
loadShader();
|
||||
if (file::getConfigValueBool("shader_enabled", false)) enableShader();
|
||||
}
|
||||
|
||||
void enableShader()
|
||||
{
|
||||
shader_enabled = true;
|
||||
shader::enable();
|
||||
//destroyDisplay();
|
||||
//createDisplay();
|
||||
file::setConfigValueBool("shader_enabled", shader_enabled);
|
||||
}
|
||||
|
||||
void disableShader()
|
||||
{
|
||||
shader_enabled = false;
|
||||
shader::disable();
|
||||
//destroyDisplay();
|
||||
//createDisplay();
|
||||
file::setConfigValueBool("shader_enabled", shader_enabled);
|
||||
}
|
||||
|
||||
void toggleShader()
|
||||
{
|
||||
shader_enabled ? disableShader() : enableShader();
|
||||
}
|
||||
|
||||
void hideCursor()
|
||||
{
|
||||
screen_cursor = false;
|
||||
SDL_HideCursor();
|
||||
}
|
||||
|
||||
void showCursor()
|
||||
{
|
||||
screen_cursor = true;
|
||||
SDL_ShowCursor();
|
||||
}
|
||||
|
||||
// Crea una superficie i torna un punter a ella
|
||||
surface *createSurface(const uint16_t w, const uint16_t h)
|
||||
{
|
||||
// Primer reservem memòria per a la estructura "surface"
|
||||
surface *surf = (surface *)malloc(sizeof(surface));
|
||||
|
||||
// Després reservem memòria per als pixels
|
||||
surf->pixels = (uint8_t *)malloc(w * h);
|
||||
|
||||
// I apuntem el ample i alt de la superficie
|
||||
surf->w = w;
|
||||
surf->h = h;
|
||||
|
||||
// ...i tornem la superficie creada, clar
|
||||
return surf;
|
||||
}
|
||||
|
||||
// Carrega un gràfic d'un arxiu (en format GIF) a una nova superficie, i torna un punter a ella
|
||||
surface *loadSurface(const char *filename, const bool loadPalette)
|
||||
{
|
||||
// Agafem un buffer de bytes de l'arxiu especificat
|
||||
// getFileBuffer() simplement ens torna el arxiu sencer dins de un array de char
|
||||
int size;
|
||||
uint8_t *buffer = (uint8_t *)file::getFileBuffer(filename, size);
|
||||
|
||||
// Si ens ha tornat nullptr, es que no l'ha trobat, tornem nosaltres també nullptr ja que no s'ha pogut crear la superficie
|
||||
if (buffer == nullptr)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Primer reservem memòria per a la estructura "surface"
|
||||
surface *surf = (surface *)malloc(sizeof(surface));
|
||||
|
||||
// Després li passem el buffer de bytes a la funció de carregar un GIF.
|
||||
// El resultat es un array de bytes, els pixels en sí. Ja havem reservat
|
||||
// la memòria necessaria en "LoadGif", així que no tenim que fer-ho ara,
|
||||
// però, ojo, sí que tindrem que alliberar-la.
|
||||
surf->pixels = LoadGif(buffer, &surf->w, &surf->h);
|
||||
|
||||
// Si li havem dit que carregue també la paleta...
|
||||
if (loadPalette)
|
||||
{
|
||||
// Li passem el array del arxiu a LoadPalette. Ell ens torna un array de uint32_t amb la paleta
|
||||
// Van a ser com a molt 256 entrades de 32 bits (pero no sempre), cada entrada es un color, amb el format 0xAARRGGBB
|
||||
int paletteSize;
|
||||
uint32_t *pal = LoadPalette(buffer, &paletteSize);
|
||||
|
||||
// Copiem eixe array al nostre array de la paleta de sistema. Ara ja tenim la paleta carregada.
|
||||
for (int i=0;i<256;++i) {
|
||||
palette[i] = i<paletteSize ? pal[i] : 0;
|
||||
}
|
||||
//memset(palette, 0, 1024); // Fiquem tot a 0, que la paleta potser no es de 256 i quedaria basura
|
||||
//memcpy(palette, pal, paletteSize*4); // 32 bits per entrada == 4 bytes x 'paletteSize' entrades
|
||||
|
||||
// Alliberem el array que ens habia tornat LoadPalette()
|
||||
free(pal);
|
||||
}
|
||||
|
||||
// Com ja no ens fa falta, alliberem la memòria del buffer del arxiu
|
||||
free(buffer);
|
||||
|
||||
// I finalment tornem la superficie
|
||||
return surf;
|
||||
}
|
||||
|
||||
// Allibera la memòria d'una superficie, els seus pixels inclosos
|
||||
void freeSurface(surface *surf)
|
||||
{
|
||||
// Si la superficie existeix...
|
||||
if (surf != nullptr)
|
||||
{
|
||||
// Si el array de pixels existeix, l'alliberem
|
||||
if (surf->pixels != nullptr)
|
||||
{
|
||||
free(surf->pixels);
|
||||
}
|
||||
|
||||
// ... alliberem la superficie
|
||||
free(surf);
|
||||
}
|
||||
}
|
||||
|
||||
// Estableix una superficie com a superficie que rebrà les funcions de pintat (especificar nullptr per a pintar a pantalla)
|
||||
void setDestination(surface *surf)
|
||||
{
|
||||
// Si han especificat nullptr, fiquem "screen" com a destinació
|
||||
destination = surf == nullptr ? screen : surf;
|
||||
resetViewport();
|
||||
}
|
||||
|
||||
// Estableix una superficie com a superficie de la que s'agafaràn els gràfics per a pintar
|
||||
void setSource(surface *surf)
|
||||
{
|
||||
// Si han especificat nullptr, fiquem "screen" com a font
|
||||
source = surf == nullptr ? screen : surf;
|
||||
}
|
||||
|
||||
void pushSource()
|
||||
{
|
||||
pushed_source = source;
|
||||
}
|
||||
|
||||
void popSource()
|
||||
{
|
||||
source = pushed_source;
|
||||
}
|
||||
|
||||
void setViewport(const int x, const int y, const int w, const int h)
|
||||
{
|
||||
viewport.x = x>0?x:0;
|
||||
viewport.y = y>0?y:0;
|
||||
viewport.w = w+x<destination->w?w:destination->w;
|
||||
viewport.h = h+y<destination->h?h:destination->h;
|
||||
}
|
||||
|
||||
void resetViewport()
|
||||
{
|
||||
viewport.x = viewport.y = 0;
|
||||
viewport.w = destination->w;
|
||||
viewport.h = destination->h;
|
||||
}
|
||||
|
||||
const int getLocalX(const int x)
|
||||
{
|
||||
return x - viewport.x;
|
||||
}
|
||||
|
||||
const int getLocalY(const int y)
|
||||
{
|
||||
return y - viewport.y;
|
||||
}
|
||||
|
||||
// Carrega la paleta d'un GIF i la torna en un array de uint32_t
|
||||
uint32_t *loadPalette(const char *filename, int *paletteSize)
|
||||
{
|
||||
// Agafem un buffer de bytes de l'arxiu especificat
|
||||
// getFileBuffer() simplement ens torna el arxiu sencer dins de un array de char
|
||||
int size;
|
||||
uint8_t *buffer = (uint8_t *)file::getFileBuffer(filename, size);
|
||||
|
||||
// Li passem el array del arxiu a LoadPalette. Ell ens torna un array de uint32_t amb la paleta
|
||||
// Van a ser 256 entrades de 32 bits, cada entrada es un color, amb el format 0xAARRGGBB
|
||||
uint32_t *pal = LoadPalette(buffer);
|
||||
|
||||
// I també el buffer del arxiu
|
||||
free(buffer);
|
||||
|
||||
if (paletteSize) *paletteSize = size;
|
||||
return pal;
|
||||
}
|
||||
|
||||
// Estableix la paleta del sistema, o part de ella, des d'un array especificat
|
||||
void setPalette(const uint32_t *pal, const int len, const int pos)
|
||||
{
|
||||
for (int i=0; i<len; ++i)
|
||||
{
|
||||
palette[i+pos] = pal[i];
|
||||
}
|
||||
}
|
||||
|
||||
//Recupera la paleta del sistema, o part de ella, a un array
|
||||
uint32_t *getPalette()
|
||||
{
|
||||
uint32_t *p = (uint32_t*)malloc(256*sizeof(uint32_t));
|
||||
for (int i=0; i<256; ++i) p[i] = palette[i];
|
||||
return p;
|
||||
}
|
||||
|
||||
// Estableix una entrada de la paleta del sistema
|
||||
void setPaletteEntry(const uint8_t index, const uint8_t r, const uint8_t g, const uint8_t b)
|
||||
{
|
||||
palette[index] = (r << 16) + (g << 8) + b;
|
||||
}
|
||||
|
||||
// Esborra la superficie "destination" amb el color especificat
|
||||
void cls(const uint8_t color)
|
||||
{
|
||||
// El tamany es width x height bytes
|
||||
const int size = destination->w * destination->h;
|
||||
|
||||
// Omplim la memòria dels pixels de la superficie de destinació amb "color"
|
||||
memset(destination->pixels, color, size);
|
||||
}
|
||||
|
||||
//Estableix el color especificat com a transparent
|
||||
void setTrans(const uint8_t color)
|
||||
{
|
||||
transparent = color;
|
||||
}
|
||||
|
||||
// Funció interna per a pintar un pixel d'una superficie sense eixir-se'n de la memòria i petar el mame
|
||||
void pset(surface *surface, const int x, const int y, const uint8_t color)
|
||||
{
|
||||
// Si el color es transparent, eixim, ni ens molestem en mirar res més
|
||||
if (color == transparent) return;
|
||||
|
||||
// Si pintem a "destination", mirem que estiga dins del "viewport" i sinó fora
|
||||
if (surface == destination) {
|
||||
if (x+viewport.x >= 0 && y+viewport.y >= 0 && x < viewport.w && y < viewport.h)
|
||||
surface->pixels[(viewport.x+x) + (y+viewport.y) * surface->w] = color_indices[color];
|
||||
} else {
|
||||
// Si no es destinations, mirem que estiga dins de la surface, i sinó fora!
|
||||
if (x >= 0 && y >= 0 && x < surface->w && y < surface->h)
|
||||
surface->pixels[x + y * surface->w] = color_indices[color];
|
||||
}
|
||||
}
|
||||
|
||||
// Funció interna per a llegir un pixel d'una superficie eixir-se'n de la memòria i petar el mame
|
||||
const uint8_t pget(surface *surface, const int x, const int y)
|
||||
{
|
||||
// Si estem llegint de "destination", mirar que estigam llegint dins del viewport
|
||||
if (surface == destination) {
|
||||
if (x+viewport.x >= 0 && y+viewport.y >= 0 && x < viewport.w && y < viewport.h)
|
||||
return surface->pixels[(viewport.x + x) + (viewport.y + y) * surface->w];
|
||||
} else {
|
||||
// Si no es "destination", si la coordenada està dins del rang que abarca la superficie,
|
||||
if (x >= 0 && y >= 0 && x < surface->w && y < surface->h)
|
||||
return surface->pixels[x + y * surface->w];
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void putPixel(const int x, const int y, const uint8_t color)
|
||||
{
|
||||
pset(screen, x, y, color);
|
||||
}
|
||||
|
||||
// Pinta un troç de la superficie "source" en la superficie "destination".
|
||||
void draw(const int dx, const int dy, const int w, const int h, const int sx, const int sy, const draw::flip flip)
|
||||
{
|
||||
// Si no hi ha superficie d'oritge especificada, no fem res, o petarà el mame
|
||||
if (source == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// En principi, el quadrat de l'oritge començarà en (sx,sy) i avançarem 1 pixel en positiu tant en x com en y
|
||||
int sdx = 1, sdy = 1, ssx = sx, ssy = sy;
|
||||
|
||||
// Però si s'ha especificat que fem flip en horitzontal...
|
||||
if (flip & draw::flip::horizontal)
|
||||
{
|
||||
sdx = -1; // Avançarem 1 pixel en negatiu
|
||||
ssx = sx + w - 1; // I començarem al final, o siga, sumarem a sx el ample
|
||||
}
|
||||
|
||||
// De igual forma per al flip en vertical, per a la y
|
||||
if (flip & draw::flip::vertical)
|
||||
{
|
||||
sdy = -1;
|
||||
ssy = sy + h - 1;
|
||||
}
|
||||
|
||||
// guardem la coordenada d'oritge en x per a restablir-la a cada linea
|
||||
int csx = ssx;
|
||||
|
||||
// Anem linea per linea. Les variables dels dos bucles for controlen les coordenades en la destinació, que sempre van avant.
|
||||
for (int y = dy; y < dy + h; ++y)
|
||||
{
|
||||
ssx = csx; // fiquem la coordenada de l'oritge al principi
|
||||
|
||||
// en cada linea, anem pixel a pixel
|
||||
for (int x = dx; x < dx + w; ++x)
|
||||
{
|
||||
pset(destination, x, y, pget(source, ssx, ssy)); // Agafem pixel de l'oritge i el fiquem en la destinació
|
||||
ssx += sdx; // avancem (o retrocedim) la coordenada x de l'oritge
|
||||
}
|
||||
ssy += sdy; // avancem (o retrocedim) la coordenada y de l'oritge
|
||||
}
|
||||
}
|
||||
|
||||
// Pinta tota la superficie "source" en la superficie "destination", posició (x,y).
|
||||
void draw(const int x, const int y)
|
||||
{
|
||||
draw(x, y, source->w, source->h, 0, 0);
|
||||
}
|
||||
|
||||
// Pinta tota la superficie "source" en la superficie "destination", posició (0,0)
|
||||
void draw()
|
||||
{
|
||||
draw(0,0,source->w, source->h, 0, 0);
|
||||
}
|
||||
|
||||
// Carrega la superficie especificada en "source" i la pinta tota en la superficie "destination", posició (0,0).
|
||||
void draw(draw::surface* surf)
|
||||
{
|
||||
setSource(surf);
|
||||
draw();
|
||||
}
|
||||
|
||||
void swapcol(const Uint8 c1, const Uint8 c2)
|
||||
{
|
||||
color_indices[c1] = c2;
|
||||
}
|
||||
|
||||
void restorecol(const Uint8 c)
|
||||
{
|
||||
color_indices[c] = c;
|
||||
}
|
||||
|
||||
void color(const Uint8 col)
|
||||
{
|
||||
sel_color = col;
|
||||
}
|
||||
|
||||
void hline(const int x, const int y, const int w)
|
||||
{
|
||||
for (int i=x;i<x+w;++i) pset(destination, i, y, sel_color);
|
||||
}
|
||||
|
||||
void vline(const int x, const int y, const int h)
|
||||
{
|
||||
for (int i=y;i<y+h;++i) pset(destination, x, i, sel_color);
|
||||
}
|
||||
|
||||
void fillrect(const int x, const int y, const int w, const int h)
|
||||
{
|
||||
for (int j=y;j<y+h;++j) for (int i=x;i<x+w;++i) pset(destination, i, j, sel_color);
|
||||
}
|
||||
|
||||
void rect(const int x, const int y, const int w, const int h)
|
||||
{
|
||||
hline(x,y,w);
|
||||
hline(x,y+h-1,w);
|
||||
vline(x,y,h);
|
||||
vline(x+w-1,y,h);
|
||||
}
|
||||
|
||||
bool decPalEntry(const uint8_t index, const uint8_t val)
|
||||
{
|
||||
const uint32_t entry = palette[index];
|
||||
uint8_t r = (entry >> 16) & 0xff;
|
||||
uint8_t g = (entry >> 8) & 0xff;
|
||||
uint8_t b = entry & 0xff;
|
||||
|
||||
r = r>=val ? r-val : 0;
|
||||
g = g>=val ? g-val : 0;
|
||||
b = b>=val ? b-val : 0;
|
||||
palette[index] = (r << 16) + (g << 8) + b;
|
||||
|
||||
return palette[index] != 0;
|
||||
}
|
||||
|
||||
bool incPalEntry(const uint8_t index, const uint8_t val)
|
||||
{
|
||||
const uint32_t entry = palette[index];
|
||||
uint8_t r = (entry >> 16) & 0xff;
|
||||
uint8_t g = (entry >> 8) & 0xff;
|
||||
uint8_t b = entry & 0xff;
|
||||
|
||||
const uint32_t dest_entry = aux_palette[index];
|
||||
const uint8_t dr = (dest_entry >> 16) & 0xff;
|
||||
const uint8_t dg = (dest_entry >> 8) & 0xff;
|
||||
const uint8_t db = dest_entry & 0xff;
|
||||
|
||||
r = (r+val > dr) ? dr : r+val;
|
||||
g = (g+val > dg) ? dg : g+val;
|
||||
b = (b+val > db) ? db : b+val;
|
||||
palette[index] = (r << 16) + (g << 8) + b;
|
||||
|
||||
return palette[index] != aux_palette[index];
|
||||
}
|
||||
|
||||
void fadein()
|
||||
{
|
||||
if (!fading_in) {
|
||||
for (int i=0;i<256;++i) {
|
||||
aux_palette[i] = palette[i];
|
||||
palette[i] = 0;
|
||||
}
|
||||
}
|
||||
fading_in = false;
|
||||
for (int i=0; i<256; ++i) if (incPalEntry(i, 8)) fading_in = true;
|
||||
}
|
||||
|
||||
void fadeout()
|
||||
{
|
||||
fading_out = false;
|
||||
for (int i=0; i<256; ++i) if (decPalEntry(i, 8)) fading_out = true;
|
||||
}
|
||||
|
||||
bool isfading()
|
||||
{
|
||||
return fading_in || fading_out;
|
||||
}
|
||||
|
||||
// Refresca la pantalla
|
||||
void render()
|
||||
{
|
||||
Uint32 *sdl_pixels; // Punter al array de pixels que enstornarà SDL_LockTexture
|
||||
int sdl_pitch; // Ací estarà guardat el pitch de la textura, com es de 32 bits, no m'afecta
|
||||
const uint32_t size = screen->w * screen->h; // tamany de la superficie
|
||||
|
||||
if (fading_in) fadein();
|
||||
if (fading_out) fadeout();
|
||||
|
||||
// Bloquejem la textura SDL i agafem els seus pixels (son enters de 32 bits amb format 0xAARRGGBB)
|
||||
SDL_LockTexture(sdl_texture, NULL, (void **)&sdl_pixels, &sdl_pitch);
|
||||
|
||||
// Cada pixel de la superficie "screen" es un enter de 8 bits que representa un index en la paleta de colors
|
||||
// Per tant, per a pintar en la textura SDL, pillem el color de la paleta que correspon al index en "screen"
|
||||
// i el enviem a la textura SDL
|
||||
for (uint32_t i = 0; i < size; ++i)
|
||||
{
|
||||
sdl_pixels[i] = palette[screen->pixels[i]] | 0xff000000;
|
||||
}
|
||||
|
||||
// Desbloquejem la textura
|
||||
SDL_UnlockTexture(sdl_texture);
|
||||
|
||||
SDL_SetRenderTarget(sdl_renderer, sdl_shadertex);
|
||||
// Pintem la textura a pantalla
|
||||
SDL_RenderTexture(sdl_renderer, sdl_texture, NULL, NULL);
|
||||
|
||||
// I ho presentem
|
||||
shader::render();
|
||||
//SDL_RenderPresent(sdl_renderer);
|
||||
}
|
||||
|
||||
}
|
||||
154
source/japi/draw.h
Normal file
154
source/japi/draw.h
Normal file
@@ -0,0 +1,154 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
|
||||
//#define DRAW_FLIP_NONE 0
|
||||
//#define DRAW_FLIP_HORIZONTAL 1
|
||||
//#define DRAW_FLIP_VERTICAL 2
|
||||
//#define DRAW_FLIP_BOTH 3
|
||||
|
||||
// Unitat per a la gestió dels recursos gràfics i dibuixat en pantalla
|
||||
namespace draw
|
||||
{
|
||||
enum flip { none, horizontal, vertical, both };
|
||||
|
||||
// Estructura per a mantindre una superficie de pintat, la "pantalla virtual" de tota la vida
|
||||
struct surface
|
||||
{
|
||||
uint16_t w; // Ample de la superficie
|
||||
uint16_t h; // Alt de la superficie
|
||||
uint8_t *pixels; // pixels de la superficie
|
||||
};
|
||||
|
||||
/// @brief Inicialització de tot el que fa falta per a carregar gràfics i pintar en pantalla.
|
||||
/// @brief La finestra serà width*zoom x height*zoom de gran.
|
||||
/// @param titol es el text que apareixerà en la finestra
|
||||
/// @param width es el ample de la finestra "virtual"
|
||||
/// @param height es el alt de la finestra "virtual"
|
||||
/// @param zoom es com de grans son els pixels.
|
||||
void init(const char *titol, const uint16_t width, const uint16_t height, const int zoom, const bool fullscreen=false, const float ratio=1.0);
|
||||
|
||||
/// @brief Finalització del sistema (tancar coses de SDL, superficies fixes, etc...)
|
||||
void quit();
|
||||
|
||||
void setZoom(const int value);
|
||||
const int getZoom();
|
||||
const float getScaleX();
|
||||
const float getScaleY();
|
||||
const int getOffsetX();
|
||||
const int getOffsetY();
|
||||
|
||||
bool getFullscreen();
|
||||
void setFullscreen(const bool value);
|
||||
|
||||
void loadShader();
|
||||
void setShader(const char* shader_file);
|
||||
void enableShader();
|
||||
void disableShader();
|
||||
void toggleShader();
|
||||
|
||||
void hideCursor();
|
||||
void showCursor();
|
||||
|
||||
/// @brief Crea una superficie i torna un punter a ella
|
||||
/// @param w ample de la superficie
|
||||
/// @param h alt de la superficie
|
||||
/// @return un punter a una nova superficie
|
||||
surface *createSurface(const uint16_t w, const uint16_t h);
|
||||
|
||||
/// @brief Carrega un gràfic d'un arxiu (en format GIF) a una nova superficie, i torna un punter a ella
|
||||
/// @param filename nom de l'arxiu GIF d'on carregar la superficie
|
||||
/// @param loadPalette si es true també se carrega la paleta del GIF
|
||||
/// @return un punter a una nova superficie
|
||||
surface *loadSurface(const char* filename, const bool loadPalette = false);
|
||||
|
||||
/// @brief Allibera la memòria d'una superficie, els seus pixels inclosos
|
||||
/// @param surf punter a la superficie a alliberar
|
||||
void freeSurface(surface *surf);
|
||||
|
||||
/// @brief Estableix una superficie com a superficie que rebrà les funcions de pintat (especificar nullptr per a pintar a pantalla)
|
||||
/// @param surf punter a la superficie a establir com a destinació
|
||||
void setDestination(surface *surf);
|
||||
|
||||
/// @brief Estableix una superficie com a superficie de la que s'agafaràn els gràfics per a pintar
|
||||
/// @param surf punter a la superficie a establir com a oritge
|
||||
void setSource(surface *surf);
|
||||
|
||||
void pushSource();
|
||||
void popSource();
|
||||
|
||||
void setViewport(const int x, const int y, const int w, const int h);
|
||||
void resetViewport();
|
||||
|
||||
const int getLocalX(const int x);
|
||||
const int getLocalY(const int y);
|
||||
|
||||
/// @brief Carrega la paleta d'un GIF i la torna en un array de uint32_t
|
||||
/// @param filename nom de l'arxiu GIF d'on carregar la paleta
|
||||
/// @param paletteSize si no es NULL ens torna el tamany de la paleta carregada
|
||||
uint32_t *loadPalette(const char *filename, int *paletteSize = nullptr);
|
||||
|
||||
/// @brief Estableix la paleta del sistema, o part de ella, des d'un array especificat
|
||||
/// @param pal un array de uint32_t
|
||||
/// @param len quantes entrades volem trasladar a la paleta de sistema (no superar el tamany de 'pal'!)
|
||||
/// @param pos des de quina posició de la paleta de sistema comencem a copiar
|
||||
void setPalette(const uint32_t *pal, const int len, const int pos=0);
|
||||
|
||||
/// @brief Recupera la paleta del sistema, o part de ella, a un array
|
||||
/// @return un array de uint32_t
|
||||
uint32_t *getPalette();
|
||||
|
||||
/// @brief Estableix una entrada de la paleta del sistema
|
||||
/// @param index l'index de l'entrada de la paleta
|
||||
/// @param r la component roja de l'entrada de la paleta
|
||||
/// @param g la component verda de l'entrada de la paleta
|
||||
/// @param b la component blava de l'entrada de la paleta
|
||||
void setPaletteEntry(const uint8_t index, const uint8_t r, const uint8_t g, const uint8_t b);
|
||||
|
||||
/// @brief Esborra la superficie "destination" amb el color especificat
|
||||
/// @param color color a usar per a borrar la superficie de destinació
|
||||
void cls(const uint8_t color);
|
||||
|
||||
/// @brief Estableix el color especificat com a transparent
|
||||
/// @param color color a usar com a transparent
|
||||
void setTrans(const uint8_t color);
|
||||
|
||||
void putPixel(const int x, const int y, const uint8_t color);
|
||||
|
||||
/// @brief Pinta un troç de la superficie "source" en la superficie "destination".
|
||||
/// @param dx coordenada x de la destinació
|
||||
/// @param dy coordenada y de la destinació
|
||||
/// @param w ample del quadrat a pintar
|
||||
/// @param h alt del quadrat a pintar
|
||||
/// @param sx coordenada x de l'oritge
|
||||
/// @param sy coordenada y de l'oritge
|
||||
/// @param flip si s'ha de fer flip en hortizontal o vertical (o ambdos)
|
||||
void draw(const int dx, const int dy, const int w, const int h, const int sx, const int sy, const draw::flip flip = draw::flip::none);
|
||||
|
||||
/// @brief Pinta tota la superficie "source" en la superficie "destination", posició (x,y).
|
||||
/// @param x coordenada x de la destinació
|
||||
/// @param y coordenada y de la destinació
|
||||
void draw(const int x, const int y);
|
||||
|
||||
/// @brief Pinta tota la superficie "source" en la superficie "destination", posició (0,0).
|
||||
void draw();
|
||||
|
||||
/// @brief Carrega la superficie especificada en "source" i la pinta tota en la superficie "destination", posició (0,0).
|
||||
void draw(draw::surface* surf);
|
||||
|
||||
void swapcol(const uint8_t c1, const uint8_t c2);
|
||||
void restorecol(const uint8_t c);
|
||||
void color(const uint8_t col);
|
||||
void hline(const int x, const int y, const int w);
|
||||
void vline(const int x, const int y, const int h);
|
||||
void fillrect(const int x, const int y, const int w, const int h);
|
||||
void rect(const int x, const int y, const int w, const int h);
|
||||
|
||||
void fadein();
|
||||
void fadeout();
|
||||
bool isfading();
|
||||
|
||||
//void print(const char* text, const int x, const int y, const uint8_t color, const uint8_t borde);
|
||||
|
||||
/// @brief Refresca la pantalla
|
||||
void render();
|
||||
}
|
||||
275
source/japi/file.cpp
Normal file
275
source/japi/file.cpp
Normal file
@@ -0,0 +1,275 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include "file.h"
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <pwd.h>
|
||||
#endif
|
||||
|
||||
#define DEFAULT_FILENAME "data.jf2"
|
||||
#define DEFAULT_FOLDER "data/"
|
||||
#define CONFIG_FILENAME "config.txt"
|
||||
|
||||
namespace file
|
||||
{
|
||||
struct file_t
|
||||
{
|
||||
std::string path;
|
||||
uint32_t size;
|
||||
uint32_t offset;
|
||||
};
|
||||
|
||||
std::vector<file_t> toc;
|
||||
|
||||
/* El std::map me fa coses rares, vaig a usar un good old std::vector amb una estructura key,value propia i au, que sempre funciona */
|
||||
struct keyvalue_t {
|
||||
std::string key, value;
|
||||
};
|
||||
|
||||
char *resource_filename = NULL;
|
||||
char *resource_folder = NULL;
|
||||
int file_source = SOURCE_FILE;
|
||||
char scratch[255];
|
||||
static std::string config_folder;
|
||||
std::vector<keyvalue_t> config;
|
||||
|
||||
void setResourceFilename(const char *str) {
|
||||
if (resource_filename != NULL) free(resource_filename);
|
||||
resource_filename = (char*)malloc(strlen(str)+1);
|
||||
strcpy(resource_filename, str);
|
||||
}
|
||||
|
||||
void setResourceFolder(const char *str) {
|
||||
if (resource_folder != NULL) free(resource_folder);
|
||||
resource_folder = (char*)malloc(strlen(str)+1);
|
||||
strcpy(resource_folder, str);
|
||||
}
|
||||
|
||||
void setSource(const int src) {
|
||||
file_source = src%2; // mod 2 so it always is a valid value, 0 (file) or 1 (folder)
|
||||
if (src==SOURCE_FOLDER && resource_folder==NULL) setResourceFolder(DEFAULT_FOLDER);
|
||||
}
|
||||
|
||||
bool getDictionary() {
|
||||
if (resource_filename == NULL) setResourceFilename(DEFAULT_FILENAME);
|
||||
|
||||
std::ifstream fi (resource_filename, std::ios::binary);
|
||||
if (!fi.is_open()) return false;
|
||||
char header[4];
|
||||
fi.read(header, 4);
|
||||
uint32_t num_files, toc_offset;
|
||||
fi.read((char*)&num_files, 4);
|
||||
fi.read((char*)&toc_offset, 4);
|
||||
fi.seekg(toc_offset);
|
||||
|
||||
for (uint i=0; i<num_files; ++i)
|
||||
{
|
||||
uint32_t file_offset, file_size;
|
||||
fi.read( (char*)&file_offset, 4 );
|
||||
fi.read( (char*)&file_size, 4 );
|
||||
uint8_t path_size;
|
||||
fi.read( (char*)&path_size, 1 );
|
||||
char file_name[path_size+1];
|
||||
fi.read( file_name, path_size );
|
||||
file_name[path_size] = 0;
|
||||
std::string filename = file_name;
|
||||
toc.push_back({filename, file_size, file_offset});
|
||||
}
|
||||
fi.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
char *getFilenameWithFolder(const char* filename) {
|
||||
strcpy(scratch, resource_folder);
|
||||
strcat(scratch, filename);
|
||||
return scratch;
|
||||
}
|
||||
|
||||
FILE *getFilePointer(const char *resourcename, int& filesize, const bool binary) {
|
||||
|
||||
if (file_source==SOURCE_FILE and toc.size()==0) {
|
||||
if (not getDictionary()) setSource(SOURCE_FOLDER);
|
||||
}
|
||||
|
||||
FILE *f;
|
||||
|
||||
if (file_source==SOURCE_FILE) {
|
||||
bool found = false;
|
||||
uint32_t count = 0;
|
||||
while( !found && count < toc.size() ) {
|
||||
found = ( std::string(resourcename) == toc[count].path );
|
||||
if( !found ) count++;
|
||||
}
|
||||
|
||||
if( !found ) {
|
||||
perror("El recurs no s'ha trobat en l'arxiu de recursos");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
filesize = toc[count].size;
|
||||
|
||||
f = fopen(resource_filename, binary?"rb":"r");
|
||||
if (not f) {
|
||||
perror("No s'ha pogut obrir l'arxiu de recursos");
|
||||
exit(1);
|
||||
}
|
||||
fseek(f, toc[count].offset, SEEK_SET);
|
||||
} else {
|
||||
f = fopen(getFilenameWithFolder(resourcename), binary?"rb":"r");
|
||||
fseek(f, 0, SEEK_END);
|
||||
filesize = ftell(f);
|
||||
fseek(f, 0, SEEK_SET);
|
||||
}
|
||||
return f;
|
||||
}
|
||||
|
||||
char *getFileBuffer(const char *resourcename, int& filesize, const bool zero_terminate) {
|
||||
FILE *f = getFilePointer(resourcename, filesize, true);
|
||||
char* buffer = (char*)malloc(zero_terminate?filesize+1:filesize);
|
||||
fread(buffer, filesize, 1, f);
|
||||
if (zero_terminate) buffer[filesize]=0;
|
||||
fclose(f);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
// Crea la carpeta del sistema donde guardar datos
|
||||
void setConfigFolder(const char *foldername)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
config_folder = std::string(getenv("APPDATA")) + "/" + foldername;
|
||||
#elif __APPLE__
|
||||
struct passwd *pw = getpwuid(getuid());
|
||||
const char *homedir = pw->pw_dir;
|
||||
config_folder = std::string(homedir) + "/Library/Application Support/" + foldername;
|
||||
#elif __linux__
|
||||
struct passwd *pw = getpwuid(getuid());
|
||||
const char *homedir = pw->pw_dir;
|
||||
config_folder = std::string(homedir) + "/." + foldername;
|
||||
#endif
|
||||
|
||||
struct stat st = {0};
|
||||
if (stat(config_folder.c_str(), &st) == -1)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
int ret = mkdir(config_folder.c_str());
|
||||
#else
|
||||
int ret = mkdir(config_folder.c_str(), S_IRWXU);
|
||||
#endif
|
||||
|
||||
if (ret == -1)
|
||||
{
|
||||
printf("ERROR CREATING CONFIG FOLDER.");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const char *getConfigFolder() {
|
||||
std::string folder = config_folder + "/";
|
||||
return folder.c_str();
|
||||
}
|
||||
|
||||
void loadConfigValues() {
|
||||
config.clear();
|
||||
std::string config_file = config_folder + "/config.txt";
|
||||
FILE *f = fopen(config_file.c_str(), "r");
|
||||
if (!f) return;
|
||||
|
||||
char line[1024];
|
||||
while (fgets(line, sizeof(line), f)) {
|
||||
char *value = strchr(line, '=');
|
||||
if (value) {
|
||||
*value='\0'; value++;
|
||||
value[strlen(value)-1] = '\0';
|
||||
config.push_back({line, value});
|
||||
}
|
||||
}
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
void saveConfigValues() {
|
||||
std::string config_file = config_folder + "/config.txt";
|
||||
FILE *f = fopen(config_file.c_str(), "w");
|
||||
if (f) {
|
||||
for (auto pair : config) {
|
||||
fprintf(f, "%s=%s\n", pair.key.c_str(), pair.value.c_str());
|
||||
}
|
||||
fclose(f);
|
||||
}
|
||||
}
|
||||
|
||||
const char* getConfigValueString(const char *key) {
|
||||
if (config.empty()) loadConfigValues();
|
||||
for (auto pair : config) {
|
||||
if (pair.key == std::string(key)) {
|
||||
strcpy(scratch, pair.value.c_str());
|
||||
return scratch;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const int getConfigValueInteger(const char *key, const int default_value)
|
||||
{
|
||||
const char* value = getConfigValueString(key);
|
||||
if (!value) return default_value;
|
||||
return atoi(value);
|
||||
}
|
||||
|
||||
const float getConfigValueFloat(const char *key, const float default_value)
|
||||
{
|
||||
const char* value = getConfigValueString(key);
|
||||
if (!value) return default_value;
|
||||
return atof(value);
|
||||
}
|
||||
|
||||
const bool getConfigValueBool(const char *key, const bool default_value)
|
||||
{
|
||||
const char* value = getConfigValueString(key);
|
||||
if (!value) return default_value;
|
||||
return strcmp(value, "true")==0?true:false;
|
||||
}
|
||||
|
||||
void setConfigValueString(const char* key, const char* value) {
|
||||
if (config.empty()) loadConfigValues();
|
||||
for (auto &pair : config) {
|
||||
if (pair.key == std::string(key)) {
|
||||
pair.value = value;
|
||||
saveConfigValues();
|
||||
return;
|
||||
}
|
||||
}
|
||||
config.push_back({key, value});
|
||||
saveConfigValues();
|
||||
return;
|
||||
}
|
||||
|
||||
void setConfigValueInteger(const char* key, const int value)
|
||||
{
|
||||
char tmp[256];
|
||||
sprintf(tmp, "%i", value);
|
||||
setConfigValueString(key, tmp);
|
||||
}
|
||||
|
||||
void setConfigValueFloat(const char* key, const float value)
|
||||
{
|
||||
char tmp[256];
|
||||
sprintf(tmp, "%.2f", value);
|
||||
setConfigValueString(key, tmp);
|
||||
}
|
||||
|
||||
void setConfigValueBool(const char* key, const bool value)
|
||||
{
|
||||
setConfigValueString(key, value?"true":"false");
|
||||
}
|
||||
|
||||
}
|
||||
27
source/japi/file.h
Normal file
27
source/japi/file.h
Normal file
@@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
#include <stdio.h>
|
||||
|
||||
#define SOURCE_FILE 0
|
||||
#define SOURCE_FOLDER 1
|
||||
|
||||
namespace file
|
||||
{
|
||||
void setConfigFolder(const char *foldername);
|
||||
const char *getConfigFolder();
|
||||
|
||||
void setResourceFilename(const char *str);
|
||||
void setResourceFolder(const char *str);
|
||||
void setSource(const int src);
|
||||
|
||||
FILE *getFilePointer(const char *resourcename, int& filesize, const bool binary=false);
|
||||
char *getFileBuffer(const char *resourcename, int& filesize, const bool zero_terminate=false);
|
||||
|
||||
const char* getConfigValueString(const char *key);
|
||||
const int getConfigValueInteger(const char *key, const int default_value=0);
|
||||
const float getConfigValueFloat(const char *key, const float default_value=0.0f);
|
||||
const bool getConfigValueBool(const char *key, const bool default_value=false);
|
||||
void setConfigValueString(const char* key, const char* value);
|
||||
void setConfigValueInteger(const char* key, const int value);
|
||||
void setConfigValueFloat(const char* key, const float value);
|
||||
void setConfigValueBool(const char* key, const bool value);
|
||||
}
|
||||
133
source/japi/game.cpp
Normal file
133
source/japi/game.cpp
Normal file
@@ -0,0 +1,133 @@
|
||||
#include "game.h"
|
||||
#include "draw.h"
|
||||
#include "input.h"
|
||||
#include <SDL3/SDL.h>
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#ifdef MACOS_BUNDLE
|
||||
#include <libgen.h>
|
||||
#endif
|
||||
|
||||
namespace game
|
||||
{
|
||||
bool windowHasFocus = true;
|
||||
|
||||
static bool (*loop)() = nullptr;
|
||||
static unsigned int ticks_per_frame = 1000/60;
|
||||
static std::map<std::string, int> config;
|
||||
|
||||
void setUpdateTicks(const int ticks)
|
||||
{
|
||||
ticks_per_frame = ticks;
|
||||
}
|
||||
|
||||
void setState(bool (*loop)())
|
||||
{
|
||||
game::loop = loop;
|
||||
}
|
||||
|
||||
void setConfig(const char* key, const int value)
|
||||
{
|
||||
config[key] = value;
|
||||
}
|
||||
|
||||
const int getConfig(const char* key)
|
||||
{
|
||||
return config[key];
|
||||
}
|
||||
|
||||
const uint32_t getTicks()
|
||||
{
|
||||
return SDL_GetTicks();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
#ifdef MACOS_BUNDLE
|
||||
char res_file[255] = "";
|
||||
strcpy(res_file, dirname(argv[0]));
|
||||
strcat(res_file, "/../Resources/data.jf2");
|
||||
file_setresourcefilename(res_file);
|
||||
#endif
|
||||
|
||||
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO);
|
||||
|
||||
#ifdef DEBUG
|
||||
SDL_SetLogPriorities(SDL_LOG_PRIORITY_DEBUG);
|
||||
#endif
|
||||
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_SYSTEM, "JAPI v%s\n", JAPI_VERSION);
|
||||
|
||||
game::windowHasFocus = true;
|
||||
game::init();
|
||||
input::init();
|
||||
|
||||
static unsigned int current_ticks = SDL_GetTicks();
|
||||
|
||||
bool should_exit=false;
|
||||
SDL_Event e;
|
||||
while (!should_exit)
|
||||
{
|
||||
while(SDL_PollEvent(&e))
|
||||
{
|
||||
if (e.type==SDL_EVENT_QUIT)
|
||||
{
|
||||
should_exit = true;
|
||||
break;
|
||||
}
|
||||
if (e.type==SDL_EVENT_KEY_DOWN)
|
||||
{
|
||||
input::updateKey(e.key.scancode);
|
||||
}
|
||||
if (e.type==SDL_EVENT_KEY_UP)
|
||||
{
|
||||
switch (e.key.scancode) {
|
||||
case SDL_SCANCODE_F1:
|
||||
draw::setZoom(draw::getZoom()-1);
|
||||
break;
|
||||
case SDL_SCANCODE_F2:
|
||||
draw::setZoom(draw::getZoom()+1);
|
||||
break;
|
||||
case SDL_SCANCODE_F3:
|
||||
draw::setFullscreen(!draw::getFullscreen());
|
||||
break;
|
||||
case SDL_SCANCODE_F4:
|
||||
draw::toggleShader();
|
||||
break;
|
||||
default:
|
||||
input::updateKeypressed(e.key.scancode);
|
||||
}
|
||||
}
|
||||
if (e.type==SDL_EVENT_MOUSE_BUTTON_UP)
|
||||
{
|
||||
input::updateClk(e.button.button);
|
||||
}
|
||||
if (e.type==SDL_EVENT_MOUSE_WHEEL)
|
||||
{
|
||||
input::updateWheel(e.wheel.y);
|
||||
}
|
||||
if ( e.type == SDL_EVENT_WINDOW_FOCUS_GAINED )
|
||||
{
|
||||
game::windowHasFocus = true;
|
||||
}
|
||||
if ( e.type == SDL_EVENT_WINDOW_FOCUS_LOST )
|
||||
{
|
||||
game::windowHasFocus = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (SDL_GetTicks()-current_ticks >= game::ticks_per_frame)
|
||||
{
|
||||
if (game::loop) if (!game::loop()) should_exit = true;
|
||||
input::updateKey(SDL_SCANCODE_UNKNOWN);
|
||||
input::updateKeypressed(SDL_SCANCODE_UNKNOWN);
|
||||
input::updateClk(0);
|
||||
input::updateWheel(0);
|
||||
current_ticks = SDL_GetTicks();
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
25
source/japi/game.h
Normal file
25
source/japi/game.h
Normal file
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include "draw.h"
|
||||
#include "input.h"
|
||||
#include "audio.h"
|
||||
#include "file.h"
|
||||
|
||||
#define JAPI_VERSION "0.8"
|
||||
|
||||
namespace game
|
||||
{
|
||||
extern bool windowHasFocus;
|
||||
|
||||
void setUpdateTicks(const int ticks);
|
||||
|
||||
void init();
|
||||
|
||||
void setState(bool (*loop)());
|
||||
|
||||
void setConfig(const char* key, const int value);
|
||||
|
||||
const int getConfig(const char* key);
|
||||
|
||||
const uint32_t getTicks();
|
||||
}
|
||||
481
source/japi/gif.h
Normal file
481
source/japi/gif.h
Normal file
@@ -0,0 +1,481 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define EXTENSION_INTRODUCER 0x21
|
||||
#define IMAGE_DESCRIPTOR 0x2C
|
||||
#define TRAILER 0x3B
|
||||
|
||||
#define GRAPHIC_CONTROL 0xF9
|
||||
#define APPLICATION_EXTENSION 0xFF
|
||||
#define COMMENT_EXTENSION 0xFE
|
||||
#define PLAINTEXT_EXTENSION 0x01
|
||||
|
||||
#define READ(dst, size) memcpy(dst, buffer, size); buffer += size
|
||||
|
||||
typedef struct
|
||||
{
|
||||
unsigned short width;
|
||||
unsigned short height;
|
||||
unsigned char fields;
|
||||
unsigned char background_color_index;
|
||||
unsigned char pixel_aspect_ratio;
|
||||
}
|
||||
screen_descriptor_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
unsigned char r;
|
||||
unsigned char g;
|
||||
unsigned char b;
|
||||
}
|
||||
rgb;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
unsigned short image_left_position;
|
||||
unsigned short image_top_position;
|
||||
unsigned short image_width;
|
||||
unsigned short image_height;
|
||||
unsigned char fields;
|
||||
}
|
||||
image_descriptor_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
unsigned char byte;
|
||||
int prev;
|
||||
int len;
|
||||
}
|
||||
dictionary_entry_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
unsigned char extension_code;
|
||||
unsigned char block_size;
|
||||
}
|
||||
extension_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
unsigned char fields;
|
||||
unsigned short delay_time;
|
||||
unsigned char transparent_color_index;
|
||||
}
|
||||
graphic_control_extension_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
unsigned char application_id[ 8 ];
|
||||
unsigned char version[ 3 ];
|
||||
}
|
||||
application_extension_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
unsigned short left;
|
||||
unsigned short top;
|
||||
unsigned short width;
|
||||
unsigned short height;
|
||||
unsigned char cell_width;
|
||||
unsigned char cell_height;
|
||||
unsigned char foreground_color;
|
||||
unsigned char background_color;
|
||||
}
|
||||
plaintext_extension_t;
|
||||
|
||||
//static unsigned short width = 0;
|
||||
//static unsigned short height = 0;
|
||||
//static unsigned char* uncompressed_data = NULL;
|
||||
|
||||
void uncompress( int code_length,
|
||||
const unsigned char *input,
|
||||
int input_length,
|
||||
unsigned char *out )
|
||||
{
|
||||
//int maxbits;
|
||||
int i, bit;
|
||||
int code, prev = -1;
|
||||
dictionary_entry_t *dictionary;
|
||||
int dictionary_ind;
|
||||
unsigned int mask = 0x01;
|
||||
int reset_code_length;
|
||||
int clear_code; // This varies depending on code_length
|
||||
int stop_code; // one more than clear code
|
||||
int match_len;
|
||||
|
||||
clear_code = 1 << ( code_length );
|
||||
stop_code = clear_code + 1;
|
||||
// To handle clear codes
|
||||
reset_code_length = code_length;
|
||||
|
||||
// Create a dictionary large enough to hold "code_length" entries.
|
||||
// Once the dictionary overflows, code_length increases
|
||||
dictionary = ( dictionary_entry_t * )
|
||||
malloc( sizeof( dictionary_entry_t ) * ( 1 << ( code_length + 1 ) ) );
|
||||
|
||||
// Initialize the first 2^code_len entries of the dictionary with their
|
||||
// indices. The rest of the entries will be built up dynamically.
|
||||
|
||||
// Technically, it shouldn't be necessary to initialize the
|
||||
// dictionary. The spec says that the encoder "should output a
|
||||
// clear code as the first code in the image data stream". It doesn't
|
||||
// say must, though...
|
||||
for ( dictionary_ind = 0;
|
||||
dictionary_ind < ( 1 << code_length );
|
||||
dictionary_ind++ )
|
||||
{
|
||||
dictionary[ dictionary_ind ].byte = dictionary_ind;
|
||||
// XXX this only works because prev is a 32-bit int (> 12 bits)
|
||||
dictionary[ dictionary_ind ].prev = -1;
|
||||
dictionary[ dictionary_ind ].len = 1;
|
||||
}
|
||||
|
||||
// 2^code_len + 1 is the special "end" code; don't give it an entry here
|
||||
dictionary_ind++;
|
||||
dictionary_ind++;
|
||||
|
||||
// TODO verify that the very last byte is clear_code + 1
|
||||
while ( input_length )
|
||||
{
|
||||
code = 0x0;
|
||||
// Always read one more bit than the code length
|
||||
for ( i = 0; i < ( code_length + 1 ); i++ )
|
||||
{
|
||||
// This is different than in the file read example; that
|
||||
// was a call to "next_bit"
|
||||
bit = ( *input & mask ) ? 1 : 0;
|
||||
mask <<= 1;
|
||||
|
||||
if ( mask == 0x100 )
|
||||
{
|
||||
mask = 0x01;
|
||||
input++;
|
||||
input_length--;
|
||||
}
|
||||
|
||||
code = code | ( bit << i );
|
||||
}
|
||||
|
||||
if ( code == clear_code )
|
||||
{
|
||||
code_length = reset_code_length;
|
||||
dictionary = ( dictionary_entry_t * ) realloc( dictionary,
|
||||
sizeof( dictionary_entry_t ) * ( 1 << ( code_length + 1 ) ) );
|
||||
|
||||
for ( dictionary_ind = 0;
|
||||
dictionary_ind < ( 1 << code_length );
|
||||
dictionary_ind++ )
|
||||
{
|
||||
dictionary[ dictionary_ind ].byte = dictionary_ind;
|
||||
// XXX this only works because prev is a 32-bit int (> 12 bits)
|
||||
dictionary[ dictionary_ind ].prev = -1;
|
||||
dictionary[ dictionary_ind ].len = 1;
|
||||
}
|
||||
dictionary_ind++;
|
||||
dictionary_ind++;
|
||||
prev = -1;
|
||||
continue;
|
||||
}
|
||||
else if ( code == stop_code )
|
||||
{
|
||||
/*if ( input_length > 1 )
|
||||
{
|
||||
fprintf( stderr, "Malformed GIF (early stop code)\n" );
|
||||
exit( 0 );
|
||||
}*/
|
||||
break;
|
||||
}
|
||||
|
||||
// Update the dictionary with this character plus the _entry_
|
||||
// (character or string) that came before it
|
||||
if ( ( prev > -1 ) && ( code_length < 12 ) )
|
||||
{
|
||||
if ( code > dictionary_ind )
|
||||
{
|
||||
fprintf( stderr, "code = %.02x, but dictionary_ind = %.02x\n",
|
||||
code, dictionary_ind );
|
||||
exit( 0 );
|
||||
}
|
||||
|
||||
// Special handling for KwKwK
|
||||
if ( code == dictionary_ind )
|
||||
{
|
||||
int ptr = prev;
|
||||
|
||||
while ( dictionary[ ptr ].prev != -1 )
|
||||
{
|
||||
ptr = dictionary[ ptr ].prev;
|
||||
}
|
||||
dictionary[ dictionary_ind ].byte = dictionary[ ptr ].byte;
|
||||
}
|
||||
else
|
||||
{
|
||||
int ptr = code;
|
||||
while ( dictionary[ ptr ].prev != -1 )
|
||||
{
|
||||
ptr = dictionary[ ptr ].prev;
|
||||
}
|
||||
dictionary[ dictionary_ind ].byte = dictionary[ ptr ].byte;
|
||||
}
|
||||
|
||||
dictionary[ dictionary_ind ].prev = prev;
|
||||
|
||||
dictionary[ dictionary_ind ].len = dictionary[ prev ].len + 1;
|
||||
|
||||
dictionary_ind++;
|
||||
|
||||
// GIF89a mandates that this stops at 12 bits
|
||||
if ( ( dictionary_ind == ( 1 << ( code_length + 1 ) ) ) &&
|
||||
( code_length < 11 ) )
|
||||
{
|
||||
code_length++;
|
||||
|
||||
dictionary = ( dictionary_entry_t * ) realloc( dictionary,
|
||||
sizeof( dictionary_entry_t ) * ( 1 << ( code_length + 1 ) ) );
|
||||
}
|
||||
}
|
||||
|
||||
prev = code;
|
||||
|
||||
// Now copy the dictionary entry backwards into "out"
|
||||
match_len = dictionary[ code ].len;
|
||||
while ( code != -1 )
|
||||
{
|
||||
out[ dictionary[ code ].len - 1 ] = dictionary[ code ].byte;
|
||||
if ( dictionary[ code ].prev == code )
|
||||
{
|
||||
fprintf( stderr, "Internal error; self-reference." );
|
||||
exit( 0 );
|
||||
}
|
||||
code = dictionary[ code ].prev;
|
||||
}
|
||||
|
||||
out += match_len;
|
||||
}
|
||||
}
|
||||
|
||||
static int read_sub_blocks( unsigned char* buffer, unsigned char **data )
|
||||
{
|
||||
int data_length;
|
||||
int index;
|
||||
unsigned char block_size;
|
||||
|
||||
// Everything following are data sub-blocks, until a 0-sized block is
|
||||
// encountered.
|
||||
data_length = 0;
|
||||
*data = NULL;
|
||||
index = 0;
|
||||
|
||||
while ( 1 )
|
||||
{
|
||||
READ(&block_size, 1);
|
||||
|
||||
if ( block_size == 0 ) // end of sub-blocks
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
data_length += block_size;
|
||||
*data = (unsigned char*)realloc( *data, data_length );
|
||||
|
||||
// TODO this could be split across block size boundaries
|
||||
READ(*data + index, block_size);
|
||||
|
||||
index += block_size;
|
||||
}
|
||||
|
||||
return data_length;
|
||||
}
|
||||
|
||||
unsigned char* process_image_descriptor( unsigned char* buffer,
|
||||
rgb *gct,
|
||||
int gct_size,
|
||||
int resolution_bits )
|
||||
{
|
||||
image_descriptor_t image_descriptor;
|
||||
int compressed_data_length;
|
||||
unsigned char *compressed_data = NULL;
|
||||
unsigned char lzw_code_size;
|
||||
int uncompressed_data_length = 0;
|
||||
unsigned char *uncompressed_data = NULL;
|
||||
|
||||
// TODO there could actually be lots of these
|
||||
READ(&image_descriptor, 9);
|
||||
|
||||
// TODO if LCT = true, read the LCT
|
||||
|
||||
READ(&lzw_code_size, 1);
|
||||
|
||||
compressed_data_length = read_sub_blocks( buffer, &compressed_data );
|
||||
|
||||
// width = image_descriptor.image_width;
|
||||
// height = image_descriptor.image_height;
|
||||
uncompressed_data_length = image_descriptor.image_width *
|
||||
image_descriptor.image_height;
|
||||
uncompressed_data = (unsigned char*)malloc( uncompressed_data_length );
|
||||
|
||||
uncompress( lzw_code_size, compressed_data, compressed_data_length,
|
||||
uncompressed_data );
|
||||
|
||||
if ( compressed_data ) free( compressed_data );
|
||||
|
||||
//if ( uncompressed_data )
|
||||
// free( uncompressed_data );
|
||||
|
||||
return uncompressed_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param gif_file the file descriptor of a file containing a
|
||||
* GIF-encoded file. This should point to the first byte in
|
||||
* the file when invoked.
|
||||
*/
|
||||
#define rb (*(buffer++))
|
||||
|
||||
uint32_t* LoadPalette(unsigned char *buffer, int *paletteSize = NULL) {
|
||||
unsigned char header[7];
|
||||
screen_descriptor_t screen_descriptor;
|
||||
//int color_resolution_bits;
|
||||
|
||||
int global_color_table_size = 0; // number of entries in global_color_table
|
||||
uint32_t *global_color_table = NULL;
|
||||
|
||||
READ(header, 6);
|
||||
READ(&screen_descriptor, 7);
|
||||
|
||||
//color_resolution_bits = ((screen_descriptor.fields & 0x70) >> 4) + 1;
|
||||
//global_color_table = (uint32_t *)calloc(1, 1024);
|
||||
|
||||
if (screen_descriptor.fields & 0x80)
|
||||
{
|
||||
global_color_table_size = 1 << (((screen_descriptor.fields & 0x07) + 1));
|
||||
|
||||
if (paletteSize != NULL) *paletteSize = global_color_table_size;
|
||||
global_color_table = (uint32_t *)malloc(4 * global_color_table_size);
|
||||
//READ(global_color_table, 3 * global_color_table_size);
|
||||
for (int i=0; i<global_color_table_size;++i) {
|
||||
global_color_table[i] = (buffer[0]<<16) + (buffer[1]<<8) + buffer[2];
|
||||
buffer+=3;
|
||||
}
|
||||
}
|
||||
return global_color_table;
|
||||
}
|
||||
|
||||
static unsigned char* process_gif_stream(unsigned char *buffer, unsigned short* w, unsigned short* h)
|
||||
{
|
||||
unsigned char header[ 7 ];
|
||||
screen_descriptor_t screen_descriptor;
|
||||
int color_resolution_bits;
|
||||
|
||||
int global_color_table_size =0; // number of entries in global_color_table
|
||||
rgb *global_color_table = NULL;
|
||||
|
||||
unsigned char block_type = 0x0;
|
||||
|
||||
// A GIF file starts with a Header (section 17)
|
||||
READ(header, 6);
|
||||
header[ 6 ] = 0x0;
|
||||
|
||||
// XXX there's another format, GIF87a, that you may still find
|
||||
// floating around.
|
||||
/*if ( strcmp( "GIF89a", (char*)header ) )
|
||||
{
|
||||
fprintf( stderr,
|
||||
"Invalid GIF file (header is '%s', should be 'GIF89a')\n",
|
||||
header );
|
||||
return NULL;
|
||||
}*/
|
||||
|
||||
// Followed by a logical screen descriptor
|
||||
// Note that this works because GIFs specify little-endian order; on a
|
||||
// big-endian machine, the height & width would need to be reversed.
|
||||
|
||||
// Can't use sizeof here since GCC does byte alignment;
|
||||
// sizeof( screen_descriptor_t ) = 8!
|
||||
READ(&screen_descriptor, 7);
|
||||
*w = screen_descriptor.width;
|
||||
*h = screen_descriptor.height;
|
||||
|
||||
color_resolution_bits = ( ( screen_descriptor.fields & 0x70 ) >> 4 ) + 1;
|
||||
|
||||
if ( screen_descriptor.fields & 0x80 )
|
||||
{
|
||||
//int i;
|
||||
// If bit 7 is set, the next block is a global color table; read it
|
||||
global_color_table_size = 1 <<
|
||||
( ( ( screen_descriptor.fields & 0x07 ) + 1 ) );
|
||||
|
||||
global_color_table = ( rgb * ) malloc( 3 * global_color_table_size );
|
||||
|
||||
// XXX this could conceivably return a short count...
|
||||
READ(global_color_table, 3 * global_color_table_size);
|
||||
}
|
||||
|
||||
while ( block_type != TRAILER )
|
||||
{
|
||||
READ(&block_type, 1);
|
||||
|
||||
unsigned char size;
|
||||
switch ( block_type )
|
||||
{
|
||||
case IMAGE_DESCRIPTOR:
|
||||
return process_image_descriptor(buffer,
|
||||
global_color_table,
|
||||
global_color_table_size,
|
||||
color_resolution_bits);
|
||||
break;
|
||||
case EXTENSION_INTRODUCER:
|
||||
buffer++;
|
||||
size = *(buffer++);
|
||||
buffer += size;
|
||||
do {
|
||||
size = *(buffer++);
|
||||
buffer += size;
|
||||
} while (size != 0);
|
||||
|
||||
/*if ( !process_extension( buffer ) )
|
||||
{
|
||||
return NULL;
|
||||
}*/
|
||||
break;
|
||||
case TRAILER:
|
||||
break;
|
||||
default:
|
||||
fprintf( stderr, "Bailing on unrecognized block type %.02x\n",
|
||||
block_type );
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
unsigned char* LoadGif(unsigned char *buffer, unsigned short* w, unsigned short* h) {
|
||||
return process_gif_stream(buffer, w, h);
|
||||
}
|
||||
|
||||
/*int main( int argc, char *argv[] )
|
||||
{
|
||||
FILE* gif_file;
|
||||
|
||||
if ( argc < 2 )
|
||||
{
|
||||
fprintf( stderr, "Usage: %s <path-to-gif-file>\n", argv[ 0 ] );
|
||||
exit( 0 );
|
||||
}
|
||||
|
||||
gif_file = fopen( argv[ 1 ], "rb" );
|
||||
|
||||
if ( gif_file == NULL )
|
||||
{
|
||||
fprintf( stderr, "Unable to open file '%s'", argv[ 1 ] );
|
||||
perror( ": " );
|
||||
}
|
||||
|
||||
process_gif_stream( gif_file );
|
||||
|
||||
fclose( gif_file );
|
||||
}*/
|
||||
111
source/japi/input.cpp
Normal file
111
source/japi/input.cpp
Normal file
@@ -0,0 +1,111 @@
|
||||
#include "input.h"
|
||||
#include <SDL3/SDL.h>
|
||||
#include "draw.h"
|
||||
|
||||
namespace input
|
||||
{
|
||||
static const bool *keys = nullptr;
|
||||
static uint8_t keypressed = 0;
|
||||
static uint8_t keydown = 0;
|
||||
static uint8_t btnClicked = 0;
|
||||
static int wheel = 0;
|
||||
|
||||
void init()
|
||||
{
|
||||
keys = SDL_GetKeyboardState(NULL);
|
||||
}
|
||||
|
||||
// Determina si la tecla especificada està sent polsada ara mateix
|
||||
bool keyDown(const uint8_t key)
|
||||
{
|
||||
return keys[key];
|
||||
}
|
||||
|
||||
// Determina si la tecla especificada ha sigut polsada, pero no tornarà a ser true fins
|
||||
bool keyPressed(const uint8_t key)
|
||||
{
|
||||
return key == keypressed;
|
||||
}
|
||||
|
||||
// Determina si hi ha alguna tecla polsada ara mateix
|
||||
bool anyKey()
|
||||
{
|
||||
return keydown != 0;
|
||||
}
|
||||
|
||||
bool anyKeyPressed()
|
||||
{
|
||||
return keypressed != 0;
|
||||
}
|
||||
|
||||
// Torna el codi de la tecla que està sent polsada ara mateix
|
||||
const uint8_t whichKey()
|
||||
{
|
||||
return keydown;
|
||||
}
|
||||
|
||||
// Torna el codi de la tecla que està sent polsada ara mateix
|
||||
const uint8_t getKeyPressed()
|
||||
{
|
||||
return keypressed;
|
||||
}
|
||||
|
||||
// (US INTERN) Actualitza la tecla actualment polsada (keydown) desde jgame
|
||||
void updateKey(const uint8_t key)
|
||||
{
|
||||
keydown = key;
|
||||
}
|
||||
|
||||
// (US INTERN) Actualitza la tecla actualment polsada (keypress) desde jgame
|
||||
void updateKeypressed(const uint8_t key)
|
||||
{
|
||||
keypressed = key;
|
||||
}
|
||||
|
||||
// (US INTERN) Actualitza el botó del ratolí actualment polsat desde jgame
|
||||
void updateClk(const uint8_t btn)
|
||||
{
|
||||
btnClicked = btn;
|
||||
}
|
||||
|
||||
// (US INTERN) Actualitza el valor de la roda del ratolí actual desde jgame
|
||||
void updateWheel(const int dy)
|
||||
{
|
||||
wheel = dy;
|
||||
}
|
||||
|
||||
// Torna la posició X actual del ratolí
|
||||
const int mouseX()
|
||||
{
|
||||
float x;
|
||||
SDL_GetMouseState(&x, NULL);
|
||||
return (x-draw::getOffsetX())/draw::getScaleX();
|
||||
}
|
||||
|
||||
// Torna la posició Y actual del ratolí
|
||||
const int mouseY()
|
||||
{
|
||||
float y;
|
||||
SDL_GetMouseState(NULL, &y);
|
||||
return (y-draw::getOffsetY())/draw::getScaleY();
|
||||
}
|
||||
|
||||
// Determina si el botó del ratolí especificat està sent polsada ara mateix
|
||||
const bool mouseBtn(const int btn)
|
||||
{
|
||||
return (SDL_GetMouseState(NULL, NULL) & SDL_BUTTON_MASK(btn));
|
||||
}
|
||||
|
||||
// Determina si el botó especificat ha sigut polsat, pero no tornarà a ser true fins
|
||||
// que no se solte el botó i se torne a polsar (Equivalent a keypress en tecles).
|
||||
const bool mouseClk(const int btn)
|
||||
{
|
||||
return btnClicked == btn;
|
||||
}
|
||||
|
||||
// Obté el valor actual de la rodeta del ratolí
|
||||
const int mouseWheel()
|
||||
{
|
||||
return wheel;
|
||||
}
|
||||
}
|
||||
87
source/japi/input.h
Normal file
87
source/japi/input.h
Normal file
@@ -0,0 +1,87 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
|
||||
namespace input
|
||||
{
|
||||
namespace mouse
|
||||
{
|
||||
namespace button
|
||||
{
|
||||
const int left = 1;
|
||||
const int middle = 2;
|
||||
const int right = 3;
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief Inicialitza els sistemes de teclat, ratolí i gamepad
|
||||
void init();
|
||||
|
||||
/// @brief Determina si la tecla especificada està sent polsada ara mateix
|
||||
/// @param key tecla a consultar
|
||||
/// @return true si està polsada, false si no
|
||||
bool keyDown(const uint8_t key);
|
||||
|
||||
/// @brief Determina si la tecla especificada ha sigut polsada, pero no tornarà a ser true fins
|
||||
/// @brief que no se solte la tecla i se torne a polsar.
|
||||
/// @param key tecla a consultar
|
||||
/// @return true si està polsada, false si no
|
||||
bool keyPressed(const uint8_t key);
|
||||
|
||||
/// @brief Determina si hi ha alguna tecla polsada ara mateix
|
||||
/// @return true si hi ha alguna tecla polsada, false si no
|
||||
bool anyKey();
|
||||
|
||||
/// @brief El equivalent a anykey per a keypressed
|
||||
/// @return true si hi ha alguna tecla polsada, false si no
|
||||
bool anyKeyPressed();
|
||||
|
||||
/// @brief Torna el codi de la tecla que està sent polsada ara mateix
|
||||
/// @return Quina tecla està sent polsada
|
||||
const uint8_t whichKey();
|
||||
|
||||
/// @brief Torna el codi de la tecla que està sent polsada ara mateix
|
||||
/// @brief (nomes una vegada, com keypress)
|
||||
/// @return Quina tecla està sent polsada
|
||||
const uint8_t getKeyPressed();
|
||||
|
||||
/// @brief (US INTERN) Actualitza la tecla actualment polsada (keydown) desde jgame
|
||||
/// @param key tecla polsada
|
||||
void updateKey(const uint8_t key);
|
||||
|
||||
/// @brief (US INTERN) Actualitza la tecla actualment polsada (keypress) desde jgame
|
||||
/// @param key tecla polsada
|
||||
void updateKeypressed(const uint8_t key);
|
||||
|
||||
/// @brief (US INTERN) Actualitza el botó del ratolí actualment polsat desde jgame
|
||||
/// @param btn botó polsat
|
||||
void updateClk(const uint8_t btn);
|
||||
|
||||
/// @brief (US INTERN) Actualitza el valor de la roda del ratolí actual desde jgame
|
||||
/// @param dy desplaçament de la rodeta
|
||||
void updateWheel(const int dy);
|
||||
|
||||
/// @brief Torna la posició X actual del ratolí
|
||||
/// @return valor de la coordenada X del ratolí
|
||||
const int mouseX();
|
||||
|
||||
/// @brief Torna la posició Y actual del ratolí
|
||||
/// @return valor de la coordenada Y del ratolí
|
||||
const int mouseY();
|
||||
|
||||
/// @brief Determina si el botó del ratolí especificat està sent polsada ara mateix
|
||||
/// @brief (Equivalent a keydown en tecles)
|
||||
/// @param btn botó a consultar
|
||||
/// @return true si està polsat, false si no
|
||||
const bool mouseBtn(const int btn);
|
||||
|
||||
/// @brief Determina si el botó especificat ha sigut polsat, pero no tornarà a ser true fins
|
||||
/// @brief que no se solte el botó i se torne a polsar (Equivalent a keypress en tecles).
|
||||
/// @param btn botó a consultar
|
||||
/// @return true si està polsat, false si no
|
||||
const bool mouseClk(const int btn);
|
||||
|
||||
/// @brief Obté el valor actual de la rodeta del ratolí
|
||||
/// @return 0 si no es mou, positiu si roda cap amunt, negatiu si roda cap avall
|
||||
const int mouseWheel();
|
||||
|
||||
}
|
||||
277
source/japi/shader.cpp
Normal file
277
source/japi/shader.cpp
Normal file
@@ -0,0 +1,277 @@
|
||||
#include "shader.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include "CoreFoundation/CoreFoundation.h"
|
||||
#include <OpenGL/OpenGL.h>
|
||||
|
||||
#if ESSENTIAL_GL_PRACTICES_SUPPORT_GL3
|
||||
#include <OpenGL/gl3.h>
|
||||
#else
|
||||
#include <OpenGL/gl.h>
|
||||
#endif //!ESSENTIAL_GL_PRACTICES_SUPPORT_GL3
|
||||
#else
|
||||
#include <SDL3/SDL_opengl.h>
|
||||
#include <SDL3/SDL_opengl_glext.h>
|
||||
#endif
|
||||
|
||||
namespace shader
|
||||
{
|
||||
SDL_Window *win = nullptr;
|
||||
SDL_Renderer *renderer = nullptr;
|
||||
GLuint programId = 0;
|
||||
SDL_Texture* backBuffer = nullptr;
|
||||
SDL_FRect window = {0, 0, 640, 480};
|
||||
SDL_FPoint tex_size = {320, 240};
|
||||
float aspect_ratio = 1;
|
||||
bool can_use_opengl = false;
|
||||
bool using_opengl = false;
|
||||
GLuint texture_number;
|
||||
GLuint nose;
|
||||
|
||||
#ifndef __APPLE__
|
||||
|
||||
// I'm avoiding the use of GLEW or some extensions handler, but that
|
||||
// doesn't mean you should...
|
||||
PFNGLCREATESHADERPROC glCreateShader;
|
||||
PFNGLSHADERSOURCEPROC glShaderSource;
|
||||
PFNGLCOMPILESHADERPROC glCompileShader;
|
||||
PFNGLGETSHADERIVPROC glGetShaderiv;
|
||||
PFNGLGETSHADERINFOLOGPROC glGetShaderInfoLog;
|
||||
PFNGLDELETESHADERPROC glDeleteShader;
|
||||
PFNGLATTACHSHADERPROC glAttachShader;
|
||||
PFNGLCREATEPROGRAMPROC glCreateProgram;
|
||||
PFNGLDELETEPROGRAMPROC glDeleteProgram;
|
||||
PFNGLLINKPROGRAMPROC glLinkProgram;
|
||||
PFNGLVALIDATEPROGRAMPROC glValidateProgram;
|
||||
PFNGLGETPROGRAMIVPROC glGetProgramiv;
|
||||
PFNGLGETPROGRAMINFOLOGPROC glGetProgramInfoLog;
|
||||
PFNGLUSEPROGRAMPROC glUseProgram;
|
||||
PFNGLGETUNIFORMLOCATIONPROC glGetUniformLocation;
|
||||
PFNGLUNIFORM2FPROC glUniform2f;
|
||||
|
||||
bool initGLExtensions() {
|
||||
glCreateShader = (PFNGLCREATESHADERPROC)SDL_GL_GetProcAddress("glCreateShader");
|
||||
glShaderSource = (PFNGLSHADERSOURCEPROC)SDL_GL_GetProcAddress("glShaderSource");
|
||||
glCompileShader = (PFNGLCOMPILESHADERPROC)SDL_GL_GetProcAddress("glCompileShader");
|
||||
glGetShaderiv = (PFNGLGETSHADERIVPROC)SDL_GL_GetProcAddress("glGetShaderiv");
|
||||
glGetShaderInfoLog = (PFNGLGETSHADERINFOLOGPROC)SDL_GL_GetProcAddress("glGetShaderInfoLog");
|
||||
glDeleteShader = (PFNGLDELETESHADERPROC)SDL_GL_GetProcAddress("glDeleteShader");
|
||||
glAttachShader = (PFNGLATTACHSHADERPROC)SDL_GL_GetProcAddress("glAttachShader");
|
||||
glCreateProgram = (PFNGLCREATEPROGRAMPROC)SDL_GL_GetProcAddress("glCreateProgram");
|
||||
glDeleteProgram = (PFNGLDELETEPROGRAMPROC)SDL_GL_GetProcAddress("glDeleteProgram");
|
||||
glLinkProgram = (PFNGLLINKPROGRAMPROC)SDL_GL_GetProcAddress("glLinkProgram");
|
||||
glValidateProgram = (PFNGLVALIDATEPROGRAMPROC)SDL_GL_GetProcAddress("glValidateProgram");
|
||||
glGetProgramiv = (PFNGLGETPROGRAMIVPROC)SDL_GL_GetProcAddress("glGetProgramiv");
|
||||
glGetProgramInfoLog = (PFNGLGETPROGRAMINFOLOGPROC)SDL_GL_GetProcAddress("glGetProgramInfoLog");
|
||||
glUseProgram = (PFNGLUSEPROGRAMPROC)SDL_GL_GetProcAddress("glUseProgram");
|
||||
glGetUniformLocation = (PFNGLGETUNIFORMLOCATIONPROC)SDL_GL_GetProcAddress("glGetUniformLocation");
|
||||
glUniform2f = (PFNGLUNIFORM2FPROC)SDL_GL_GetProcAddress("glUniform2f");
|
||||
|
||||
return glCreateShader && glShaderSource && glCompileShader && glGetShaderiv &&
|
||||
glGetShaderInfoLog && glDeleteShader && glAttachShader && glCreateProgram &&
|
||||
glDeleteProgram && glLinkProgram && glValidateProgram && glGetProgramiv &&
|
||||
glGetProgramInfoLog && glUseProgram && glGetUniformLocation && glUniform2f;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
GLuint compileShader(const char* source, GLuint shaderType) {
|
||||
// Create ID for shader
|
||||
GLuint result = glCreateShader(shaderType);
|
||||
// Add define depending on shader type
|
||||
const char *sources[2] = { shaderType==GL_VERTEX_SHADER?"#define VERTEX\n":"#define FRAGMENT\n", source };
|
||||
// Define shader text
|
||||
glShaderSource(result, 2, sources, NULL);
|
||||
// Compile shader
|
||||
glCompileShader(result);
|
||||
|
||||
//Check vertex shader for errors
|
||||
GLint shaderCompiled = GL_FALSE;
|
||||
glGetShaderiv( result, GL_COMPILE_STATUS, &shaderCompiled );
|
||||
if (shaderCompiled != GL_TRUE)
|
||||
{
|
||||
std::cout << "Error en la compilación: " << result << "!" << std::endl;
|
||||
GLint logLength;
|
||||
glGetShaderiv(result, GL_INFO_LOG_LENGTH, &logLength);
|
||||
if (logLength > 0)
|
||||
{
|
||||
GLchar *log = (GLchar*)malloc(logLength);
|
||||
glGetShaderInfoLog(result, logLength, &logLength, log);
|
||||
std::cout << "Shader compile log:" << log << std::endl;
|
||||
//std::cout << source << std::endl;
|
||||
free(log);
|
||||
}
|
||||
glDeleteShader(result);
|
||||
result = 0;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
GLuint compileProgram(const char* vertexShaderSource, const char* fragmentShaderSource)
|
||||
{
|
||||
GLuint programId = 0;
|
||||
GLuint vtxShaderId, fragShaderId;
|
||||
|
||||
if (programId != 0) glDeleteProgram(programId);
|
||||
programId = glCreateProgram();
|
||||
|
||||
|
||||
vtxShaderId = compileShader(vertexShaderSource, GL_VERTEX_SHADER);
|
||||
fragShaderId = compileShader(fragmentShaderSource?fragmentShaderSource:vertexShaderSource, GL_FRAGMENT_SHADER);
|
||||
|
||||
if(vtxShaderId && fragShaderId)
|
||||
{
|
||||
// Associate shader with program
|
||||
glAttachShader(programId, vtxShaderId);
|
||||
glAttachShader(programId, fragShaderId);
|
||||
glLinkProgram(programId);
|
||||
glValidateProgram(programId);
|
||||
|
||||
// Check the status of the compile/link
|
||||
GLint logLen;
|
||||
glGetProgramiv(programId, GL_INFO_LOG_LENGTH, &logLen);
|
||||
if (logLen > 0)
|
||||
{
|
||||
char* log = (char*) malloc(logLen * sizeof(char));
|
||||
// Show any errors as appropriate
|
||||
glGetProgramInfoLog(programId, logLen, &logLen, log);
|
||||
std::cout << "Prog Info Log: " << std::endl << log << std::endl;
|
||||
free(log);
|
||||
}
|
||||
}
|
||||
if (vtxShaderId) glDeleteShader(vtxShaderId);
|
||||
if (fragShaderId) glDeleteShader(fragShaderId);
|
||||
return programId;
|
||||
}
|
||||
|
||||
const bool init(SDL_Window* win, SDL_Texture* backBuffer, const char* vertexShader, const char* fragmentShader)
|
||||
{
|
||||
shader::win = win;
|
||||
shader::renderer = SDL_GetRenderer(win);
|
||||
shader::backBuffer = backBuffer;
|
||||
int w, h;
|
||||
SDL_GetWindowSize(win, &w, &h);
|
||||
|
||||
if (w * aspect_ratio > h) {
|
||||
window.y = 0;
|
||||
window.h = h;
|
||||
window.w = h/aspect_ratio;
|
||||
window.x = (w - window.w)/2;
|
||||
} else {
|
||||
window.x = 0;
|
||||
window.w = w;
|
||||
window.h = w*aspect_ratio;
|
||||
window.y = (h - window.h)/2;
|
||||
}
|
||||
|
||||
SDL_GetTextureSize(backBuffer, &tex_size.x, &tex_size.y);
|
||||
printf("tex size: %fx%f\n", tex_size.x, tex_size.y);
|
||||
SDL_PropertiesID props = SDL_GetTextureProperties(backBuffer);
|
||||
texture_number = SDL_GetNumberProperty(props, SDL_PROP_TEXTURE_OPENGL_TEXTURE_NUMBER, -1);
|
||||
printf("texture number: %i\n", texture_number);
|
||||
int access = SDL_GetNumberProperty(props, SDL_PROP_TEXTURE_ACCESS_NUMBER, -1);
|
||||
nose = SDL_GetNumberProperty(props, SDL_PROP_TEXTURE_OPENGL_TEXTURE_TARGET_NUMBER, -1);
|
||||
printf("texture target number: %i\n", nose);
|
||||
|
||||
if (access != SDL_TEXTUREACCESS_TARGET)
|
||||
{
|
||||
std::cout << "ERROR FATAL: La textura per al render ha de tindre SDL_TEXTUREACCESS_TARGET definit." << std::endl;
|
||||
exit(1);
|
||||
}
|
||||
|
||||
const char * renderer_name = SDL_GetRendererName(renderer);
|
||||
printf("rendererInfo.name: %s\n", renderer_name);
|
||||
|
||||
if(!strncmp(renderer_name, "opengl", 6)) {
|
||||
#ifndef __APPLE__
|
||||
static bool gl_extensions_initialized = false;
|
||||
if (!gl_extensions_initialized) {
|
||||
if (!initGLExtensions()) {
|
||||
std::cout << "WARNING: No s'han pogut inicialitzar les extensions d'OpenGL!" << std::endl;
|
||||
can_use_opengl = false;
|
||||
return false;
|
||||
}
|
||||
gl_extensions_initialized = true;
|
||||
}
|
||||
#endif
|
||||
// Compilar el shader y dejarlo listo para usar.
|
||||
if (!vertexShader) {
|
||||
can_use_opengl = false;
|
||||
return false;
|
||||
}
|
||||
programId = compileProgram(vertexShader, fragmentShader);
|
||||
} else {
|
||||
std::cout << "WARNING: El driver del renderer no es OpenGL." << std::endl;
|
||||
can_use_opengl = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
can_use_opengl = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
unsigned char pixels[512*240*4];
|
||||
|
||||
void enable() { if (can_use_opengl) using_opengl = true; }
|
||||
void disable() { using_opengl = false; }
|
||||
|
||||
void setAspectRatio(const float ratio)
|
||||
{
|
||||
aspect_ratio = ratio;
|
||||
}
|
||||
|
||||
void render()
|
||||
{
|
||||
SDL_FlushRenderer(renderer);
|
||||
SDL_SetRenderTarget(renderer, NULL);
|
||||
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
|
||||
SDL_RenderClear(renderer);
|
||||
SDL_FlushRenderer(renderer);
|
||||
|
||||
if (using_opengl)
|
||||
{
|
||||
GLint oldProgramId;
|
||||
if (programId != 0)
|
||||
{
|
||||
glGetIntegerv(GL_CURRENT_PROGRAM, &oldProgramId);
|
||||
glUseProgram(programId);
|
||||
}
|
||||
|
||||
//GLint loc = glGetUniformLocation(programId, "TextureSize");
|
||||
//glUniform2f(loc, 320, 256);
|
||||
|
||||
glEnable(GL_TEXTURE_2D);
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glBindTexture(GL_TEXTURE_2D, 1);
|
||||
//glGetTexImage(GL_TEXTURE_2D, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8, pixels);
|
||||
//if (glGetError()) { printf("GLGETERROR!\n"); exit(1);}
|
||||
//GLint param;
|
||||
//glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, ¶m);
|
||||
//printf("tex width: %i\n", param);
|
||||
glViewport(window.x, window.y, window.w, window.h);
|
||||
|
||||
glBegin(GL_TRIANGLE_STRIP);
|
||||
glTexCoord2f(0.0f, 0.0f);
|
||||
glVertex2f(-1.0f, -1.0f);
|
||||
glTexCoord2f(tex_size.x, 0.0f);
|
||||
glVertex2f(1.0f, -1.0f);
|
||||
glTexCoord2f(0.0f, tex_size.y);
|
||||
glVertex2f(-1.0f, 1.0f);
|
||||
glTexCoord2f(tex_size.x, tex_size.y);
|
||||
glVertex2f(1.0f, 1.0f);
|
||||
glEnd();
|
||||
|
||||
SDL_GL_SwapWindow(win);
|
||||
|
||||
if (programId != 0) glUseProgram(oldProgramId);
|
||||
|
||||
} else {
|
||||
SDL_RenderTexture(renderer, backBuffer, NULL, &window);
|
||||
SDL_RenderPresent(renderer);
|
||||
}
|
||||
int glerror = glGetError();
|
||||
if (glerror) { printf("GLERROR: %i\n", glerror); exit(1); }
|
||||
}
|
||||
}
|
||||
48
source/japi/shader.h
Normal file
48
source/japi/shader.h
Normal file
@@ -0,0 +1,48 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
// TIPS:
|
||||
// =======================================================================
|
||||
// Abans de crear el renderer, cridar a la següent funció:
|
||||
//
|
||||
// SDL_SetHint(SDL_HINT_RENDER_DRIVER, "opengl");
|
||||
//
|
||||
// Aixó li diu que volem un renderer que use especificament opengl. A més,
|
||||
// al crear el renderer li tenim que dir que el volem que use acceeració
|
||||
// per hardware, i que soporte render a textura. Per exemple:
|
||||
//
|
||||
// SDL_Renderer *ren = SDL_CreateRenderer(win, -1, SDL_RENDERER_ACCELERATED |
|
||||
// SDL_RENDERER_TARGETTEXTURE);
|
||||
//
|
||||
// Per altra part, al crear la textura tenim que definir que puga ser target
|
||||
// de renderitzat (SDL_TEXTUREACCESS_TARGET), per exemple:
|
||||
//
|
||||
// SDL_Texture *tex = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888,
|
||||
// SDL_TEXTUREACCESS_TARGET, 320, 240);
|
||||
//
|
||||
// Els shaders li'ls passem com una cadena, som nosaltres els que s'encarreguem
|
||||
// de carregarlos de disc, amb fopen, ifstream, jfile o el que vullgues.
|
||||
// Si els tens en un std::string, passa-li-la com "cadena.c_str()".
|
||||
//
|
||||
// Poden ser els dos el mateix arxiu, com fa libRetro, jo desde dins ja fique
|
||||
// els defines necessaris. Si es el mateix arxiu, pots no ficar el quart paràmetre.
|
||||
//
|
||||
// Els shaders de libRetro no funcionen directament, hi ha que fer algunes modificacions.
|
||||
//
|
||||
// El pintat final de la teua escena l'has de fer com si "backBuffer" fora la pantalla.
|
||||
//
|
||||
// Ah! una cosa mes: al compilar, en Linux afegir "-lGL", en Windows afegir "-lopengl32".
|
||||
// En Mac ni idea
|
||||
|
||||
namespace shader
|
||||
{
|
||||
const bool init(SDL_Window* win, SDL_Texture* backBuffer,
|
||||
const char* vertexShader, const char* fragmentShader=nullptr);
|
||||
|
||||
void setAspectRatio(const float ratio);
|
||||
void enable();
|
||||
void disable();
|
||||
|
||||
void render();
|
||||
}
|
||||
5584
source/japi/stb_vorbis.h
Normal file
5584
source/japi/stb_vorbis.h
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user