feat: Implement loading LIP files in conversations

This commit is contained in:
Vsevolod Kremianskii 2021-02-10 17:54:55 +07:00
parent 746a742744
commit 2566e0d1e4
14 changed files with 270 additions and 34 deletions

View file

@ -155,7 +155,9 @@ set(RENDER_HEADERS
src/render/image/tgafile.h
src/render/image/tpcfile.h
src/render/image/txifile.h
src/render/lipfile.h
src/render/lip/lipanimation.h
src/render/lip/lipfile.h
src/render/lip/lips.h
src/render/mesh/aabb.h
src/render/mesh/billboard.h
src/render/mesh/cube.h
@ -191,7 +193,9 @@ set(RENDER_SOURCES
src/render/image/tgafile.cpp
src/render/image/tpcfile.cpp
src/render/image/txifile.cpp
src/render/lipfile.cpp
src/render/lip/lipanimation.cpp
src/render/lip/lipfile.cpp
src/render/lip/lips.cpp
src/render/mesh/aabb.cpp
src/render/mesh/billboard.cpp
src/render/mesh/cube.cpp

View file

@ -26,6 +26,7 @@
#include "../common/log.h"
#include "../common/pathutil.h"
#include "../experimental/tor/gr2file.h"
#include "../render/lip/lips.h"
#include "../render/model/mdlfile.h"
#include "../render/model/models.h"
#include "../render/textures.h"
@ -229,6 +230,8 @@ void Game::loadModule(const string &name, string entry) {
Scripts::instance().invalidateCache();
Blueprints::instance().invalidateCache();
SoundSets::instance().invalidateCache();
Lips::instance().invalidateCache();
Resources::instance().loadModule(name);
if (_module) {

View file

@ -21,6 +21,7 @@
#include "../../audio/player.h"
#include "../../common/log.h"
#include "../../gui/control/listbox.h"
#include "../../render/lip/lips.h"
#include "../../render/model/models.h"
#include "../../resource/resources.h"
@ -161,15 +162,16 @@ void Conversation::loadVoiceOver() {
}
// Play current voice over either from Sound or from VO_ResRef
shared_ptr<AudioStream> voice;
string voiceResRef;
if (!_currentEntry->sound.empty()) {
voice = AudioFiles::instance().get(_currentEntry->sound);
voiceResRef = _currentEntry->sound;
}
if (!voice && !_currentEntry->voResRef.empty()) {
voice = AudioFiles::instance().get(_currentEntry->voResRef);
if (voiceResRef.empty() && !_currentEntry->voResRef.empty()) {
voiceResRef = _currentEntry->voResRef;
}
if (voice) {
_currentVoice = AudioPlayer::instance().play(voice, AudioType::Voice);
if (!voiceResRef.empty()) {
_currentVoice = AudioPlayer::instance().play(voiceResRef, AudioType::Voice);
_lipAnimation = Lips::instance().get(voiceResRef);
}
}

View file

@ -19,6 +19,7 @@
#include "../../audio/soundhandle.h"
#include "../../common/timer.h"
#include "../../render/lip/lipanimation.h"
#include "../../render/model/model.h"
#include "../camera/types.h"
@ -83,6 +84,7 @@ protected:
private:
std::shared_ptr<audio::SoundHandle> _currentVoice;
std::shared_ptr<render::LipAnimation> _lipAnimation;
Timer _endEntryTimer;
float _entryDuration { 0.0f };
std::vector<const Dialog::EntryReply *> _replies;

View file

@ -0,0 +1,43 @@
/*
* Copyright (c) 2020-2021 The reone project contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "lipanimation.h"
#include <stdexcept>
using namespace std;
namespace reone {
namespace render {
LipAnimation::LipAnimation(vector<Keyframe> keyframes) : _keyframes(move(keyframes)) {
}
const LipAnimation::Keyframe &LipAnimation::getKeyframe(float time) const {
if (_keyframes.empty()) {
throw logic_error("keyframes is empty");
}
for (auto &frame : _keyframes) {
if (time <= frame.time) return frame;
}
return _keyframes.back();
}
} // namespace render
} // namespace reone

View file

@ -0,0 +1,44 @@
/*
* Copyright (c) 2020-2021 The reone project contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <cstdint>
#include <vector>
namespace reone {
namespace render {
class LipAnimation {
public:
struct Keyframe {
float time { 0.0f };
uint8_t shape { 0 }; /**< an index into the keyframes of the "talk" animation */
};
LipAnimation(std::vector<Keyframe> keyframes);
const Keyframe &getKeyframe(float time) const;
private:
std::vector<Keyframe> _keyframes;
};
} // namespace render
} // namespace reone

View file

@ -32,12 +32,15 @@ void LipFile::doLoad() {
float length = readFloat();
uint32_t entryCount = readUint32();
vector<LipAnimation::Keyframe> keyframes;
for (uint32_t i = 0; i < entryCount; ++i) {
Keyframe keyframe;
LipAnimation::Keyframe keyframe;
keyframe.time = readFloat();
keyframe.shape = readByte();
_keyframes.push_back(move(keyframe));
keyframes.push_back(move(keyframe));
}
_animation = make_shared<LipAnimation>(move(keyframes));
}
} // namespace render

View file

@ -17,7 +17,9 @@
#pragma once
#include "../resource/format/binfile.h"
#include "../../resource/format/binfile.h"
#include "lipanimation.h"
namespace reone {
@ -25,17 +27,12 @@ namespace render {
class LipFile : public resource::BinaryFile {
public:
struct Keyframe {
float time { 0.0f };
uint8_t shape { 0 };
};
LipFile();
const std::vector<Keyframe> &keyframes() const { return _keyframes; }
std::shared_ptr<LipAnimation> animation() const { return _animation; }
private:
std::vector<Keyframe> _keyframes;
std::shared_ptr<LipAnimation> _animation;
void doLoad() override;
};

60
src/render/lip/lips.cpp Normal file
View file

@ -0,0 +1,60 @@
/*
* Copyright (c) 2020-2021 The reone project contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "lips.h"
#include "../../common/streamutil.h"
#include "../../resource/resources.h"
#include "lipfile.h"
using namespace std;
using namespace reone::resource;
namespace reone {
namespace render {
Lips &Lips::instance() {
static Lips instance;
return instance;
}
void Lips::invalidateCache() {
_cache.clear();
}
shared_ptr<LipAnimation> Lips::get(const string &resRef) {
auto maybeAnimation = _cache.find(resRef);
if (maybeAnimation != _cache.end()) return maybeAnimation->second;
shared_ptr<LipAnimation> animation;
shared_ptr<ByteArray> lipData(Resources::instance().get(resRef, ResourceType::Lip));
if (lipData) {
LipFile lip;
lip.load(wrap(lipData));
animation = lip.animation();
}
return _cache.insert(make_pair(resRef, move(animation))).first->second;
}
} // namespace render
} // namespace reone

46
src/render/lip/lips.h Normal file
View file

@ -0,0 +1,46 @@
/*
* Copyright (c) 2020-2021 The reone project contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <memory>
#include <string>
#include <unordered_map>
#include "lipanimation.h"
namespace reone {
namespace render {
class Lips {
public:
static Lips &instance();
void invalidateCache();
std::shared_ptr<LipAnimation> get(const std::string &resRef);
private:
std::unordered_map<std::string, std::shared_ptr<LipAnimation>> _cache;
Lips() = default;
};
} // namespace render
} // namespace reone

View file

@ -17,6 +17,8 @@
#include "binfile.h"
#include <boost/format.hpp>
using namespace std;
namespace fs = boost::filesystem;
@ -61,7 +63,7 @@ void BinaryFile::checkSignature() {
char buf[16];
_in->read(buf, _signSize);
if (!equal(_sign.begin(), _sign.end(), buf)) {
throw runtime_error("Invalid binary file signature");
throw runtime_error(str(boost::format("Invalid binary file signature: %s") % string(buf, _signSize)));
}
}

View file

@ -49,6 +49,7 @@ static const char kSoundsDirectoryName[] = "streamsounds";
static const char kVoiceDirectoryName[] = "streamvoice";
static const char kWavesDirectoryName[] = "streamwaves";
static const char kTexturePackDirectoryName[] = "texturepacks";
static const char kLipsDirectoryName[] = "lips";
static const char kGUITexturePackFilename[] = "swpc_tex_gui.erf";
static const char kTexturePackFilename[] = "swpc_tex_tpa.erf";
@ -64,10 +65,12 @@ void Resources::init(GameID gameId, const fs::path &gamePath) {
indexKeyBifFiles();
indexTexturePacks();
indexAudioFiles();
indexOverrideDirectory();
indexTalkTable();
indexAudioFiles();
indexLipModFiles();
indexExeFile();
indexOverrideDirectory();
loadModuleNames();
}
@ -121,6 +124,28 @@ void Resources::indexAudioFiles() {
}
}
void Resources::indexLipModFiles() {
static vector<string> kotorMods { "global", "localization" };
static vector<string> tslMods { "localization" };
const vector<string> &mods = _gameId == GameID::KotOR ? kotorMods : tslMods;
fs::path lipsPath(getPathIgnoreCase(_gamePath, "lips"));
for (auto &mod : mods) {
fs::path modPath(getPathIgnoreCase(lipsPath, mod + ".mod"));
indexErfFile(modPath);
}
}
void Resources::indexRimFile(const fs::path &path) {
auto rim = make_unique<RimFile>();
rim->load(path);
_providers.push_back(move(rim));
debug(boost::format("Resources: indexed: %s") % path);
}
void Resources::indexDirectory(const fs::path &path) {
auto folder = make_unique<Folder>();
folder->load(path);
@ -192,11 +217,15 @@ void Resources::loadModule(const string &name) {
_transientProviders.clear();
fs::path modulesPath(getPathIgnoreCase(_gamePath, kModulesDirectoryName));
fs::path rimPath(getPathIgnoreCase(modulesPath, name + ".rim"));
fs::path rimsPath(getPathIgnoreCase(modulesPath, name + "_s.rim"));
fs::path moduleRimPath(getPathIgnoreCase(modulesPath, name + ".rim"));
fs::path moduleRimSPath(getPathIgnoreCase(modulesPath, name + "_s.rim"));
indexTransientRimFile(rimPath);
indexTransientRimFile(rimsPath);
fs::path lipsPath(getPathIgnoreCase(_gamePath, kLipsDirectoryName));
fs::path lipModPath(getPathIgnoreCase(lipsPath, name + "_loc.mod"));
indexTransientRimFile(moduleRimPath);
indexTransientRimFile(moduleRimSPath);
indexTransientErfFile(lipModPath);
if (_gameId == GameID::TSL) {
fs::path dlgPath(getPathIgnoreCase(modulesPath, name + "_dlg.erf"));

View file

@ -112,16 +112,19 @@ private:
Resources &operator=(const Resources &) = delete;
void indexAudioFiles();
void indexDirectory(const boost::filesystem::path &path);
void indexErfFile(const boost::filesystem::path &path);
void indexExeFile();
void indexKeyBifFiles();
void indexOverrideDirectory();
void indexTalkTable();
void indexTexturePacks();
void indexTalkTable();
void indexAudioFiles();
void indexLipModFiles();
void indexExeFile();
void indexOverrideDirectory();
void indexErfFile(const boost::filesystem::path &path);
void indexTransientErfFile(const boost::filesystem::path &path);
void indexRimFile(const boost::filesystem::path &path);
void indexTransientRimFile(const boost::filesystem::path &path);
void indexDirectory(const boost::filesystem::path &path);
void invalidateCache();
void loadModuleNames();

View file

@ -104,9 +104,7 @@ void GffTool::toJSON(const fs::path &path, const fs::path &destPath) {
}
bool GffTool::supports(Operation operation, const fs::path &target) const {
return
!fs::is_directory(target) &&
operation == Operation::ToJSON;
return !fs::is_directory(target) && operation == Operation::ToJSON;
}
} // namespace tools