mirror of
https://github.com/MihailRis/voxelcore.git
synced 2026-10-04 18:41:51 +00:00
Merge branch 'main' into volume-renderer
This commit is contained in:
commit
c7508d6270
35 changed files with 532 additions and 68 deletions
8
.github/workflows/macos.yml
vendored
8
.github/workflows/macos.yml
vendored
|
|
@ -26,10 +26,14 @@ jobs:
|
|||
brew install glfw3 glew libpng openal-soft luajit libvorbis skypjack/entt/entt googletest glm
|
||||
|
||||
- name: Configure
|
||||
run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DVOXELENGINE_BUILD_TESTS=ON -DVOXELENGINE_BUILD_APPDIR=1 -DVC_BUILD_NAME="${{ inputs.build_name }}"
|
||||
run: |
|
||||
cmake -DOPENAL_INCLUDE_DIR=$(brew --prefix openal-soft)/include \
|
||||
-DOPENAL_LIBRARY=$(brew --prefix openal-soft)/lib/libopenal.dylib \
|
||||
-S . -B build -DCMAKE_BUILD_TYPE=Release -DVOXELENGINE_BUILD_TESTS=ON -DVOXELENGINE_BUILD_APPDIR=1 -DVC_BUILD_NAME="${{ inputs.build_name }}"
|
||||
|
||||
- name: Build
|
||||
run: cmake --build build -t install
|
||||
run: |
|
||||
cmake --build build -t install
|
||||
|
||||
- name: Make fix_dylibs.sh executable
|
||||
run: chmod +x dev/fix_dylibs.sh
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"steps-sound": "steps/carpet",
|
||||
"place-sound": "blocks/carpet_place",
|
||||
"break-sound": "blocks/carpet_break"
|
||||
"break-sound": "blocks/carpet_break",
|
||||
"sound-absorption": 0.96
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"steps-sound": "steps/glass",
|
||||
"place-sound": "blocks/glass_place",
|
||||
"break-sound": "blocks/glass_break"
|
||||
"break-sound": "blocks/glass_break",
|
||||
"sound-absorption": 0.3
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"steps-sound": "steps/grass",
|
||||
"place-sound": "steps/grass",
|
||||
"break-sound": "steps/grass"
|
||||
"break-sound": "steps/grass",
|
||||
"sound-absorption": 0.85
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,5 +2,6 @@
|
|||
"steps-sound": "steps/grass",
|
||||
"place-sound": "blocks/ground_place",
|
||||
"break-sound": "blocks/ground_break",
|
||||
"hit-sound": "blocks/ground_hit"
|
||||
"hit-sound": "blocks/ground_hit",
|
||||
"sound-absorption": 0.8
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,5 +2,6 @@
|
|||
"steps-sound": "steps/ground",
|
||||
"place-sound": "blocks/ground_place",
|
||||
"break-sound": "blocks/ground_break",
|
||||
"hit-sound": "blocks/ground_hit"
|
||||
"hit-sound": "blocks/ground_hit",
|
||||
"sound-absorption": 0.7
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"steps-sound": "steps/metal",
|
||||
"place-sound": "blocks/metal_place",
|
||||
"break-sound": "blocks/metal_break"
|
||||
"break-sound": "blocks/metal_break",
|
||||
"sound-absorption": 0.1
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"steps-sound": "steps/sand",
|
||||
"place-sound": "blocks/ground_place",
|
||||
"break-sound": "blocks/ground_break"
|
||||
"break-sound": "blocks/ground_break",
|
||||
"sound-absorption": 0.8
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,5 +2,6 @@
|
|||
"steps-sound": "steps/stone",
|
||||
"place-sound": "blocks/stone_place",
|
||||
"break-sound": "blocks/stone_break",
|
||||
"hit-sound": "blocks/stone_hit"
|
||||
"hit-sound": "blocks/stone_hit",
|
||||
"sound-absorption": 0.3
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,5 +2,6 @@
|
|||
"steps-sound": "steps/wood",
|
||||
"place-sound": "blocks/wood_place",
|
||||
"break-sound": "blocks/wood_break",
|
||||
"hit-sound": "blocks/wood_hit"
|
||||
"hit-sound": "blocks/wood_hit",
|
||||
"sound-absorption": 0.5
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,14 @@ function update_setting(x, id, name, postfix)
|
|||
)
|
||||
end
|
||||
|
||||
function create_checkbox(id, name, tooltip)
|
||||
tooltip = tooltip or ''
|
||||
document.root:add(string.format(
|
||||
"<checkbox consumer='function(x) app.set_setting(\"%s\", x) end' checked='%s' tooltip='%s'>%s</checkbox>",
|
||||
id, app.str_setting(id), gui.str(tooltip, "settings"), gui.str(name, "settings")
|
||||
))
|
||||
end
|
||||
|
||||
local initialized = false
|
||||
|
||||
function on_open()
|
||||
|
|
@ -71,4 +79,6 @@ function on_open()
|
|||
if info then
|
||||
selectbox.value = info.device_specifier
|
||||
end
|
||||
|
||||
create_checkbox("audio.acoustic-effects", "Acoustic effects", "audio.acoustic-effects.tooltip")
|
||||
end
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ console.add_command(
|
|||
"clear",
|
||||
"Clears the console",
|
||||
function()
|
||||
if not hud then error("command is unavailable in headless mode") return end
|
||||
local document = Document.new("core:console")
|
||||
document.log.text = ""
|
||||
end
|
||||
|
|
|
|||
|
|
@ -17,10 +17,12 @@ devtools.traceback=Traceback (most recent call first)
|
|||
devtools.output=Output
|
||||
|
||||
# Tooltips
|
||||
audio.acoustic-effects.tooltip=Acoustic effects may not be supported by your system
|
||||
graphics.gamma.tooltip=Lighting brightness curve
|
||||
graphics.backlight.tooltip=Backlight to prevent total darkness
|
||||
graphics.dense-render.tooltip=Enables transparency in blocks like leaves
|
||||
graphics.soft-lighting.tooltip=Enables blocks soft lighting
|
||||
graphics.advanced-render.tooltip=Use graphics pipeline supporting advanced effects like shadows, SSAO
|
||||
|
||||
# settings
|
||||
settings.Controls Search Mode=Search by attached button name
|
||||
|
|
|
|||
|
|
@ -38,10 +38,12 @@ error.dependency-version-not-met=Версия зависимости не соо
|
|||
pack.remove-confirm=Удалить весь поставляемый паком/паками контент из мира (безвозвратно)?
|
||||
|
||||
# Подсказки
|
||||
audio.acoustic-effects.tooltip=Акустические эффекты могут не поддерживаться системой
|
||||
graphics.gamma.tooltip=Кривая яркости освещения
|
||||
graphics.backlight.tooltip=Подсветка, предотвращающая полную темноту
|
||||
graphics.dense-render.tooltip=Включает прозрачность блоков, таких как листья
|
||||
graphics.soft-lighting.tooltip=Включает мягкое освещение у блоков
|
||||
graphics.advanced-render.tooltip=Использовать графический конвейер, поддерживающий продвинутые эффекты, такие как тени и SSAO
|
||||
|
||||
# Меню
|
||||
menu.Apply=Применить
|
||||
|
|
@ -111,6 +113,7 @@ settings.Borderless=Безрамочный
|
|||
settings.Microphone=Микрофон
|
||||
settings.microphone.None=Нет
|
||||
settings.Clouds quality=Качество облаков
|
||||
settings.Acoustic effects=Акустические эффекты
|
||||
|
||||
# Управление
|
||||
chunks.reload=Перезагрузить Чанки
|
||||
|
|
|
|||
|
|
@ -40,6 +40,22 @@ static bool check_alc_errors(ALCdevice* device, const char* context) {
|
|||
return true;
|
||||
}
|
||||
|
||||
static LPALAUXILIARYEFFECTSLOTI alAuxiliaryEffectSloti = nullptr;
|
||||
static LPALGENAUXILIARYEFFECTSLOTS alGenAuxiliaryEffectSlots = nullptr;
|
||||
static LPALGENEFFECTS alGenEffects = nullptr;
|
||||
static LPALDELETEEFFECTS alDeleteEffects = nullptr;
|
||||
static LPALISEFFECT alIsEffect = nullptr;
|
||||
static LPALEFFECTI alEffecti = nullptr;
|
||||
static LPALEFFECTF alEffectf = nullptr;
|
||||
static LPALGENFILTERS alGenFilters = nullptr;
|
||||
static LPALDELETEFILTERS alDeleteFilters = nullptr;
|
||||
static LPALISFILTER alIsFilter = nullptr;
|
||||
static LPALFILTERI alFilteri = nullptr;
|
||||
static LPALFILTERF alFilterf = nullptr;
|
||||
|
||||
static inline constexpr int REVERB_EFFECT = 0;
|
||||
static inline constexpr int LOWPASS_FILTER = 0;
|
||||
|
||||
ALSound::ALSound(
|
||||
ALAudio* al, uint buffer, const std::shared_ptr<PCM>& pcm, bool keepPCM
|
||||
)
|
||||
|
|
@ -376,12 +392,19 @@ void ALSpeaker::setLoop(bool loop) {
|
|||
void ALSpeaker::play() {
|
||||
paused = false;
|
||||
manuallyStopped = false;
|
||||
auto p_channel = get_channel(this->channel);
|
||||
auto channel = get_channel(this->channel);
|
||||
AL_CHECK(alSourcef(
|
||||
source,
|
||||
AL_GAIN,
|
||||
volume * p_channel->getVolume()
|
||||
volume * channel->getVolume()
|
||||
));
|
||||
if (al->useEffects && channel->isEffectsApplied()) {
|
||||
AL_CHECK(alSource3i(source, AL_AUXILIARY_SEND_FILTER, al->effectSlots[0], 0, al->filters[0]));
|
||||
//AL_CHECK(alSourcei(source, AL_DIRECT_FILTER, al->filters[LOWPASS_FILTER])); // TODO: use lowpass filter
|
||||
} else {
|
||||
AL_CHECK(alSource3i(source, AL_AUXILIARY_SEND_FILTER, 0, 0, 0));
|
||||
AL_CHECK(alSourcei(source, AL_DIRECT_FILTER, 0));
|
||||
}
|
||||
AL_CHECK(alSourcePlay(source));
|
||||
}
|
||||
|
||||
|
|
@ -455,15 +478,22 @@ int ALSpeaker::getPriority() const {
|
|||
return priority;
|
||||
}
|
||||
|
||||
|
||||
bool ALSpeaker::isManuallyStopped() const {
|
||||
return manuallyStopped;
|
||||
}
|
||||
|
||||
static bool alc_enumeration_ext = false;
|
||||
|
||||
ALAudio::ALAudio(ALCdevice* device, ALCcontext* context)
|
||||
: device(device), context(context) {
|
||||
ALAudio::ALAudio(
|
||||
ALCdevice* device,
|
||||
ALCcontext* context,
|
||||
bool useEffects,
|
||||
const AudioSettings& settings
|
||||
)
|
||||
: device(device),
|
||||
context(context),
|
||||
settings(settings),
|
||||
useEffects(useEffects) {
|
||||
ALCint size;
|
||||
alcGetIntegerv(device, ALC_ATTRIBUTES_SIZE, 1, &size);
|
||||
std::vector<ALCint> attrs(size);
|
||||
|
|
@ -487,6 +517,11 @@ ALAudio::ALAudio(ALCdevice* device, ALCcontext* context)
|
|||
}
|
||||
|
||||
alDopplerFactor(1.0 / 3.0);
|
||||
alGetError();
|
||||
|
||||
if (useEffects) {
|
||||
this->useEffects = initEffects();
|
||||
}
|
||||
}
|
||||
|
||||
ALAudio::~ALAudio() {
|
||||
|
|
@ -513,6 +548,80 @@ ALAudio::~ALAudio() {
|
|||
context = nullptr;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static bool get_proc_address(const char* name, T& ptr) {
|
||||
ptr = (T) alGetProcAddress(name);
|
||||
return ptr != nullptr;
|
||||
}
|
||||
|
||||
static bool request_efx_ext_functions() {
|
||||
return get_proc_address("alAuxiliaryEffectSloti", alAuxiliaryEffectSloti)
|
||||
&& get_proc_address("alGenAuxiliaryEffectSlots", alGenAuxiliaryEffectSlots)
|
||||
&& get_proc_address("alGenEffects", alGenEffects)
|
||||
&& get_proc_address("alDeleteEffects", alDeleteEffects)
|
||||
&& get_proc_address("alIsEffect", alIsEffect)
|
||||
&& get_proc_address("alEffecti", alEffecti)
|
||||
&& get_proc_address("alEffectf", alEffectf)
|
||||
&& get_proc_address("alGenFilters", alGenFilters)
|
||||
&& get_proc_address("alDeleteFilters", alDeleteFilters)
|
||||
&& get_proc_address("alIsFilter", alIsFilter)
|
||||
&& get_proc_address("alFilteri", alFilteri)
|
||||
&& get_proc_address("alFilterf", alFilterf);
|
||||
}
|
||||
|
||||
bool ALAudio::initEffects() {
|
||||
if (!request_efx_ext_functions()) {
|
||||
logger.error() << "could not get effects extension function pointers";
|
||||
return false;
|
||||
}
|
||||
for (uint i = 0; i < maxEffectSlots; i++) {
|
||||
effectSlots.emplace_back();
|
||||
alGenAuxiliaryEffectSlots(1, &effectSlots[i]);
|
||||
if (alGetError() != AL_NO_ERROR) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
logger.info() << "created " << effectSlots.size() << " effect slots";
|
||||
|
||||
for (uint i = 0; i < 4; i++) {
|
||||
effects.emplace_back();
|
||||
alGenEffects(1, &effects[i]);
|
||||
if (alGetError() != AL_NO_ERROR || !alIsEffect(effects[i])) {
|
||||
logger.error() << "could not to create effect #" << i;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
logger.info() << "created " << effects.size() << " effects";
|
||||
|
||||
for (uint i = 0; i < filters.size(); i++) {
|
||||
alGenFilters(1, &filters[i]);
|
||||
if (alGetError() != AL_NO_ERROR || !alIsFilter(filters[i])) {
|
||||
logger.error() << "could not to create filter #" << i;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
alEffecti(effects[REVERB_EFFECT], AL_EFFECT_TYPE, AL_EFFECT_REVERB);
|
||||
if (alGetError() != AL_NO_ERROR) {
|
||||
logger.error() << "reverb effect is not supported";
|
||||
return false;
|
||||
}
|
||||
|
||||
alFilteri(filters[LOWPASS_FILTER], AL_FILTER_TYPE, AL_FILTER_LOWPASS);
|
||||
if (alGetError() != AL_NO_ERROR) {
|
||||
logger.error() << "lowpass filter is not supported";
|
||||
return false;
|
||||
}
|
||||
alFilterf(filters[LOWPASS_FILTER], AL_LOWPASS_GAIN, 1.0f);
|
||||
alFilterf(filters[LOWPASS_FILTER], AL_LOWPASS_GAINHF, 0.01f);
|
||||
|
||||
alAuxiliaryEffectSloti(effectSlots[0], AL_EFFECTSLOT_EFFECT, effects[0]);
|
||||
if (alGetError() == AL_NO_ERROR) {
|
||||
logger.info() << "successfully loaded effect into effect slot";
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::unique_ptr<Sound> ALAudio::createSound(
|
||||
std::shared_ptr<PCM> pcm, bool keepPCM
|
||||
) {
|
||||
|
|
@ -592,19 +701,36 @@ std::unique_ptr<InputDevice> ALAudio::openInputDevice(
|
|||
);
|
||||
}
|
||||
|
||||
std::unique_ptr<ALAudio> ALAudio::create() {
|
||||
std::unique_ptr<ALAudio> ALAudio::create(const AudioSettings& settings) {
|
||||
alc_enumeration_ext = alcIsExtensionPresent(nullptr, "ALC_ENUMERATION_EXT");
|
||||
|
||||
ALCdevice* device = alcOpenDevice(nullptr);
|
||||
if (device == nullptr) return nullptr;
|
||||
if (device == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
bool effects = true;
|
||||
ALint attribs[4] {};
|
||||
if (alcIsExtensionPresent(device, "ALC_EXT_EFX") == AL_TRUE) {
|
||||
logger.info() << "AL effects extension present";
|
||||
attribs[0] = ALC_MAX_AUXILIARY_SENDS;
|
||||
attribs[1] = 4;
|
||||
} else {
|
||||
logger.warning() << "AL effects extension is not supported";
|
||||
effects = false;
|
||||
}
|
||||
ALCcontext* context = alcCreateContext(device, nullptr);
|
||||
if (!alcMakeContextCurrent(context)) {
|
||||
alcCloseDevice(device);
|
||||
return nullptr;
|
||||
}
|
||||
AL_CHECK();
|
||||
|
||||
ALCint sends = 0;
|
||||
alcGetIntegerv(device, ALC_MAX_AUXILIARY_SENDS, 1, &sends);
|
||||
logger.info() << "device supports " << sends << " aux sends per source";
|
||||
|
||||
logger.info() << "initialized";
|
||||
return std::make_unique<ALAudio>(device, context);
|
||||
return std::make_unique<ALAudio>(device, context, effects, settings);
|
||||
}
|
||||
|
||||
uint ALAudio::getFreeSource() {
|
||||
|
|
@ -664,3 +790,84 @@ void ALAudio::setListener(
|
|||
|
||||
void ALAudio::update(double) {
|
||||
}
|
||||
|
||||
void ALAudio::setAcoustics(Acoustics acoustics) {
|
||||
if (!useEffects) {
|
||||
return;
|
||||
}
|
||||
if (!settings.acousticEffects.get()) {
|
||||
alAuxiliaryEffectSloti(effectSlots[0], AL_EFFECTSLOT_EFFECT, 0);
|
||||
return;
|
||||
}
|
||||
int reverbEffect = effects[REVERB_EFFECT];
|
||||
AL_CHECK(alEffectf(
|
||||
reverbEffect,
|
||||
AL_REVERB_GAIN,
|
||||
glm::max(0.1f, glm::min(acoustics.reverbGain, 0.8f) *
|
||||
(1.0f - glm::min(1.0f, acoustics.reverbAbsorption)))
|
||||
));
|
||||
AL_CHECK(alEffectf(
|
||||
reverbEffect,
|
||||
AL_REVERB_REFLECTIONS_DELAY,
|
||||
glm::clamp(
|
||||
acoustics.reverbReflectionsDelay,
|
||||
AL_REVERB_MIN_REFLECTIONS_DELAY,
|
||||
AL_REVERB_MAX_REFLECTIONS_DELAY
|
||||
)
|
||||
));
|
||||
AL_CHECK(alEffectf(
|
||||
reverbEffect,
|
||||
AL_REVERB_LATE_REVERB_DELAY,
|
||||
glm::clamp(
|
||||
acoustics.reverbLateReflectionsDelay,
|
||||
AL_REVERB_MIN_LATE_REVERB_DELAY,
|
||||
AL_REVERB_MAX_LATE_REVERB_DELAY
|
||||
)
|
||||
));
|
||||
AL_CHECK(alEffectf(
|
||||
reverbEffect,
|
||||
AL_REVERB_DECAY_TIME,
|
||||
glm::clamp(
|
||||
acoustics.reverbDecayTime,
|
||||
AL_REVERB_MIN_DECAY_TIME,
|
||||
AL_REVERB_MAX_DECAY_TIME
|
||||
)
|
||||
));
|
||||
AL_CHECK(alEffectf(
|
||||
reverbEffect,
|
||||
AL_REVERB_ROOM_ROLLOFF_FACTOR,
|
||||
glm::clamp(
|
||||
acoustics.reverbRoomRolloff,
|
||||
AL_REVERB_MIN_ROOM_ROLLOFF_FACTOR,
|
||||
AL_REVERB_MAX_ROOM_ROLLOFF_FACTOR
|
||||
)
|
||||
));
|
||||
AL_CHECK(alEffectf(
|
||||
reverbEffect,
|
||||
AL_REVERB_REFLECTIONS_GAIN,
|
||||
glm::clamp(
|
||||
glm::pow(acoustics.reverbGain, 2.0f),
|
||||
AL_REVERB_MIN_REFLECTIONS_GAIN,
|
||||
AL_REVERB_MAX_REFLECTIONS_GAIN
|
||||
)
|
||||
));
|
||||
AL_CHECK(alEffectf(
|
||||
reverbEffect,
|
||||
AL_REVERB_LATE_REVERB_GAIN,
|
||||
glm::clamp(
|
||||
glm::pow(acoustics.reverbGain, 2.0f),
|
||||
AL_REVERB_MIN_LATE_REVERB_GAIN,
|
||||
AL_REVERB_MAX_LATE_REVERB_GAIN
|
||||
)
|
||||
));
|
||||
AL_CHECK(alEffectf(
|
||||
reverbEffect,
|
||||
AL_REVERB_DECAY_HFRATIO,
|
||||
glm::clamp(
|
||||
1.0f - acoustics.reverbAbsorption,
|
||||
AL_REVERB_MIN_DECAY_HFRATIO,
|
||||
AL_REVERB_MAX_DECAY_HFRATIO
|
||||
)
|
||||
));
|
||||
alAuxiliaryEffectSloti(effectSlots[0], AL_EFFECTSLOT_EFFECT, reverbEffect);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,19 +3,19 @@
|
|||
#include <glm/glm.hpp>
|
||||
#include <queue>
|
||||
#include <string>
|
||||
#include <array>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "typedefs.hpp"
|
||||
#include "audio/audio.hpp"
|
||||
#include "audio/effects.hpp"
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <OpenAL/al.h>
|
||||
#include <OpenAL/alc.h>
|
||||
#else
|
||||
#include <AL/al.h>
|
||||
#include <AL/alc.h>
|
||||
#endif
|
||||
#include <AL/alext.h>
|
||||
|
||||
struct AudioSettings;
|
||||
|
||||
namespace audio {
|
||||
struct ALBuffer;
|
||||
|
|
@ -176,10 +176,25 @@ namespace audio {
|
|||
|
||||
std::vector<uint> allbuffers;
|
||||
std::vector<uint> freebuffers;
|
||||
|
||||
uint maxSources = 256;
|
||||
uint maxEffectSlots = 64;
|
||||
|
||||
const AudioSettings& settings;
|
||||
|
||||
bool initEffects();
|
||||
public:
|
||||
ALAudio(ALCdevice* device, ALCcontext* context);
|
||||
std::vector<uint> effectSlots;
|
||||
std::vector<uint> effects;
|
||||
std::array<uint, 1> filters;
|
||||
|
||||
bool useEffects;
|
||||
|
||||
ALAudio(
|
||||
ALCdevice* device,
|
||||
ALCcontext* context,
|
||||
bool effects,
|
||||
const AudioSettings& settings
|
||||
);
|
||||
~ALAudio();
|
||||
|
||||
uint getFreeSource();
|
||||
|
|
@ -214,10 +229,12 @@ namespace audio {
|
|||
|
||||
void update(double delta) override;
|
||||
|
||||
void setAcoustics(Acoustics acoustics) override;
|
||||
|
||||
bool isDummy() const override {
|
||||
return false;
|
||||
}
|
||||
|
||||
static std::unique_ptr<ALAudio> create();
|
||||
static std::unique_ptr<ALAudio> create(const AudioSettings& settings);
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,12 @@
|
|||
#pragma once
|
||||
|
||||
#include "typedefs.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
|
||||
#include "typedefs.hpp"
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <OpenAL/al.h>
|
||||
#else
|
||||
#include <AL/al.h>
|
||||
#endif
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#define AL_CHECK(STATEMENT) \
|
||||
|
|
|
|||
|
|
@ -104,6 +104,9 @@ namespace audio {
|
|||
void update(double delta) override {
|
||||
}
|
||||
|
||||
void setAcoustics(Acoustics) override {
|
||||
}
|
||||
|
||||
bool isDummy() const override {
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,8 @@ namespace {
|
|||
static bool input_enabled = false;
|
||||
}
|
||||
|
||||
Channel::Channel(std::string name) : name(std::move(name)) {
|
||||
Channel::Channel(std::string name, bool effects)
|
||||
: name(std::move(name)), effects(effects) {
|
||||
}
|
||||
|
||||
float Channel::getVolume() const {
|
||||
|
|
@ -56,6 +57,10 @@ bool Channel::isPaused() const {
|
|||
return paused;
|
||||
}
|
||||
|
||||
bool Channel::isEffectsApplied() const {
|
||||
return effects;
|
||||
}
|
||||
|
||||
size_t PCMStream::readFully(char* buffer, size_t bufferSize, bool loop) {
|
||||
if (!isOpen()) {
|
||||
return 0;
|
||||
|
|
@ -159,7 +164,7 @@ void audio::initialize(
|
|||
enabled = enabled && settings.enabled.get();
|
||||
if (enabled) {
|
||||
logger.info() << "initializing ALAudio backend";
|
||||
backend = ALAudio::create().release();
|
||||
backend = ALAudio::create(settings).release();
|
||||
}
|
||||
if (backend == nullptr) {
|
||||
if (enabled) {
|
||||
|
|
@ -171,15 +176,16 @@ void audio::initialize(
|
|||
struct {
|
||||
std::string name;
|
||||
NumberSetting* setting;
|
||||
bool effects;
|
||||
} builtin_channels[] {
|
||||
{"master", &settings.volumeMaster},
|
||||
{"regular", &settings.volumeRegular},
|
||||
{"music", &settings.volumeMusic},
|
||||
{"ambient", &settings.volumeAmbient},
|
||||
{"ui", &settings.volumeUI}
|
||||
{"master", &settings.volumeMaster, false},
|
||||
{"regular", &settings.volumeRegular, true},
|
||||
{"music", &settings.volumeMusic, false},
|
||||
{"ambient", &settings.volumeAmbient, true},
|
||||
{"ui", &settings.volumeUI, false}
|
||||
};
|
||||
for (auto& channel : builtin_channels) {
|
||||
create_channel(channel.name);
|
||||
create_channel(channel.name, channel.effects);
|
||||
objects_keeper.keepAlive(channel.setting->observe([=](auto value) {
|
||||
audio::get_channel(channel.name)->setVolume(value * value);
|
||||
}, true));
|
||||
|
|
@ -419,12 +425,12 @@ Speaker* audio::get_speaker(speakerid_t id) {
|
|||
return found->second.get();
|
||||
}
|
||||
|
||||
int audio::create_channel(const std::string& name) {
|
||||
int audio::create_channel(const std::string& name, bool effects) {
|
||||
int index = get_channel_index(name);
|
||||
if (index != -1) {
|
||||
return index;
|
||||
}
|
||||
channels.emplace_back(std::make_unique<Channel>(name));
|
||||
channels.emplace_back(std::make_unique<Channel>(name, effects));
|
||||
return channels.size() - 1;
|
||||
}
|
||||
|
||||
|
|
@ -496,6 +502,10 @@ void audio::update(double delta) {
|
|||
}
|
||||
}
|
||||
|
||||
void audio::set_acoustics(Acoustics acoustics) {
|
||||
backend->setAcoustics(std::move(acoustics));
|
||||
}
|
||||
|
||||
void audio::reset_channel(int index) {
|
||||
auto channel = get_channel(index);
|
||||
if (channel == nullptr) {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
#include "typedefs.hpp"
|
||||
#include "settings.hpp"
|
||||
#include "io/fwd.hpp"
|
||||
#include "effects.hpp"
|
||||
|
||||
namespace audio {
|
||||
/// @brief playing speaker uid
|
||||
|
|
@ -42,8 +43,9 @@ namespace audio {
|
|||
/// @brief Channel volume setting
|
||||
float volume = 1.0f;
|
||||
bool paused = false;
|
||||
bool effects;
|
||||
public:
|
||||
Channel(std::string name);
|
||||
Channel(std::string name, bool effects);
|
||||
|
||||
/// @brief Get channel volume
|
||||
float getVolume() const;
|
||||
|
|
@ -72,6 +74,9 @@ namespace audio {
|
|||
|
||||
/// @brief Check if the channel is paused
|
||||
bool isPaused() const;
|
||||
|
||||
/// @brief Check if the channel uses common acoustic effects / filters
|
||||
bool isEffectsApplied() const;
|
||||
};
|
||||
|
||||
/// @brief Pulse-code modulation data
|
||||
|
|
@ -394,6 +399,7 @@ namespace audio {
|
|||
virtual std::vector<std::string> getInputDeviceNames() = 0;
|
||||
virtual std::vector<std::string> getOutputDeviceNames() = 0;
|
||||
virtual void update(double delta) = 0;
|
||||
virtual void setAcoustics(Acoustics acoustics) = 0;
|
||||
|
||||
/// @brief Check if backend is an abstraction that does not internally
|
||||
/// work with actual audio data or play anything
|
||||
|
|
@ -547,8 +553,9 @@ namespace audio {
|
|||
/// @brief Create new channel.
|
||||
/// All non-builtin channels will be destroyed on audio::reset() call
|
||||
/// @param name channel name
|
||||
/// @param effects use common acoustic effects / filters
|
||||
/// @return new channel index
|
||||
int create_channel(const std::string& name);
|
||||
int create_channel(const std::string& name, bool effects);
|
||||
|
||||
/// @brief Get channel index by name
|
||||
/// @param name channel name
|
||||
|
|
@ -580,6 +587,8 @@ namespace audio {
|
|||
/// @param delta time elapsed since the last update (seconds)
|
||||
void update(double delta);
|
||||
|
||||
void set_acoustics(Acoustics acoustics);
|
||||
|
||||
/// @brief Stop all playing audio in channel, reset channel state
|
||||
void reset_channel(int channel);
|
||||
|
||||
|
|
|
|||
38
src/audio/effects.hpp
Normal file
38
src/audio/effects.hpp
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <variant>
|
||||
#include <optional>
|
||||
|
||||
namespace audio {
|
||||
struct Effect {
|
||||
struct Property {
|
||||
using Value = std::variant<float, int>;
|
||||
int id;
|
||||
Value def;
|
||||
Value min;
|
||||
Value max;
|
||||
};
|
||||
std::map<std::string, Property> properties;
|
||||
};
|
||||
|
||||
class EffectControl {
|
||||
public:
|
||||
EffectControl(const Effect* effect) : effect(effect) {};
|
||||
virtual ~EffectControl() = default;
|
||||
|
||||
virtual void set(const std::string& name, Effect::Property::Value value) = 0;
|
||||
protected:
|
||||
const Effect* effect = nullptr;
|
||||
};
|
||||
|
||||
struct Acoustics {
|
||||
float reverbGain;
|
||||
float reverbReflectionsDelay;
|
||||
float reverbLateReflectionsDelay;
|
||||
float reverbDecayTime;
|
||||
float reverbRoomRolloff;
|
||||
float reverbAbsorption;
|
||||
};
|
||||
}
|
||||
40
src/devtools/stdin_cmd_reader.cpp
Normal file
40
src/devtools/stdin_cmd_reader.cpp
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
#include "stdin_cmd_reader.hpp"
|
||||
|
||||
#include "engine/Engine.hpp"
|
||||
#include "logic/CommandsInterpreter.hpp"
|
||||
#include "coders/json.hpp"
|
||||
#include "debug/Logger.hpp"
|
||||
|
||||
#include <thread>
|
||||
#include <iostream>
|
||||
|
||||
static debug::Logger logger("stdin-cmd");
|
||||
|
||||
static std::thread reader_thread;
|
||||
|
||||
void cmd::start_stdin_cmd_reader(Engine& engine) {
|
||||
reader_thread = std::thread([&engine]() {
|
||||
auto& interpreter = engine.getCmd();
|
||||
logger.info() << "reader thread started";
|
||||
|
||||
std::string line;
|
||||
while (std::getline(std::cin, line)) {
|
||||
if (line.empty()) {
|
||||
continue;
|
||||
}
|
||||
engine.postRunnable([line, &interpreter] () {
|
||||
try {
|
||||
auto result = interpreter.execute(line);
|
||||
if (result.isString()) {
|
||||
logger.info() << result.asString();
|
||||
} else {
|
||||
logger.info() << json::stringify(result, true);
|
||||
}
|
||||
} catch (const std::exception& err) {
|
||||
logger.error() << err.what();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
reader_thread.detach();
|
||||
}
|
||||
7
src/devtools/stdin_cmd_reader.hpp
Normal file
7
src/devtools/stdin_cmd_reader.hpp
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
class Engine;
|
||||
|
||||
namespace cmd {
|
||||
void start_stdin_cmd_reader(Engine& engine);
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@
|
|||
struct CoreParameters {
|
||||
bool headless = false;
|
||||
bool testMode = false;
|
||||
bool stdinCommands = false;
|
||||
std::filesystem::path resFolder = "res";
|
||||
std::filesystem::path userFolder = ".";
|
||||
std::filesystem::path scriptFile;
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
#include "devtools/DebuggingServer.hpp"
|
||||
#include "devtools/Editor.hpp"
|
||||
#include "devtools/Project.hpp"
|
||||
#include "devtools/stdin_cmd_reader.hpp"
|
||||
#include "EnginePaths.hpp"
|
||||
#include "frontend/locale.hpp"
|
||||
#include "frontend/menu.hpp"
|
||||
|
|
@ -191,6 +192,9 @@ void Engine::initialize(CoreParameters coreParameters) {
|
|||
if (!params.headless) {
|
||||
project->loadProjectClientScript();
|
||||
}
|
||||
if (params.stdinCommands) {
|
||||
cmd::start_stdin_cmd_reader(*this);
|
||||
}
|
||||
}
|
||||
|
||||
void Engine::loadSettings() {
|
||||
|
|
@ -419,10 +423,20 @@ EngineSettings& Engine::getSettings() {
|
|||
}
|
||||
|
||||
Assets* Engine::getAssets() {
|
||||
return assets->getStorage();
|
||||
return assets ? assets->getStorage() : nullptr;
|
||||
}
|
||||
|
||||
Assets& Engine::requireAssets() {
|
||||
if (isHeadless()) {
|
||||
throw std::runtime_error("assets are not available in headless mode");
|
||||
}
|
||||
return *assets->getStorage();
|
||||
}
|
||||
|
||||
AssetsLoader& Engine::acquireBackgroundLoader() {
|
||||
if (isHeadless()) {
|
||||
throw std::runtime_error("assets are not available in headless mode");
|
||||
}
|
||||
return assets->acquireBackgroundLoader();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -110,6 +110,7 @@ public:
|
|||
|
||||
/// @brief Get active assets storage instance
|
||||
Assets* getAssets();
|
||||
Assets& requireAssets();
|
||||
|
||||
AssetsLoader& acquireBackgroundLoader();
|
||||
|
||||
|
|
|
|||
|
|
@ -23,9 +23,12 @@
|
|||
#include "io/io.hpp"
|
||||
#include "audio/audio.hpp"
|
||||
#include "maths/util.hpp"
|
||||
#include "debug/Logger.hpp"
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
static debug::Logger logger("decorator");
|
||||
|
||||
/// @brief Not greater than 64 for this BIG_PRIME value
|
||||
inline constexpr int UPDATE_AREA_DIAMETER = 32;
|
||||
/// @brief Number of blocks in the volume
|
||||
|
|
@ -70,19 +73,90 @@ Decorator::Decorator(
|
|||
|
||||
void Decorator::addParticles(const Block& def, const glm::ivec3& pos) {
|
||||
const auto& found = blockEmitters.find(pos);
|
||||
if (found == blockEmitters.end()) {
|
||||
auto treg = util::get_texture_region(
|
||||
assets, def.particles->texture, ""
|
||||
);
|
||||
blockEmitters[pos] = renderer.particles->add(std::make_unique<Emitter>(
|
||||
level,
|
||||
glm::vec3{pos.x + 0.5, pos.y + 0.5, pos.z + 0.5},
|
||||
*def.particles,
|
||||
treg.texture,
|
||||
treg.region,
|
||||
-1
|
||||
));
|
||||
if (found != blockEmitters.end()) {
|
||||
return;
|
||||
}
|
||||
auto treg = util::get_texture_region(
|
||||
assets, def.particles->texture, ""
|
||||
);
|
||||
blockEmitters[pos] = renderer.particles->add(std::make_unique<Emitter>(
|
||||
level,
|
||||
glm::vec3{pos.x + 0.5, pos.y + 0.5, pos.z + 0.5},
|
||||
*def.particles,
|
||||
treg.texture,
|
||||
treg.region,
|
||||
-1
|
||||
));
|
||||
}
|
||||
|
||||
void Decorator::updateAcoustics(const Camera& camera) {
|
||||
audio::Acoustics acoustics {};
|
||||
util::PseudoRandom random(34621U);
|
||||
|
||||
auto& chunks = *player.chunks;
|
||||
const auto& start = camera.position;
|
||||
float rayLength = 40.0f;
|
||||
|
||||
auto& blocks = level.content.getIndices()->blocks;
|
||||
|
||||
int rays = 100;
|
||||
int hit = 0;
|
||||
float averageDistance = 0.0f;
|
||||
float averageAbsorption = 0.0f;
|
||||
float speedOfSound = 300.0f;
|
||||
float minDistance = rayLength;
|
||||
for (int i = 0; i < rays; i++) {
|
||||
float u1 = random.randFloat();
|
||||
float u2 = random.randFloat();
|
||||
float z = 2.0f * u1 - 1.0f;
|
||||
float phi = 2.0f * glm::pi<float>() * u2;
|
||||
float r = std::sqrt(1.0f - z * z);
|
||||
|
||||
glm::vec3 dir = { r * std::cos(phi), r * std::sin(phi), z };
|
||||
|
||||
dir = glm::normalize(dir);
|
||||
glm::vec3 end;
|
||||
glm::ivec3 norm;
|
||||
glm::ivec3 iend;
|
||||
auto vox = chunks.rayCast(start, dir, rayLength, end, norm, iend);
|
||||
if (vox == nullptr) {
|
||||
continue;
|
||||
}
|
||||
auto distance = glm::distance(start, end);
|
||||
if (distance >= rayLength * 0.98f) {
|
||||
continue;
|
||||
}
|
||||
if (distance < minDistance) {
|
||||
minDistance = distance;
|
||||
}
|
||||
auto def = blocks.get(vox->id);
|
||||
if (def == nullptr) {
|
||||
continue;
|
||||
}
|
||||
auto material = level.content.findBlockMaterial(def->material);
|
||||
if (material == nullptr) {
|
||||
return;
|
||||
}
|
||||
hit++;
|
||||
averageDistance += distance;
|
||||
averageAbsorption += material->soundAbsorption;
|
||||
}
|
||||
if (hit > 0) {
|
||||
averageDistance /= hit;
|
||||
averageAbsorption /= hit;
|
||||
}
|
||||
|
||||
float decayTime = 20.0f * (averageDistance / rayLength);
|
||||
float escapeRatio = static_cast<float>(rays - hit) / static_cast<float>(rays);
|
||||
|
||||
acoustics.reverbGain = glm::pow(hit / static_cast<float>(rays), 3.0f);
|
||||
acoustics.reverbReflectionsDelay = minDistance / speedOfSound;
|
||||
acoustics.reverbLateReflectionsDelay = averageDistance / speedOfSound;
|
||||
acoustics.reverbDecayTime = decayTime;
|
||||
acoustics.reverbRoomRolloff = escapeRatio * 1.5f;
|
||||
acoustics.reverbAbsorption = averageAbsorption;
|
||||
|
||||
audio::set_acoustics(std::move(acoustics));
|
||||
}
|
||||
|
||||
void Decorator::updateRandom(
|
||||
|
|
@ -283,4 +357,5 @@ void Decorator::update(
|
|||
}
|
||||
updateBlockEmitters(camera);
|
||||
updateTextNotes();
|
||||
updateAcoustics(camera);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ class Decorator {
|
|||
void updateBlockEmitters(const Camera& camera);
|
||||
void updateTextNotes();
|
||||
void addParticles(const Block& def, const glm::ivec3& pos);
|
||||
void updateAcoustics(const Camera& camera);
|
||||
public:
|
||||
Decorator(
|
||||
Engine& engine,
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ SettingsHandler::SettingsHandler(EngineSettings& settings) {
|
|||
builder.add("volume-ambient", &settings.audio.volumeAmbient);
|
||||
builder.add("volume-music", &settings.audio.volumeMusic);
|
||||
builder.add("input-device", &settings.audio.inputDevice);
|
||||
builder.add("acoustic-effects", &settings.audio.acousticEffects);
|
||||
|
||||
builder.addSection("display");
|
||||
builder.add("width", &settings.display.width);
|
||||
|
|
|
|||
|
|
@ -15,10 +15,10 @@
|
|||
using namespace scripting;
|
||||
|
||||
static void load_texture(
|
||||
const ubyte* bytes, size_t size, const std::string& destname
|
||||
Assets& assets, const ubyte* bytes, size_t size, const std::string& destname
|
||||
) {
|
||||
try {
|
||||
engine->getAssets()->store(png::load_texture(bytes, size), destname);
|
||||
assets.store(png::load_texture(bytes, size), destname);
|
||||
} catch (const std::runtime_error& err) {
|
||||
debug::Logger logger("lua.assetslib");
|
||||
logger.error() << err.what();
|
||||
|
|
@ -34,6 +34,8 @@ static int l_request_texture(lua::State* L) {
|
|||
}
|
||||
|
||||
static int l_load_texture(lua::State* L) {
|
||||
auto& assets = engine->requireAssets();
|
||||
|
||||
if (lua::isstring(L, 3) && lua::require_lstring(L, 3) != "png") {
|
||||
throw std::runtime_error("unsupportd image format");
|
||||
}
|
||||
|
|
@ -47,10 +49,13 @@ static int l_load_texture(lua::State* L) {
|
|||
lua::pop(L);
|
||||
}
|
||||
lua::pop(L);
|
||||
load_texture(buffer.data(), buffer.size(), lua::require_string(L, 2));
|
||||
load_texture(
|
||||
assets, buffer.data(), buffer.size(), lua::require_string(L, 2)
|
||||
);
|
||||
} else {
|
||||
auto string = lua::bytearray_as_string(L, 1);
|
||||
load_texture(
|
||||
assets,
|
||||
reinterpret_cast<const ubyte*>(string.data()),
|
||||
string.size(),
|
||||
lua::require_string(L, 2)
|
||||
|
|
@ -61,13 +66,15 @@ static int l_load_texture(lua::State* L) {
|
|||
}
|
||||
|
||||
static int l_parse_model(lua::State* L) {
|
||||
auto& assets = engine->requireAssets();
|
||||
|
||||
auto format = lua::require_lstring(L, 1);
|
||||
auto string = lua::require_lstring(L, 2);
|
||||
auto name = lua::require_string(L, 3);
|
||||
|
||||
if (format == "xml" || format == "vcm") {
|
||||
auto vcmModel = vcm::parse(name, string, format == "xml");
|
||||
engine->getAssets()->store(
|
||||
assets.store(
|
||||
std::make_unique<model::Model>(std::move(vcmModel.squash())), name
|
||||
);
|
||||
} else {
|
||||
|
|
@ -79,7 +86,7 @@ static int l_parse_model(lua::State* L) {
|
|||
}
|
||||
|
||||
static int l_to_canvas(lua::State* L) {
|
||||
auto& assets = *engine->getAssets();
|
||||
auto& assets = engine->requireAssets();
|
||||
|
||||
auto alias = lua::require_lstring(L, 1);
|
||||
size_t sep = alias.rfind(':');
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ using namespace scripting;
|
|||
static DocumentNode get_document_node_impl(
|
||||
lua::State*, const std::string& name, const std::string& nodeName, bool throwable=true
|
||||
) {
|
||||
auto doc = engine->getAssets()->get<UiDocument>(name);
|
||||
auto doc = engine->requireAssets().get<UiDocument>(name);
|
||||
if (doc == nullptr) {
|
||||
if (throwable) {
|
||||
throw std::runtime_error("document '" + name + "' not found");
|
||||
|
|
@ -1027,7 +1027,7 @@ static int l_gui_str(lua::State* L) {
|
|||
|
||||
static int l_gui_reindex(lua::State* L) {
|
||||
auto name = lua::require_string(L, 1);
|
||||
auto doc = engine->getAssets()->get<UiDocument>(name);
|
||||
auto doc = engine->requireAssets().get<UiDocument>(name);
|
||||
if (doc == nullptr) {
|
||||
throw std::runtime_error(
|
||||
"document '" + std::string(name) + "' not found"
|
||||
|
|
@ -1125,7 +1125,7 @@ static int l_gui_load_document(lua::State* L) {
|
|||
engine->getGUI(), std::move(env), alias, filename, filename.string()
|
||||
);
|
||||
auto document = documentPtr.get();
|
||||
engine->getAssets()->store(std::move(documentPtr), alias);
|
||||
engine->requireAssets().store(std::move(documentPtr), alias);
|
||||
|
||||
// namespace extension
|
||||
if (lua::istable(L, 4)) {
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ struct AudioSettings {
|
|||
NumberSetting volumeMusic {1.0f, 0.0f, 1.0f, setting_format::percent};
|
||||
|
||||
StringSetting inputDevice {"auto"};
|
||||
|
||||
FlagSetting acousticEffects {true};
|
||||
};
|
||||
|
||||
struct DisplaySettings {
|
||||
|
|
|
|||
|
|
@ -62,6 +62,10 @@ static bool perform_keyword(
|
|||
params.headless = true;
|
||||
return true;
|
||||
}, "", "run in headless mode."),
|
||||
ArgC("--stdin-cmd", [¶ms]() -> bool {
|
||||
params.stdinCommands = true;
|
||||
return true;
|
||||
}, "", "run commands from stdin."),
|
||||
ArgC("--tps", [¶ms, &reader]() -> bool {
|
||||
params.tps = reader.nextInt();
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -24,7 +24,8 @@ dv::value BlockMaterial::serialize() const {
|
|||
{"steps-sound", stepsSound},
|
||||
{"place-sound", placeSound},
|
||||
{"break-sound", breakSound},
|
||||
{"hit-sound", hitSound}
|
||||
{"hit-sound", hitSound},
|
||||
{"sound-absorption", soundAbsorption}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -34,6 +35,7 @@ void BlockMaterial::deserialize(const dv::value& src) {
|
|||
src.at("place-sound").get(placeSound);
|
||||
src.at("break-sound").get(breakSound);
|
||||
src.at("hit-sound").get(hitSound);
|
||||
src.at("sound-absorption").get(soundAbsorption);
|
||||
}
|
||||
|
||||
CoordSystem::CoordSystem(glm::ivec3 axisX, glm::ivec3 axisY, glm::ivec3 axisZ)
|
||||
|
|
|
|||
|
|
@ -147,6 +147,7 @@ struct BlockMaterial : Serializable {
|
|||
std::string placeSound;
|
||||
std::string breakSound;
|
||||
std::string hitSound;
|
||||
float soundAbsorption = 0.5f;
|
||||
|
||||
dv::value toTable() const; // for compatibility
|
||||
dv::value serialize() const override;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue