CXX = g++ --std=c++11
LDXX = g++
-LIBS = sdl2 SDL2_image SDL2_ttf glew
+LIBS = sdl2 SDL2_image SDL2_ttf glew openal freealut
PKGFLAGS := $(shell pkg-config --cflags $(LIBS))
PKGLIBS := $(shell pkg-config --libs $(LIBS))
-Subproject commit 7ac546c3310a3f147a889077dffa6919fad94be7
+Subproject commit 96db33a3047bf3f20f5b6d4464cf4a4ee238146d
Dependencies
============
- GLEW, GLM, SDL2, SDL2_image, SDL2_ttf
+ GLEW, GLM, SDL2, SDL2_image, SDL2_ttf, OpenAL, freealut
CppUnit for tests
-archlinux: pacman -S glew glm sdl2 sdl2_image sdl2_ttf cppunit
+archlinux: pacman -S glew glm sdl2 sdl2_image sdl2_ttf openal freealut cppunit
manual:
CppUnit http://sourceforge.net/projects/cppunit/
--no-hud
disable HUD drawing (includes the selected block outline)
+--no-audio
+ disable audio
+ the audio device and sounds will still be allocated
+ it just stops the interface from queueing buffers
+
World
-----
Press N to toggle player/world collision.
+F1 toggles UI rendering.
F3 toggles a display telling how long on average it takes to compute a frame.
+F4 toggles audio.
#include "FrameCounter.hpp"
#include "init.hpp"
#include "RandomWalk.hpp"
+#include "../audio/Audio.hpp"
#include "../graphics/Viewport.hpp"
#include "../ui/Interface.hpp"
#include "../world/World.hpp"
};
explicit Application(const Config &);
+ ~Application();
Application(const Application &) = delete;
Application &operator =(const Application &) = delete;
Init init;
Viewport viewport;
Assets assets;
+ Audio audio;
FrameCounter counter;
World world;
namespace blank {
class Font;
+class Sound;
class Assets {
explicit Assets(const std::string &base);
Font LoadFont(const std::string &name, int size) const;
+ Sound LoadSound(const std::string &name) const;
private:
std::string fonts;
+ std::string sounds;
};
config.interface.mouse_disabled = true;
} else if (strcmp(arg + 2, "no-hud") == 0) {
config.interface.visual_disabled = true;
+ } else if (strcmp(arg + 2, "no-audio") == 0) {
+ config.interface.audio_disabled = true;
} else {
cerr << "unknown option " << arg << endl;
error = true;
#include "Assets.hpp"
#include "FrameCounter.hpp"
+#include "../audio/Sound.hpp"
#include "../graphics/Font.hpp"
#include "../world/BlockType.hpp"
#include "../world/Entity.hpp"
: init(config.doublebuf, config.multisampling)
, viewport()
, assets(get_asset_path())
+, audio()
, counter()
, world(config.world)
-, interface(config.interface, assets, counter, world)
+, interface(config.interface, assets, audio, counter, world)
, test_controller(MakeTestEntity(world))
, running(false) {
viewport.VSync(config.vsync);
}
+Application::~Application() {
+ audio.StopAll();
+}
+
Entity &Application::MakeTestEntity(World &world) {
Entity &e = world.AddEntity();
e.Name("test");
interface.Update(dt);
test_controller.Update(dt);
world.Update(dt);
+
+ glm::mat4 trans = world.Player().Transform(Chunk::Pos(0, 0, 0));
+ glm::vec3 dir(trans * glm::vec4(0.0f, 0.0f, -1.0f, 0.0f));
+ glm::vec3 up(trans * glm::vec4(0.0f, 1.0f, 0.0f, 0.0f));
+ audio.Position(world.Player().Position());
+ audio.Velocity(world.Player().Velocity());
+ audio.Orientation(dir, up);
+
counter.ExitUpdate();
}
Assets::Assets(const string &base)
-: fonts(base + "fonts/") {
+: fonts(base + "fonts/")
+, sounds(base + "sounds/") {
}
return Font(full.c_str(), size);
}
+Sound Assets::LoadSound(const string &name) const {
+ string full = sounds + name + ".wav";
+ return Sound(full.c_str());
+}
+
void FrameCounter::EnterFrame() noexcept {
last_enter = SDL_GetTicks();
#include "init.hpp"
#include <algorithm>
+#include <alut.h>
#include <SDL.h>
#include <SDL_image.h>
#include <SDL_ttf.h>
return msg;
}
+std::string alut_error_append(ALenum num, std::string msg) {
+ const char *error = alutGetErrorString(num);
+ if (*error != '\0') {
+ msg += ": ";
+ msg += error;
+ }
+ return msg;
+}
+
}
namespace blank {
+AlutError::AlutError(ALenum num)
+: std::runtime_error(alutGetErrorString(num)) {
+
+}
+
+AlutError::AlutError(ALenum num, const std::string &msg)
+: std::runtime_error(alut_error_append(num, msg)) {
+
+}
+
+
SDLError::SDLError()
: std::runtime_error(SDL_GetError()) {
}
+InitAL::InitAL() {
+ if (!alutInit(nullptr, nullptr)) {
+ throw AlutError(alutGetError(), "alutInit");
+ }
+}
+
+InitAL::~InitAL() {
+ if (!alutExit()) {
+ throw AlutError(alutGetError(), "alutExit");
+ }
+}
+
+
InitGL::InitGL(bool double_buffer, int sample_size) {
if (SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3) != 0) {
throw SDLError("SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3)");
#ifndef BLANK_APP_INIT_HPP_
#define BLANK_APP_INIT_HPP_
+#include <al.h>
#include <SDL.h>
#include <stdexcept>
#include <string>
namespace blank {
+class AlutError
+: public std::runtime_error {
+
+public:
+ explicit AlutError(ALenum);
+ AlutError(ALenum, const std::string &);
+
+};
+
class SDLError
: public std::runtime_error {
};
+class InitAL {
+
+public:
+ InitAL();
+ ~InitAL();
+
+ InitAL(const InitAL &) = delete;
+ InitAL &operator =(const InitAL &) = delete;
+
+};
+
+
class InitGL {
public:
InitSDL init_sdl;
InitIMG init_img;
InitTTF init_ttf;
+ InitAL init_al;
InitGL init_gl;
Window window;
GLContext ctx;
--- /dev/null
+#ifndef BLANK_AUDIO_ALERROR_HPP_
+#define BLANK_AUDIO_ALERROR_HPP_
+
+#include <al.h>
+#include <stdexcept>
+#include <string>
+
+
+namespace blank {
+
+class ALError
+: public std::runtime_error {
+
+public:
+ explicit ALError(ALenum);
+ ALError(ALenum, const std::string &);
+
+};
+
+}
+
+#endif
--- /dev/null
+#ifndef BLANK_AUDIO_AUDIO_HPP_
+#define BLANK_AUDIO_AUDIO_HPP_
+
+#include <al.h>
+#include <glm/glm.hpp>
+
+
+namespace blank {
+
+class Sound;
+
+class Audio {
+
+public:
+ Audio();
+ ~Audio();
+
+ Audio(const Audio &) = delete;
+ Audio &operator =(const Audio &) = delete;
+
+public:
+ void Position(const glm::vec3 &) noexcept;
+ void Velocity(const glm::vec3 &) noexcept;
+ void Orientation(const glm::vec3 &dir, const glm::vec3 &up) noexcept;
+
+ void Play(
+ const Sound &,
+ const glm::vec3 &pos = glm::vec3(0.0f),
+ const glm::vec3 &vel = glm::vec3(0.0f),
+ const glm::vec3 &dir = glm::vec3(0.0f)
+ ) noexcept;
+
+ void StopAll() noexcept;
+
+private:
+ static constexpr std::size_t NUM_SRC = 1;
+ ALuint source[NUM_SRC];
+
+};
+
+}
+
+#endif
--- /dev/null
+#ifndef BLANK_AUDIO_SOUND_HPP_
+#define BLANK_AUDIO_SOUND_HPP_
+
+#include <al.h>
+
+
+namespace blank {
+
+class Sound {
+
+public:
+ Sound();
+ explicit Sound(const char *);
+ ~Sound();
+
+ Sound(Sound &&);
+ Sound &operator =(Sound &&);
+
+ Sound(const Sound &) = delete;
+ Sound &operator =(const Sound &) = delete;
+
+public:
+ void Bind(ALuint src) const;
+
+private:
+ ALuint handle;
+
+};
+
+}
+
+#endif
--- /dev/null
+#include "ALError.hpp"
+#include "Audio.hpp"
+#include "Sound.hpp"
+
+#include <algorithm>
+#include <alut.h>
+#include <iostream>
+#include <glm/gtc/type_ptr.hpp>
+#include <glm/gtx/io.hpp>
+
+
+namespace {
+
+const char *al_error_string(ALenum num) {
+ switch (num) {
+ case AL_NO_ERROR:
+ return "no error";
+ case AL_INVALID_NAME:
+ return "invalid name";
+ case AL_INVALID_ENUM:
+ return "invalid enum";
+ case AL_INVALID_VALUE:
+ return "invalid value";
+ case AL_INVALID_OPERATION:
+ return "invalid operation";
+ case AL_OUT_OF_MEMORY:
+ return "out of memory";
+ }
+ return "unknown AL error";
+}
+
+std::string al_error_append(ALenum num, std::string msg) {
+ return msg + ": " + al_error_string(num);
+}
+
+}
+
+namespace blank {
+
+ALError::ALError(ALenum num)
+: std::runtime_error(al_error_string(num)) {
+
+}
+
+ALError::ALError(ALenum num, const std::string &msg)
+: std::runtime_error(al_error_append(num, msg)) {
+
+}
+
+
+Audio::Audio() {
+ alGenSources(NUM_SRC, source);
+ ALenum err = alGetError();
+ if (err != AL_NO_ERROR) {
+ throw ALError(err, "alGenSources");
+ }
+ for (std::size_t i = 0; i < NUM_SRC; ++i) {
+ alSourcef(source[i], AL_REFERENCE_DISTANCE, 2.0f);
+ alSourcef(source[i], AL_ROLLOFF_FACTOR, 1.0f);
+ }
+}
+
+Audio::~Audio() {
+ alDeleteSources(NUM_SRC, source);
+ ALenum err = alGetError();
+ if (err != AL_NO_ERROR) {
+ std::cerr << "warning: alDeleteSources failed with " << al_error_string(err) << std::endl;
+ //throw ALError(err, "alDeleteSources");
+ }
+}
+
+void Audio::Position(const glm::vec3 &pos) noexcept {
+ alListenerfv(AL_POSITION, glm::value_ptr(pos));
+ //std::cout << "listener at " << pos << std::endl;
+}
+
+void Audio::Velocity(const glm::vec3 &vel) noexcept {
+ alListenerfv(AL_VELOCITY, glm::value_ptr(vel));
+}
+
+void Audio::Orientation(const glm::vec3 &dir, const glm::vec3 &up) noexcept {
+ ALfloat orient[6] = {
+ dir.x, dir.y, dir.z,
+ up.x, up.y, up.z,
+ };
+ alListenerfv(AL_ORIENTATION, orient);
+}
+
+void Audio::Play(
+ const Sound &sound,
+ const glm::vec3 &pos,
+ const glm::vec3 &vel,
+ const glm::vec3 &dir
+) noexcept {
+ // TODO: find next free source
+ ALuint src = source[0];
+
+ sound.Bind(src);
+ alSourcefv(src, AL_POSITION, glm::value_ptr(pos));
+ alSourcefv(src, AL_VELOCITY, glm::value_ptr(vel));
+ alSourcefv(src, AL_DIRECTION, glm::value_ptr(dir));
+ alSourcePlay(src);
+}
+
+void Audio::StopAll() noexcept {
+ alSourceStopv(NUM_SRC, source);
+ for (std::size_t i = 0; i < NUM_SRC; ++i) {
+ alSourcei(source[i], AL_BUFFER, AL_NONE);
+ }
+}
+
+
+Sound::Sound()
+: handle(AL_NONE) {
+ alGenBuffers(1, &handle);
+ ALenum err = alGetError();
+ if (err != AL_NO_ERROR) {
+ throw ALError(err, "alGenBuffers");
+ }
+}
+
+Sound::Sound(const char *file)
+: handle(alutCreateBufferFromFile(file)) {
+ if (handle == AL_NONE) {
+ throw ALError(alGetError(), "alutCreateBufferFromFile");
+ }
+}
+
+Sound::~Sound() {
+ if (handle != AL_NONE) {
+ alDeleteBuffers(1, &handle);
+ ALenum err = alGetError();
+ if (err != AL_NO_ERROR) {
+ std::cerr << "warning: alDeleteBuffers failed with " << al_error_string(err) << std::endl;
+ //throw ALError(err, "alDeleteBuffers");
+ }
+ }
+}
+
+Sound::Sound(Sound &&other)
+: handle(other.handle) {
+ other.handle = AL_NONE;
+}
+
+Sound &Sound::operator =(Sound &&other) {
+ std::swap(handle, other.handle);
+ return *this;
+}
+
+void Sound::Bind(ALuint src) const {
+ alSourcei(src, AL_BUFFER, handle);
+}
+
+}
#include "HUD.hpp"
#include "../app/FPSController.hpp"
#include "../app/IntervalTimer.hpp"
+#include "../audio/Sound.hpp"
#include "../graphics/FixedText.hpp"
#include "../graphics/Font.hpp"
#include "../graphics/MessageBox.hpp"
namespace blank {
class Assets;
+class Audio;
class Chunk;
class FrameCounter;
class Viewport;
bool keyboard_disabled = false;
bool mouse_disabled = false;
+ bool audio_disabled = false;
bool visual_disabled = false;
};
- Interface(const Config &, const Assets &, const FrameCounter &, World &);
+ Interface(const Config &, const Assets &, Audio &, const FrameCounter &, World &);
void HandlePress(const SDL_KeyboardEvent &);
void HandleRelease(const SDL_KeyboardEvent &);
void SelectNext();
void SelectPrevious();
+ void ToggleAudio();
+ void ToggleVisual();
+
void ToggleCounter();
void UpdateCounter();
void CheckAim();
private:
+ Audio &audio;
const FrameCounter &counter;
World &world;
FPSController ctrl;
Block remove;
Block selection;
+ Sound place_sound;
+ Sound remove_sound;
+
glm::tvec3<int> fwd, rev;
};
#include "../app/Assets.hpp"
#include "../app/FrameCounter.hpp"
#include "../app/init.hpp"
+#include "../audio/Audio.hpp"
#include "../graphics/Font.hpp"
#include "../graphics/Viewport.hpp"
#include "../model/shapes.hpp"
Interface::Interface(
const Config &config,
const Assets &assets,
+ Audio &audio,
const FrameCounter &counter,
World &world)
-: counter(counter)
+: audio(audio)
+, counter(counter)
, world(world)
, ctrl(world.Player())
, font(assets.LoadFont("DejaVuSans", 16))
, remove_timer(256)
, remove(0)
, selection(1)
+, place_sound(assets.LoadSound("thump"))
+, remove_sound(assets.LoadSound("plop"))
, fwd(0)
, rev(0) {
counter_text.Hide();
PrintSelectionInfo();
break;
+ case SDLK_F1:
+ ToggleVisual();
+ break;
case SDLK_F3:
ToggleCounter();
break;
+ case SDLK_F4:
+ ToggleAudio();
+ break;
}
}
PostMessage(s.str());
}
+void Interface::ToggleAudio() {
+ config.audio_disabled = !config.audio_disabled;
+ if (config.audio_disabled) {
+ PostMessage("audio off");
+ } else {
+ PostMessage("audio on");
+ }
+}
+
+void Interface::ToggleVisual() {
+ config.visual_disabled = !config.visual_disabled;
+ if (config.visual_disabled) {
+ PostMessage("visual off");
+ } else {
+ PostMessage("visual on");
+ }
+}
+
void Interface::ToggleCounter() {
counter_text.Toggle();
if (counter_text.Visible()) {
}
mod_chunk->SetBlock(next_pos, selection);
mod_chunk->Invalidate();
+
+ if (config.audio_disabled) return;
+ const Entity &player = ctrl.Controlled();
+ audio.Play(
+ place_sound,
+ mod_chunk->ToSceneCoords(player.ChunkCoords(), next_pos)
+ );
}
void Interface::RemoveBlock() noexcept {
if (!aim_chunk) return;
aim_chunk->SetBlock(aim_block, remove);
aim_chunk->Invalidate();
+
+ if (config.audio_disabled) return;
+ const Entity &player = ctrl.Controlled();
+ audio.Play(
+ remove_sound,
+ aim_chunk->ToSceneCoords(player.ChunkCoords(), Chunk::ToCoords(aim_block))
+ );
}
}
glm::mat4 ToTransform(const Pos &pos, int idx) const noexcept;
+ Block::Pos ToSceneCoords(const Pos &base, const Block::Pos &pos) const noexcept {
+ return Block::Pos((position - base) * Extent()) + pos;
+ }
+
static bool IsBorder(const Pos &pos) noexcept {
return
pos.x == 0 ||