feat: sound (#28)

* build: add OpenAL dependency

* feat(deps): add dr_libs audio library

* feat(audio): integrate OpenAL audio engine with WAV/MP3/FLAC support

* feat: add bgm001

* feat(audio): add audio fade in/out and fix delta time naming

* fix(audio-source): improve state, volume, and const correctness

* feat(audio): add 3D audio playback with source pool and auto-loading

* feat: add ogg loader

* feat(audio): add footstep sounds and fix audio listener orientation

* feat(sound): add block place and break sounds

* feat(audio): add per-channel volume control and config reload

* fix(audio): set distance model, reduce max distance, fix sound pos

- Set OpenAL distance model to AL_INVERSE_DISTANCE_CLAMPED in engine.
- Reduced AL_MAX_DISTANCE from 100 to 48 for better falloff.
- Fixed block sound position to use world coordinates instead of local block coordinates.

* refactor(game_time): introduce Timer for time-based updates and rename existing to TickTimer

Add a new `Timer` class that operates in seconds (using `TimeType`) alongside the existing `TickTimer` which uses `TickType`. Rename the original `Timer` to `TickTimer` throughout the codebase. Update `ClientWorld` to maintain separate maps for tick-based and time-based timers, and replace the hardcoded footstep timer in `ClientPlayer` with a time-based callback timer. Also add `play_2d` to `AudioEngine` and load an ambient bird sound to demonstrate the new timer functionality.

* feat(gameplay): add ocean wave ambient sounds with random selection

Add four ocean wave FLAC sound files and play one randomly when player is within 10 blocks of sea level and in an ocean biome. Include a Random member initialized from ChunkGenerator seed for consistent randomization.

* feat(gameplay): detect underwater state and play transition/bubble sounds

- Add is_underwater() and set_underwater() to ClientPlayer.
- Change get_world() to return non-const reference for audio access.
- Update Camera::update_move_camera() to detect water block changes and trigger transition sound.
- Add repeating timer in ClientWorld to play random bubble ambient sounds while underwater.
- Include new ambient sound assets for water transitions and bubbles.
- Also add sand walk sound asset (unused in this commit).

* feat(assets): add and update block break/place/walk sounds

Add sounds for dirt, grass_block, leaf, log, sand, snowy_grass_block, stone.
Update existing leaf/walk, sand/place, sand/walk, and stone/place sounds.

* feat(gameplay): add walking sound for players and adjust underwater bubble interval

- Move walk/run sound interval constants from anonymous namespace to ClientPlayer header
- Add moving_time field to PlayerInfo to track continuous movement
- Implement play_walk_sound callback triggered by WALK and RUN gaits
- Increase underwater bubble timer from 1.0s to 1.5s

* feat(asset): add grass block break and place sound effects

* feat(renderer): add --no-debug flag to disable OpenGL debug output

* feat(audio): add day/night-based BGM switching

* feat(audio): add underwater low-pass filter effect

* feat(audio): add pitch control to audio sources

Implement set_pitch method that clamps pitch between 0.0 and 1.0 and applies it via AL_PITCH. Includes minor whitespace cleanup in audio_engine.cpp.

* feat(audio): implement reverb effect and integrate into underwater system

* asset(sound): update dirt and grass block break/place sounds

* feat(network): synchronize player water sound

* fix: include <numbers>

* fix(audio): play ambient water bubble sound in 3D at player position
This commit is contained in:
zhenyan121
2026-07-08 19:41:10 +08:00
committed by GitHub
parent 8a4081cf9c
commit b1341d7da4
88 changed files with 34374 additions and 53 deletions

View File

@@ -44,4 +44,14 @@ target_sources(${PROJECT_NAME}
gameplay/session.cpp
gameplay/network_client.cpp
player_renderer.cpp
audio/audio_engine.cpp
audio/audio_loader.cpp
audio/audio_source.cpp
audio/audio_buffer.cpp
audio/sound_manager.cpp
audio/source_pool.cpp
audio/audio_fade.cpp
audio/audio_filter.cpp
audio/audio_effect.cpp
audio/audio_effect_slot.cpp
)

View File

@@ -12,7 +12,7 @@
#include <imgui_impl_glfw.h>
namespace Cubed {
App::App() {}
App::App() : m_client_world(m_audio) {}
App::~App() {
if (m_client) {
@@ -59,9 +59,11 @@ void App::init(int argc, char** argv) {
cursor_enter_callback);
glfwSetCharCallback(m_window.get_glfw_window(), char_callback);
m_audio.init();
ChunkGenerator::init();
BlockManager::init();
m_renderer.init();
m_renderer.init(m_argument.debug_on);
Logger::info("Renderer Init Success");
m_window.update_viewport();
// MapTable::init_map();
@@ -125,6 +127,12 @@ void App::handle_argument(int argc, char** argv) {
[&](ArgParser) {
std::cout << CUBED_VERSION << "\n";
exit(EXIT_SUCCESS);
}},
{"--no-debug",
[&](ArgParser) {
m_argument.debug_on = false;
Logger::info("Switch off opengl debug out put");
}}
};
@@ -327,9 +335,9 @@ static Gait player_gait = Gait::WALK;
void App::update() {
glfwPollEvents();
current_time = glfwGetTime();
delta_time = current_time - last_time;
dt = current_time - last_time;
last_time = current_time;
fps_time_count += delta_time;
fps_time_count += dt;
frame_count++;
if (fps_time_count >= 1.0f) {
fps = static_cast<int>(frame_count / fps_time_count);
@@ -344,7 +352,7 @@ void App::update() {
std::format("RSS: {}mb", Tools::get_current_rss() / (1024 * 1024)));
}
m_texture_manager.update();
m_client_world.update(delta_time);
m_client_world.update(dt);
m_camera.update_move_camera();
const auto& player = m_client_world.get_player();
if (player_gait != player.get_gait()) {
@@ -357,7 +365,10 @@ void App::update() {
m_renderer.update_fov(fov + 5.0f);
}
}
m_renderer.update(delta_time);
m_audio.update_listener(m_camera.get_camera_pos(),
m_camera.get_camera_front(), glm::vec3(0, 1, 0));
m_audio.update();
m_renderer.update(dt);
}
int App::start_cubed_application(int argc, char** argv) {
@@ -379,7 +390,7 @@ int App::start_cubed_application(int argc, char** argv) {
return 1;
}
float App::delte_time() { return delta_time; }
float App::delta_time() { return dt; }
float App::get_fps() { return fps; }
@@ -391,4 +402,5 @@ Window& App::window() { return m_window; }
ClientWorld& App::client_world() { return m_client_world; }
ServerWorld& App::server_world() { return m_server.server_world(); }
const App::Argument& App::argument() const { return m_argument; }
AudioEngine& App::audio() { return m_audio; }
} // namespace Cubed

View File

@@ -0,0 +1,43 @@
#include "Cubed/audio/audio_buffer.hpp"
#include "Cubed/audio/audio_error.hpp"
namespace Cubed {
AudioBuffer::AudioBuffer(const AudioData& data) {
alGenBuffers(1, &m_buffer);
set_data(data);
}
AudioBuffer::~AudioBuffer() { alDeleteBuffers(1, &m_buffer); }
ALuint AudioBuffer::buffer() const { return m_buffer; }
float AudioBuffer::duration() const { return m_duration; }
uint32_t AudioBuffer::channels() const { return m_channels; }
void AudioBuffer::set_data(const AudioData& data) {
Logger::info("buffer={} valid={}", m_buffer, (int)alIsBuffer(m_buffer));
Logger::info("buffer={} isBuffer={}", m_buffer, (int)alIsBuffer(m_buffer));
ALenum format;
if (data.channels == 1) {
format = AL_FORMAT_MONO16;
} else {
format = AL_FORMAT_STEREO16;
}
m_channels = data.channels;
Logger::info("channels={} rate={} samples={} bytes={} format={}",
data.channels, data.sample_rate, data.pcm.size(),
data.pcm.size() * sizeof(int16_t), format);
alBufferData(m_buffer, format, data.pcm.data(),
static_cast<ALsizei>(data.pcm.size() * sizeof(int16_t)),
static_cast<ALsizei>(data.sample_rate));
check_al_error();
ALint size = 0;
alGetBufferi(m_buffer, AL_SIZE, &size);
check_al_error();
Logger::info("buffer size={}", size);
m_duration = static_cast<float>(data.pcm.size()) /
(data.channels * data.sample_rate);
}
} // namespace Cubed

View File

@@ -0,0 +1,26 @@
#include "Cubed/audio/audio_effect.hpp"
#ifndef AL_ALEXT_PROTOTYPES
#define AL_ALEXT_PROTOTYPES
#endif
#include <AL/efx.h>
namespace Cubed {
AudioEffect::AudioEffect() { alGenEffects(1, &m_effect); }
AudioEffect::~AudioEffect() {
if (m_effect) {
alDeleteEffects(1, &m_effect);
}
}
void AudioEffect::set_reverb(float decay_time, float reverb_gain, float gain_hf,
float density, float diffusion) {
alEffecti(m_effect, AL_EFFECT_TYPE, AL_EFFECT_REVERB);
alEffectf(m_effect, AL_REVERB_DECAY_TIME, decay_time);
alEffectf(m_effect, AL_REVERB_GAIN, reverb_gain);
alEffectf(m_effect, AL_REVERB_GAINHF, gain_hf);
alEffectf(m_effect, AL_REVERB_DENSITY, density);
alEffectf(m_effect, AL_REVERB_DIFFUSION, diffusion);
}
ALuint AudioEffect::effect() const { return m_effect; }
} // namespace Cubed

View File

@@ -0,0 +1,19 @@
#include "Cubed/audio/audio_effect_slot.hpp"
#ifndef AL_ALEXT_PROTOTYPES
#define AL_ALEXT_PROTOTYPES
#endif
#include <AL/efx.h>
namespace Cubed {
AudioEffectSlot::AudioEffectSlot() { alGenAuxiliaryEffectSlots(1, &m_slot); }
AudioEffectSlot::~AudioEffectSlot() {
if (m_slot) {
alDeleteAuxiliaryEffectSlots(1, &m_slot);
}
}
void AudioEffectSlot::set_effect(const AudioEffect& effect) {
alAuxiliaryEffectSloti(m_slot, AL_EFFECTSLOT_EFFECT, effect.effect());
}
ALuint AudioEffectSlot::slot() const { return m_slot; }
} // namespace Cubed

227
src/audio/audio_engine.cpp Normal file
View File

@@ -0,0 +1,227 @@
#include "Cubed/audio/audio_engine.hpp"
#include "Cubed/audio/audio_error.hpp"
#include "Cubed/config.hpp"
#include "Cubed/tools/cubed_assert.hpp"
#include "Cubed/tools/log.hpp"
#include <stdexcept>
namespace Cubed {
AudioEngine::AudioEngine() {};
AudioEngine::~AudioEngine() {
if (!m_init) {
return;
}
m_bgm.reset();
m_pool.reset();
m_sounds.clear();
m_low_pass_filter.reset();
m_underwater_effect.reset();
m_underwater_slot.reset();
alcMakeContextCurrent(nullptr);
alcDestroyContext(context);
alcCloseDevice(device);
}
void AudioEngine::init() {
device = alcOpenDevice(NULL);
if (!device) {
throw std::runtime_error("Failed to open OpenAL device.");
}
context = alcCreateContext(device, nullptr);
if (!context) {
throw std::runtime_error("Failed to create OpenAL context.");
}
alcMakeContextCurrent(context);
if (!alcIsExtensionPresent(device, "ALC_EXT_EFX")) {
Logger::error("EFX not supported!");
m_efx_supported = false;
} else {
Logger::info("EFX supported!");
m_efx_supported = true;
}
if (m_efx_supported) {
m_low_pass_filter = std::make_unique<AudioFilter>();
m_low_pass_filter->set_lowpass(1.0f, 0.15f);
m_underwater_effect = std::make_unique<AudioEffect>();
m_underwater_effect->set_reverb(0.8f, 0.3162f, 0.01f);
// m_underwater_effect->set_reverb(20.0f, 1.0f, 1.0f);
m_underwater_slot = std::make_unique<AudioEffectSlot>();
m_underwater_slot->set_effect(*m_underwater_effect);
}
alDistanceModel(AL_INVERSE_DISTANCE_CLAMPED);
check_al_error();
auto& config = Config::get();
m_music_volume = static_cast<float>(config.get<double>("volume.music"));
m_sfx_volume = static_cast<float>(config.get<double>("volume.SFX"));
m_sounds.init();
m_bgm = std::make_unique<AudioSource>(m_music_volume);
m_bgm->set_buffer_2d(m_sounds.get_buffer("bgm/bgm001.mp3"));
m_fade_map.try_emplace("bgm", m_bgm.get(), 5.0f, 2.0f);
ALCint max_mono = 0;
alcGetIntegerv(device, ALC_MONO_SOURCES, 1, &max_mono);
if (max_mono <= 1) {
Logger::error("Can't get max mono");
max_mono = 4;
}
Logger::info("Set Source Pool Size {}", static_cast<int>(max_mono));
// Reserve a source for BGM
m_pool = std::make_shared<SourcePool>(max_mono - 1);
Logger::info("Audio Engine Init Success");
m_init = true;
}
void AudioEngine::play_bgm() { m_bgm->play(); }
void AudioEngine::change_bgm(const std::string& sound) {
Logger::info("change bgm {}", sound);
m_bgm->stop();
m_bgm->reset();
m_bgm->set_buffer_2d(m_sounds.get_buffer(sound));
m_bgm->set_target_volume(m_music_volume);
m_bgm->set_volume(m_music_volume);
if (m_efx_supported && m_underwater) {
m_bgm->set_filter(*m_low_pass_filter);
}
auto it = m_fade_map.find("bgm");
if (it != m_fade_map.end()) {
it->second.reset();
}
m_bgm->play();
};
void AudioEngine::play_3d(const std::string& sound, const glm::vec3& pos,
bool check) {
if (!m_pool) {
Logger::error("Source Pool is nullptr");
return;
}
auto* source = m_pool->acquire();
source->set_volume(m_sfx_volume);
if (!source) {
Logger::error("Source is Full");
}
try {
auto& buffer = m_sounds.get_buffer(sound);
if (m_efx_supported && m_underwater) {
source->set_filter(*m_low_pass_filter);
source->set_effect_slot(*m_underwater_slot);
}
source->play_3d(buffer, pos);
} catch (const std::exception& e) {
if (check) {
ASSERT_MSG(false, e.what());
Logger::error("Player Sound Error {}", e.what());
}
}
}
void AudioEngine::play_2d(const std::string& sound, bool check) {
if (!m_pool) {
Logger::error("Source Pool is nullptr");
return;
}
auto* source = m_pool->acquire();
source->set_volume(m_sfx_volume);
if (!source) {
Logger::error("Source is Full");
}
try {
auto& buffer = m_sounds.get_buffer(sound);
if (m_efx_supported && m_underwater) {
source->set_filter(*m_low_pass_filter);
source->set_effect_slot(*m_underwater_slot);
}
source->play_2d(buffer);
} catch (const std::exception& e) {
if (check) {
ASSERT_MSG(false, e.what());
Logger::error("Player Sound Error {}", e.what());
}
}
}
void AudioEngine::update_listener(const glm::vec3& pos,
const glm::vec3& forward,
const glm::vec3& up) {
alListener3f(AL_POSITION, pos.x, pos.y, pos.z);
float orientation[] = {forward.x, forward.y, forward.z,
up.x, up.y, up.z};
alListenerfv(AL_ORIENTATION, orientation);
}
void AudioEngine::update() {
for (auto& [key, fade] : m_fade_map) {
fade.update();
}
m_pool->update();
}
void AudioEngine::reload_config() {
auto& config = Config::get();
m_music_volume = static_cast<float>(config.get<double>("volume.music"));
m_sfx_volume = static_cast<float>(config.get<double>("volume.SFX"));
if (m_bgm) {
m_bgm->set_target_volume(m_music_volume);
}
}
void AudioEngine::underwater_change(bool underwater) {
m_underwater = underwater;
if (!m_efx_supported) {
return;
}
if (!m_pool) {
return;
}
for (auto& source : m_pool->sources()) {
if (m_underwater) {
source.set_filter(*m_low_pass_filter);
source.set_effect_slot(*m_underwater_slot);
} else {
source.clear_filter();
source.clear_effect_slot();
}
}
if (underwater) {
m_bgm->set_filter(*m_low_pass_filter);
} else {
m_bgm->clear_filter();
}
Logger::info("Under Water Change {}", m_underwater);
}
float& AudioEngine::bgm_target_volume() { return m_bgm->target_volume(); }
} // namespace Cubed

70
src/audio/audio_fade.cpp Normal file
View File

@@ -0,0 +1,70 @@
#include "Cubed/audio/audio_fade.hpp"
#include <algorithm>
#include <cmath>
#include <numbers>
namespace Cubed {
AudioFade::AudioFade(AudioSource* source, float fade_in, float fade_out)
: m_source(source), m_in_duration(fade_in), m_out_duration(fade_out) {
if (!m_source) {
m_active = false;
return;
}
m_start_gain = 0.0f;
m_source->set_volume(0.0f);
}
AudioFade::~AudioFade() {}
void AudioFade::reset() {
m_active = true;
m_fade_in = true;
m_start_gain = 0.0f;
m_source->set_volume(0.0f);
}
void AudioFade::update() {
if (!m_active || !m_source) {
return;
}
if (m_fade_in) {
float t =
std::clamp(m_source->current_time() / m_in_duration, 0.0f, 1.0f);
// Ease Out Sine
t = std::sin(t * std::numbers::pi_v<float> * 0.5f);
float gain = std::lerp(m_start_gain, m_source->target_volume(), t);
m_source->set_volume(gain);
if (t >= 1.0f) {
m_source->set_volume(m_source->target_volume());
m_fade_in = false;
m_start_gain = m_source->target_volume();
}
} else {
float start_time =
std::max(0.0f, m_source->duration() - m_out_duration);
if (m_source->current_time() < start_time) {
return;
}
float elapsed = m_source->current_time() - start_time;
float t = std::clamp(elapsed / m_out_duration, 0.0f, 1.0f);
// Smoothstep
t = t * t * (3.0f - 2.0f * t);
float gain = std::lerp(m_start_gain, 0.0f, t);
m_source->set_volume(gain);
if (t >= 1.0f) {
m_source->set_volume(0.0f);
m_active = false;
}
}
}
} // namespace Cubed

View File

@@ -0,0 +1,22 @@
#include "Cubed/audio/audio_filter.hpp"
#ifndef AL_ALEXT_PROTOTYPES
#define AL_ALEXT_PROTOTYPES
#endif
#include <AL/alc.h>
#include <AL/efx.h>
namespace Cubed {
AudioFilter::AudioFilter() { alGenFilters(1, &m_filter); }
AudioFilter::~AudioFilter() {
if (m_filter != 0) {
alDeleteFilters(1, &m_filter);
}
}
void AudioFilter::set_lowpass(float gain, float gain_hf) {
alFilteri(m_filter, AL_FILTER_TYPE, AL_FILTER_LOWPASS);
alFilterf(m_filter, AL_LOWPASS_GAIN, gain);
alFilterf(m_filter, AL_LOWPASS_GAINHF, gain_hf);
}
ALuint AudioFilter::filter() const { return m_filter; }
} // namespace Cubed

143
src/audio/audio_loader.cpp Normal file
View File

@@ -0,0 +1,143 @@
#include "Cubed/audio/audio_loader.hpp"
#include "Cubed/tools/log.hpp"
#define STB_VORBIS_IMPLEMENTATION
#include "stb/stb_vorbis.h"
#include <dr_flac.h>
#include <dr_mp3.h>
#include <dr_wav.h>
namespace fs = std::filesystem;
namespace Cubed {
AudioData AudioLoader::load(const std::filesystem::path& path) {
if (!fs::is_regular_file(path)) {
std::string err = std::format("Path {} is not a file", path.string());
throw std::runtime_error(err);
}
auto ext = path.extension().string();
if (ext == ".wav") {
return load_wav(path);
}
if (ext == ".mp3") {
return load_mp3(path);
}
if (ext == ".flac") {
return load_flac(path);
}
if (ext == ".ogg") {
return load_ogg(path);
}
throw std::runtime_error(std::format("Unsupported audio format {}", ext));
}
AudioData AudioLoader::load_wav(const std::filesystem::path& path) {
drwav wav{};
if (!drwav_init_file(&wav, path.string().c_str(), nullptr)) {
throw std::runtime_error("Failed to open wav");
}
AudioData data;
data.channels = wav.channels;
data.sample_rate = wav.sampleRate;
data.pcm.resize(wav.totalPCMFrameCount * wav.channels);
drwav_read_pcm_frames_s16(&wav, wav.totalPCMFrameCount, data.pcm.data());
drwav_uninit(&wav);
Logger::info("{} channels={} rate={} samples={}", path.filename().string(),
data.channels, data.sample_rate, data.pcm.size());
return data;
}
AudioData AudioLoader::load_mp3(const std::filesystem::path& path) {
drmp3_config config;
drmp3_uint64 frame_count;
int16_t* pcm = drmp3_open_file_and_read_pcm_frames_s16(
path.string().c_str(), &config, &frame_count, nullptr);
if (!pcm)
throw std::runtime_error("mp3 load failed");
AudioData data;
data.channels = config.channels;
data.sample_rate = config.sampleRate;
data.pcm.assign(pcm, pcm + frame_count * config.channels);
drmp3_free(pcm, nullptr);
Logger::info("{} channels={} rate={} samples={}", path.filename().string(),
data.channels, data.sample_rate, data.pcm.size());
return data;
}
AudioData AudioLoader::load_flac(const std::filesystem::path& path) {
drflac_uint64 frame_count;
unsigned int channels;
unsigned int sample_rate;
int16_t* pcm = drflac_open_file_and_read_pcm_frames_s16(
path.string().c_str(), &channels, &sample_rate, &frame_count, nullptr);
if (!pcm)
throw std::runtime_error("Failed to load flac");
AudioData data;
data.channels = channels;
data.sample_rate = sample_rate;
data.pcm.assign(pcm, pcm + frame_count * channels);
drflac_free(pcm, nullptr);
Logger::info("{} channels={} rate={} samples={}", path.filename().string(),
data.channels, data.sample_rate, data.pcm.size());
return data;
}
AudioData AudioLoader::load_ogg(const std::filesystem::path& path) {
int error = 0;
stb_vorbis* vorbis =
stb_vorbis_open_filename(path.string().c_str(), &error, nullptr);
if (!vorbis) {
throw std::runtime_error("Failed to open Ogg Vorbis file: " +
path.string());
}
stb_vorbis_info info = stb_vorbis_get_info(vorbis);
int channels = info.channels;
int sample_rate = info.sample_rate;
int total_frames = stb_vorbis_stream_length_in_samples(vorbis);
if (total_frames <= 0) {
stb_vorbis_close(vorbis);
throw std::runtime_error(
"Failed to get Ogg stream length (or stream is empty)");
}
std::vector<int16_t> pcm(total_frames * channels);
int frames_read = stb_vorbis_get_samples_short_interleaved(
vorbis, channels, pcm.data(), pcm.size());
if (frames_read < total_frames) {
pcm.resize(frames_read * channels);
}
stb_vorbis_close(vorbis);
AudioData data;
data.channels = channels;
data.sample_rate = sample_rate;
data.pcm = std::move(pcm);
Logger::info("{} channels={} rate={} samples={}", path.filename().string(),
data.channels, data.sample_rate, data.pcm.size());
return data;
}
} // namespace Cubed

183
src/audio/audio_source.cpp Normal file
View File

@@ -0,0 +1,183 @@
#include "Cubed/audio/audio_source.hpp"
#include "Cubed/audio/audio_error.hpp"
#include "Cubed/tools/log.hpp"
#include <AL/efx.h>
#include <algorithm>
#include <stdexcept>
namespace Cubed {
AudioSource::AudioSource(float volume) {
alGenSources(1, &m_source);
set_target_volume(volume);
}
AudioSource::~AudioSource() {
if (m_source != 0) {
stop();
alDeleteSources(1, &m_source);
}
}
void AudioSource::set_buffer_2d(const AudioBuffer& buffer) {
if (state() != AudioState::STOPPED && state() != AudioState::INITIAL) {
stop();
}
m_duration = buffer.duration();
alSourcei(m_source, AL_BUFFER, buffer.buffer());
alSourcei(m_source, AL_SOURCE_RELATIVE, AL_TRUE);
alSource3f(m_source, AL_POSITION, 0, 0, 0);
}
void AudioSource::set_buffer_3d(const AudioBuffer& buffer,
const glm::vec3& pos) {
if (buffer.channels() != 1) {
Logger::warn("3D sound should use mono audio.");
}
if (state() != AudioState::STOPPED && state() != AudioState::INITIAL) {
stop();
}
m_duration = buffer.duration();
alSourcei(m_source, AL_BUFFER, buffer.buffer());
check_al_error();
alSource3f(m_source, AL_POSITION, pos.x, pos.y, pos.z);
check_al_error();
alSourcef(m_source, AL_REFERENCE_DISTANCE, 4.0f);
alSourcef(m_source, AL_ROLLOFF_FACTOR, 1.0f);
alSourcef(m_source, AL_MAX_DISTANCE, 48.0f);
/*
ALfloat l[3];
alGetListenerfv(AL_POSITION, l);
Logger::info("Listener Pos = ({}, {}, {})", l[0], l[1], l[2]);
ALfloat p[3];
alGetSourcefv(m_source, AL_POSITION, p);
Logger::info("Source Pos = ({}, {}, {})", p[0], p[1], p[2]);
*/
}
void AudioSource::set_loop(bool on) {
if (on) {
alSourcei(m_source, AL_LOOPING, AL_TRUE);
} else {
alSourcei(m_source, AL_LOOPING, AL_FALSE);
}
}
void AudioSource::set_volume(float volume) {
volume = std::clamp(volume, 0.0f, 1.0f);
alSourcef(m_source, AL_GAIN, volume);
}
void AudioSource::set_pitch(float pitch) {
pitch = std::clamp(pitch, 0.0f, 1.0f);
alSourcef(m_source, AL_PITCH, pitch);
}
void AudioSource::play() { alSourcePlay(m_source); }
void AudioSource::play_2d(const AudioBuffer& buffer) {
set_buffer_2d(buffer);
play();
}
void AudioSource::play_3d(const AudioBuffer& buffer, const glm::vec3& pos) {
set_buffer_3d(buffer, pos);
play();
}
void AudioSource::stop() { alSourceStop(m_source); }
void AudioSource::pause() { alSourcePause(m_source); }
float AudioSource::duration() const { return m_duration; }
float AudioSource::current_time() const {
ALfloat sec = 0.0f;
alGetSourcef(m_source, AL_SEC_OFFSET, &sec);
return static_cast<float>(sec);
}
float AudioSource::target_volume() const { return m_target_volume; }
float& AudioSource::target_volume() { return m_target_volume; }
void AudioSource::set_target_volume(float volume) {
m_target_volume = volume;
set_volume(m_target_volume);
}
AudioState AudioSource::state() const {
ALint state;
alGetSourcei(m_source, AL_SOURCE_STATE, &state);
switch (state) {
case AL_INITIAL:
return AudioState::INITIAL;
case AL_PLAYING:
return AudioState::PLAYING;
case AL_STOPPED:
return AudioState::STOPPED;
case AL_PAUSED:
return AudioState::PAUSED;
default:
throw std::runtime_error("Invalid OpenAL source state");
}
throw std::runtime_error("Invaild state");
}
void AudioSource::mark_in_use() { m_using = true; }
bool AudioSource::in_use() const { return m_using; }
void AudioSource::reset() {
alSourceStop(m_source);
alSourcei(m_source, AL_BUFFER, 0);
alSourcef(m_source, AL_GAIN, 1.0f);
alSourcef(m_source, AL_PITCH, 1.0f);
alSource3f(m_source, AL_POSITION, 0.0f, 0.0f, 0.0f);
alSource3f(m_source, AL_VELOCITY, 0.0f, 0.0f, 0.0f);
alSource3f(m_source, AL_DIRECTION, 0.0f, 0.0f, 0.0f);
alSourcef(m_source, AL_REFERENCE_DISTANCE, 1.0f);
alSourcef(m_source, AL_ROLLOFF_FACTOR, 1.0f);
alSourcef(m_source, AL_MAX_DISTANCE, FLT_MAX);
alSourcef(m_source, AL_CONE_INNER_ANGLE, 360.0f);
alSourcef(m_source, AL_CONE_OUTER_ANGLE, 360.0f);
alSourcef(m_source, AL_CONE_OUTER_GAIN, 0.0f);
alSourcei(m_source, AL_LOOPING, AL_FALSE);
alSourcei(m_source, AL_SOURCE_RELATIVE, AL_FALSE);
alSourcef(m_source, AL_SEC_OFFSET, 0.0f);
clear_filter();
clear_effect_slot();
m_duration = 0.0f;
m_target_volume = 1.0f;
m_using = false;
}
void AudioSource::set_filter(const AudioFilter& filter) {
alSourcei(m_source, AL_DIRECT_FILTER, filter.filter());
}
void AudioSource::clear_filter() {
alSourcei(m_source, AL_DIRECT_FILTER, AL_FILTER_NULL);
}
void AudioSource::set_effect_slot(const AudioEffectSlot& slot) {
alSource3i(m_source, AL_AUXILIARY_SEND_FILTER, slot.slot(), 0,
AL_FILTER_NULL);
}
void AudioSource::clear_effect_slot() {
alSource3i(m_source, AL_AUXILIARY_SEND_FILTER, AL_EFFECTSLOT_NULL, 0,
AL_FILTER_NULL);
}
} // namespace Cubed

View File

@@ -0,0 +1,49 @@
#include "Cubed/audio/sound_manager.hpp"
#include "Cubed/audio/audio_loader.hpp"
#include "Cubed/tools/log.hpp"
#include <filesystem>
namespace fs = std::filesystem;
namespace Cubed {
SoundManager::SoundManager() {}
SoundManager::~SoundManager() { clear(); }
void SoundManager::clear() { m_buffers.clear(); }
void SoundManager::init() {
try {
load("bgm/bgm001.mp3");
load("bgm/bgm002.ogg");
load("ambient/birds.ogg");
} catch (const std::exception& e) {
}
}
const AudioBuffer& SoundManager::load(const std::string& name) {
fs::path sound_path{fs::path(ASSETS_PATH) / "sound" / name};
try {
AudioData data = AudioLoader::load(sound_path);
auto [pos, inserted] = m_buffers.try_emplace(name, data);
if (!inserted) {
Logger::error("Key Already exist, check the sound name {}", name);
}
return pos->second;
} catch (const std::exception& e) {
Logger::error("Load Sound Error {}", e.what());
throw;
}
}
const AudioBuffer& SoundManager::get_buffer(const std::string& name) {
auto it = m_buffers.find(name);
if (it == m_buffers.end()) {
try {
return load(name);
} catch (const std::exception& e) {
std::string err = std::format("Can't Find Buffer {}", name);
throw std::runtime_error(err);
}
}
return it->second;
}
} // namespace Cubed

24
src/audio/source_pool.cpp Normal file
View File

@@ -0,0 +1,24 @@
#include "Cubed/audio/source_pool.hpp"
namespace Cubed {
SourcePool::SourcePool(size_t size) { m_sources.resize(size); }
SourcePool::~SourcePool() { m_sources.clear(); }
void SourcePool::update() {
for (auto& source : m_sources) {
if (source.in_use() && source.state() == AudioState::STOPPED) {
source.reset();
}
}
}
AudioSource* SourcePool::acquire() {
for (auto& source : m_sources) {
if (!source.in_use()) {
source.mark_in_use();
return &source;
}
}
return nullptr;
}
std::vector<AudioSource>& SourcePool::sources() { return m_sources; }
} // namespace Cubed

View File

@@ -40,10 +40,21 @@ void Camera::update_move_camera() {
}
glm::ivec3 block_pos = glm::floor(m_camera_pos);
auto& world = m_player->get_world();
bool change;
if (world.get_block_tpye(block_pos) == 7) {
m_under_water = true;
change = true;
} else {
m_under_water = false;
change = false;
}
if (m_under_water != change) {
m_under_water = change;
m_player->set_underwater(m_under_water);
auto& audio = m_player->get_world().get_audio();
audio.play_3d("ambient/water/in_and_out_of_water.flac", m_camera_pos,
true);
audio.underwater_change(m_under_water);
m_player->get_world().send_player_water_sound(m_under_water,
m_camera_pos);
}
}

View File

@@ -44,6 +44,11 @@ void Config::create_config() {
[texture]
aniso = 1 # i is the minimun value, indicating off
[volume]
music = 1.0
SFX = 1.0
)"sv;
try {

View File

@@ -104,6 +104,12 @@ void DevPanel::show_about_table_bar() {
ImGui::Text("Asio");
ImGui::Text("protobuf");
ImGui::Text("zstd");
ImGui::Text("OpenAl Soft");
ImGui::Text("dr_libs");
ImGui::Separator();
ImGui::Text("Music");
ImGui::Text("'Find a Peaceful Place' by ROZKOL (Free Music Archive), "
"CC BY 4.0.");
ImGui::Separator();
ImGui::Text("Special Thanks");
ImGui::Text("TANGERIME");
@@ -430,6 +436,17 @@ void DevPanel::show_settings_tab_item() {
ImGui::SameLine();
ImGui::Text("Your need to click this button to apply config\n");
}
if (ImGui::SliderFloat("Music", &m_config.volume_music, 0.0f, 1.0f)) {
Config::get().set("volume.music",
static_cast<double>(m_config.volume_music));
m_app.audio().reload_config();
}
if (ImGui::SliderFloat("SFX", &m_config.volume_sfx, 0.0f, 1.0f)) {
Config::get().set("volume.SFX",
static_cast<double>(m_config.volume_sfx));
m_app.audio().reload_config();
}
if (ImGui::Combo("Theme", &m_theme, THEMES, IM_ARRAYSIZE(THEMES))) {
if (m_theme == 0) {
ImGui::StyleColorsDark();
@@ -746,6 +763,10 @@ void DevPanel::update_config_view() {
} else {
m_config.is_enable_aniso = true;
}
m_config.volume_music =
static_cast<float>(config.val_view("volume.music").value_or(1.0));
m_config.volume_sfx =
static_cast<float>(config.val_view("volume.SFX").value_or(1.0));
}
void DevPanel::update_player_profile() {
if (!m_player) {

View File

@@ -282,7 +282,7 @@ void ClientChunk::need_upload() { m_need_upload = true; }
void ClientChunk::set_chunk_block(int index, unsigned id) {
m_blocks[index] = id;
}
BlockType ClientChunk::get_chunk_block(int index) { return m_blocks[index]; }
ChunkPos ClientChunk::chunk_pos() const { return m_chunk_pos; }
BiomeType ClientChunk::biome() const { return m_biome; }

View File

@@ -1,9 +1,12 @@
#include "Cubed/gameplay/client_player.hpp"
#include "Cubed/audio/audio_engine.hpp"
#include "Cubed/config.hpp"
#include "Cubed/debug_collector.hpp"
#include "Cubed/gameplay/client_world.hpp"
namespace {} // namespace
namespace Cubed {
ClientPlayer::ClientPlayer(ClientWorld& world) : m_world(world) {}
ClientPlayer::~ClientPlayer() {}
@@ -144,42 +147,35 @@ void ClientPlayer::update_player_move_state(int key, int action) {
case GLFW_KEY_W:
if (action == GLFW_PRESS) {
m_move_state.forward = true;
m_moving = true;
}
if (action == GLFW_RELEASE) {
m_move_state.forward = false;
m_moving = false;
m_sprinting = false;
}
break;
case GLFW_KEY_S:
if (action == GLFW_PRESS) {
m_move_state.back = true;
m_moving = true;
}
if (action == GLFW_RELEASE) {
m_move_state.back = false;
m_moving = false;
}
break;
case GLFW_KEY_A:
if (action == GLFW_PRESS) {
m_move_state.left = true;
m_moving = true;
}
if (action == GLFW_RELEASE) {
m_move_state.left = false;
m_moving = false;
}
break;
case GLFW_KEY_D:
if (action == GLFW_PRESS) {
m_move_state.right = true;
m_moving = true;
}
if (action == GLFW_RELEASE) {
m_move_state.right = false;
m_moving = false;
}
break;
case GLFW_KEY_SPACE:
@@ -223,6 +219,8 @@ void ClientPlayer::update_player_move_state(int key, int action) {
}
break;
}
m_moving = m_move_state.forward || m_move_state.back || m_move_state.left ||
m_move_state.right;
}
void ClientPlayer::update_front_vec(float offset_x, float offset_y) {
@@ -391,6 +389,19 @@ void ClientPlayer::update_move(float delta_time) {
m_player_pos = player_pos;
}
update_player_chunk();
auto it = m_timers.find("Player Walk Sound");
if (it != m_timers.end()) {
if (m_sprinting) {
it->second.set_threshold(RUN_SOUND_INTERVAL);
} else {
it->second.set_threshold(WALK_SOUND_INTERVAL);
}
}
for (auto& [key, timer] : m_timers) {
timer.update(delta_time);
}
}
void ClientPlayer::update_x_move(glm::vec3& player_pos) {
@@ -566,7 +577,7 @@ float& ClientPlayer::fly_y_speed() { return m_fly_y_speed; }
unsigned ClientPlayer::place_block() const { return m_place_block; };
void ClientPlayer::set_gait(Gait gait) { m_gait = gait; }
GameMode& ClientPlayer::game_mode() { return m_game_mode; }
const ClientWorld& ClientPlayer::get_world() const { return m_world; }
ClientWorld& ClientPlayer::get_world() { return m_world; }
void ClientPlayer::set_uuid(std::string_view uuid) {
std::lock_guard lock(m_uuid_mutex);
@@ -578,7 +589,31 @@ std::string ClientPlayer::get_uuid() const {
return m_uuid;
}
const std::string& ClientPlayer::get_name() const { return m_name; }
void ClientPlayer::init(std::string_view name) { m_name = name; }
void ClientPlayer::init(std::string_view name) {
m_name = name;
m_timers.try_emplace("Player Walk Sound", WALK_SOUND_INTERVAL, [this]() {
if (!m_moving || is_fly) {
return;
}
glm::ivec3 block = glm::floor(m_player_pos);
block.y -= 1;
BlockType id = m_world.get_block_tpye(block);
Logger::info("player Block {} Walk Sound", id);
if (id == 0) {
return;
}
std::string name = BlockManager::name_form_id(id);
std::string sound = "block/" + name + "/walk.ogg";
auto& audio = m_world.get_audio();
audio.play_3d(sound, m_player_pos);
Logger::info("Player block {} walk sound", name);
});
}
bool ClientPlayer::is_underwater() const { return m_underwater; }
void ClientPlayer::set_underwater(bool u) { m_underwater = u; }
float ClientPlayer::yaw() const { return m_yaw; }
float ClientPlayer::pitch() const { return m_pitch; }

View File

@@ -1,6 +1,7 @@
#include "Cubed/gameplay/client_world.hpp"
#include "Cubed/config.hpp"
#include "Cubed/gameplay/chunk_generator.hpp"
#include "Cubed/gameplay/game_time.hpp"
#include "Cubed/gameplay/packet.hpp"
#include "Cubed/tools/math_tools.hpp"
@@ -20,7 +21,8 @@ struct ChunkRenderData {
};
} // namespace
ClientWorld::ClientWorld() : m_player(*this) {}
ClientWorld::ClientWorld(AudioEngine& auido)
: m_player(*this), m_audio(auido) {}
ClientWorld::~ClientWorld() {
stop_client_thread();
@@ -42,7 +44,7 @@ ClientWorld::~ClientWorld() {
}
m_pending_delete_vao.clear();
}
m_timers.clear();
m_ticktimers.clear();
}
const std::optional<LookBlock>& ClientWorld::get_look_block_pos() const {
@@ -151,24 +153,38 @@ void ClientWorld::set_block(const glm::ivec3& block_pos, unsigned id) {
auto [chunk_x, chunk_z] = get_chunk_pos(world_x, world_z);
ChunkPos pos{chunk_x, chunk_z};
BlockType origin_id = 0;
{
chunk_acc acc;
if (!m_chunks.find(acc, pos)) {
return;
}
auto [x, y, z] = ClientChunk::world_to_block(world_x, world_y, world_z,
chunk_x, chunk_z);
if (x < 0 || y < 0 || z < 0 || x >= CHUNK_SIZE || y >= WORLD_SIZE_Y ||
z >= CHUNK_SIZE) {
return;
}
acc->second->set_chunk_block(ClientChunk::index(x, y, z), id);
int idx = ClientChunk::index(x, y, z);
origin_id = acc->second->get_chunk_block(idx);
acc->second->set_chunk_block(idx, id);
acc->second->mark_dirty();
}
glm::vec3 sound_pos{world_x + 0.5f, world_y + 0.5f, world_z + 0.5f};
if (id == 0) {
std::string name = BlockManager::name_form_id(origin_id);
std::string sound = "block/" + name + "/break.ogg";
m_pending_sound.emplace(sound, sound_pos);
} else {
std::string name = BlockManager::name_form_id(id);
std::string sound = "block/" + name + "/place.ogg";
m_pending_sound.emplace(sound, sound_pos);
}
auto pool = m_thread_pool.load();
pool->enqueue(0, [this, pos]() {
@@ -338,12 +354,94 @@ void ClientWorld::receive_player_logout(const LogoutRsp& rsp) {
}
}
void ClientWorld::receive_player_water_sound(const PlayerWaterSound& rsp) {
if (rsp.uuid() == m_player.get_uuid()) {
return;
}
glm::vec3 pos = {rsp.pos().x(), rsp.pos().y(), rsp.pos().z()};
Logger::info("Client: Receive Player Water Sound");
m_pending_sound.emplace("ambient/water/in_and_out_of_water.flac", pos);
}
void ClientWorld::send_player_water_sound(bool underwater,
const glm::vec3& pos) {
Arena arena;
auto* r = Arena::Create<PlayerWaterSound>(&arena);
r->set_underwater(underwater);
auto* p = r->mutable_pos();
p->set_x(pos.x);
p->set_y(pos.y);
p->set_z(pos.z);
r->set_uuid(m_player.get_uuid());
m_client->send(make_packet(*r));
Logger::info("Client: Send Player Water Sound");
}
void ClientWorld::init(std::string_view player_name,
std::shared_ptr<NetworkClient> client) {
m_player.init(player_name);
m_client = client;
m_random.init(ChunkGenerator::seed());
// timer
register_timer("player_pos", 1, [this]() { report_player_info(); });
register_ticktimer("player_pos", 1, [this]() { report_player_info(); });
m_timers.try_emplace("Birds Sound", 60.0f, [this]() {
auto player_pos = m_player.get_player_pos();
if (player_pos.y < SEA_LEVEL) {
return;
}
ChunkPos pos = get_chunk_pos(player_pos.x, player_pos.z);
{
chunk_cacc cacc;
if (m_chunks.find(cacc, pos)) {
if (cacc->second->get_biome() == BiomeType::FOREST) {
m_audio.play_2d("ambient/birds.ogg", true);
}
}
}
});
m_timers.try_emplace("Ocean Wave", 3.0f, [this]() {
auto player_pos = m_player.get_player_pos();
if (player_pos.y < SEA_LEVEL - 10 || player_pos.y > SEA_LEVEL + 10) {
return;
}
auto ans = m_random.random_int(1, 4);
std::string sound =
"ambient/ocean/wave00" + std::to_string(ans) + ".flac";
ChunkPos pos = get_chunk_pos(player_pos.x, player_pos.z);
{
chunk_cacc cacc;
if (m_chunks.find(cacc, pos)) {
if (cacc->second->get_biome() == BiomeType::OCEAN) {
m_audio.play_2d(sound, true);
}
}
}
});
m_timers.try_emplace("under water bubble", 1.5f, [this]() {
if (m_player.is_underwater()) {
auto ans = m_random.random_int(1, 2);
std::string sound =
"ambient/water/bubble00" + std::to_string(ans) + ".ogg";
m_audio.play_3d(sound, m_player.get_player_pos(), true);
}
});
m_timers.try_emplace("bgm change", 350.0f, [this]() {
if (m_day_tick >= 17000 || m_day_tick < 5000) {
m_audio.change_bgm("bgm/bgm002.ogg");
} else {
m_audio.change_bgm("bgm/bgm001.mp3");
}
});
LoginReq req;
req.set_name(m_player.get_name());
while (!client->is_connected()) {
@@ -356,6 +454,7 @@ void ClientWorld::init(std::string_view player_name,
// request login
Logger::info("Send Login Request");
m_client->send(make_packet(req), 0);
m_audio.play_bgm();
}
void ClientWorld::start_client_thread(std::string_view uuid) {
@@ -427,7 +526,7 @@ void ClientWorld::client_run(std::stop_token stoken) {
auto next = Clock::now();
while (!stoken.stop_requested()) {
next += TICK;
for (auto& x : m_timers) {
for (auto& x : m_ticktimers) {
x.second.update();
}
std::this_thread::sleep_until(next);
@@ -605,6 +704,8 @@ AABB ClientWorld::get_block_aabb(const glm::ivec3& pos) {
static_cast<float>(z + 1)}};
}
AudioEngine& ClientWorld::get_audio() { return m_audio; }
void ClientWorld::request_exit() {
if (m_receive_exit) {
return;
@@ -728,6 +829,36 @@ void ClientWorld::update(float delta_time) {
player.angle = glm::mix(player.angle, 0.0f, t);
}
// walking sound
if (player.gait == Gait::STOP) {
player.moving_time = 0.0f;
} else {
player.moving_time += delta_time;
}
auto play_walk_sound = [&]() {
glm::ivec3 block = glm::floor(player.render_pos);
block.y -= 1;
BlockType id = get_block_tpye(block);
if (id == 0) {
return;
}
std::string name = BlockManager::name_form_id(id);
std::string sound = "block/" + name + "/walk.ogg";
m_audio.play_3d(sound, player.render_pos);
};
if (player.gait == Gait::WALK) {
if (player.moving_time >= ClientPlayer::WALK_SOUND_INTERVAL) {
player.moving_time = 0.0f;
play_walk_sound();
}
}
if (player.gait == Gait::RUN) {
if (player.moving_time >= ClientPlayer::RUN_SOUND_INTERVAL) {
player.moving_time = 0.0f;
play_walk_sound();
}
}
m_render_player_data.emplace_back(
player.name, player.uuid, player.render_pos, player.render_yaw,
player.render_pitch, player.gait, player.angle);
@@ -755,6 +886,16 @@ void ClientWorld::update(float delta_time) {
m_player.get_gait(), m_player.angle());
}
}
// sound
PendingSound pending_sound;
while (m_pending_sound.try_pop(pending_sound)) {
m_audio.play_3d(pending_sound.sound, pending_sound.sound_pos);
}
for (auto& [pos, timer] : m_timers) {
timer.update(delta_time);
}
}
glm::vec3 ClientWorld::sunlight_dir() const {

View File

@@ -119,6 +119,12 @@ asio::awaitable<void> NetworkClient::read_loop() {
}
}
} break;
case std::to_underlying(PacketEnum::PLAYER_WATER_SOUND): {
auto* rsp = Arena::Create<PlayerWaterSound>(&arena);
if (decode_packet(*rsp, body_data, header)) {
m_world.receive_player_water_sound(*rsp);
}
} break;
}
}
} catch (const asio::system_error& e) {

View File

@@ -597,6 +597,47 @@ void ServerWorld::sync_player_pos(const C2S_PlayerInfo& prsp) {
}
}
void ServerWorld::sync_player_water_sound(const PlayerWaterSound& rsp) {
auto x = rsp.pos().x();
auto y = rsp.pos().y();
auto z = rsp.pos().z();
ChunkPos pos = get_chunk_pos(x, z);
auto uuid = rsp.uuid();
auto underwater = rsp.underwater();
std::vector<std::shared_ptr<Session>> other;
{
std::shared_lock lock(m_player_mutex);
for (auto& [o_uuid, player] : m_players) {
if (o_uuid == uuid) {
continue;
}
if (player.has_player(pos)) {
other.emplace_back(player.get_session());
}
}
}
Arena arena;
auto* r = Arena::Create<PlayerWaterSound>(&arena);
r->set_uuid(uuid);
r->set_underwater(underwater);
auto* p = r->mutable_pos();
p->set_x(x);
p->set_y(y);
p->set_z(z);
for (auto& session : other) {
if (!session) {
continue;
}
session->send(make_packet(*r), 5);
}
}
void ServerWorld::handle_player_login(const std::string& name,
std::shared_ptr<Session> session) {
std::string uuid = generate_uuid();

View File

@@ -92,6 +92,12 @@ asio::awaitable<void> Session::read_loop() {
m_server_world.handle_player_exit(req->uuid());
}
}
if (cmd_id == std::to_underlying(PacketEnum::PLAYER_WATER_SOUND)) {
auto* req = Arena::Create<PlayerWaterSound>(&arena);
if (decode_packet(*req, body_data, header)) {
m_server_world.sync_player_water_sound(*req);
}
}
}
} catch (const asio::system_error& e) {
auto ec = e.code();

View File

@@ -17,4 +17,10 @@ message PlayerInfoRsp {
float yaw = 4;
float pitch = 5;
int32 gait = 6;
}
message PlayerWaterSound {
string uuid = 1;
Vec3 pos = 2;
bool underwater = 3;
}

View File

@@ -57,7 +57,7 @@ void Renderer::hot_reload() {
update_fov(config.get<double>("player.fov"));
}
void Renderer::init() {
void Renderer::init(bool debug_on) {
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
Logger::error("Failed to initialize glad");
exit(EXIT_FAILURE);
@@ -118,14 +118,18 @@ void Renderer::init() {
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
#ifdef DEBUG_MODE
glEnable(GL_DEBUG_OUTPUT);
glDebugMessageCallback(
[](GLenum, GLenum, GLuint, GLenum, GLsizei, const GLchar* message,
const void*) {
Logger::log(Logger::Level::L_DEBUG, std::source_location::current(),
"GL Debug: {}", reinterpret_cast<const char*>(message));
},
nullptr);
if (debug_on) {
glEnable(GL_DEBUG_OUTPUT);
glDebugMessageCallback(
[](GLenum, GLenum, GLuint, GLenum, GLsizei, const GLchar* message,
const void*) {
Logger::log(Logger::Level::L_DEBUG,
std::source_location::current(), "GL Debug: {}",
reinterpret_cast<const char*>(message));
},
nullptr);
}
#endif
m_vao.resize(NUM_VAO);