mirror of
https://github.com/overte-org/overte.git
synced 2025-04-11 01:42:11 +02:00
Merge branch 'master' of github.com:highfidelity/hifi into head-controller
This commit is contained in:
commit
48ee546545
18 changed files with 847 additions and 372 deletions
|
@ -35,35 +35,35 @@
|
|||
{ "from": "Vive.RightApplicationMenu", "to": "Standard.RightSecondaryThumb" },
|
||||
|
||||
{ "from": "Vive.LeftHand", "to": "Standard.LeftHand", "when": [ "Application.InHMD" ] },
|
||||
{ "from": "Vive.RightHand", "to": "Standard.RightHand", "when": [ "Application.InHMD" ] },
|
||||
{ "from": "Vive.RightHand", "to": "Standard.RightHand", "when": [ "Application.InHMD" ] },
|
||||
|
||||
{
|
||||
"from": "Vive.LeftFoot", "to" : "Standard.LeftFoot",
|
||||
"filters" : [{"type" : "lowVelocity", "rotation" : 1.0, "translation": 1.0}],
|
||||
"when": [ "Application.InHMD"]
|
||||
"when": [ "Application.InHMD" ]
|
||||
},
|
||||
|
||||
{
|
||||
"from": "Vive.RightFoot", "to" : "Standard.RightFoot",
|
||||
"filters" : [{"type" : "lowVelocity", "rotation" : 1.0, "translation": 1.0}],
|
||||
"when": [ "Application.InHMD"]
|
||||
"when": [ "Application.InHMD" ]
|
||||
},
|
||||
|
||||
{
|
||||
"from": "Vive.Hips", "to" : "Standard.Hips",
|
||||
"filters" : [{"type" : "lowVelocity", "rotation" : 0.01, "translation": 0.01}],
|
||||
"when": [ "Application.InHMD"]
|
||||
"when": [ "Application.InHMD" ]
|
||||
},
|
||||
|
||||
{
|
||||
"from": "Vive.Spine2", "to" : "Standard.Spine2",
|
||||
"filters" : [{"type" : "lowVelocity", "rotation" : 0.01, "translation": 0.01}],
|
||||
"when": [ "Application.InHMD"]
|
||||
"when": [ "Application.InHMD" ]
|
||||
},
|
||||
|
||||
{ "from": "Vive.Head", "to" : "Standard.Head", "when" : [ "Application.InHMD"] },
|
||||
{ "from": "Vive.Head", "to" : "Standard.Head", "when": [ "Application.InHMD" ] },
|
||||
|
||||
{ "from": "Vive.RightArm", "to" : "Standard.RightArm", "when" : [ "Application.InHMD"] },
|
||||
{ "from": "Vive.LeftArm", "to" : "Standard.LeftArm", "when" : [ "Application.InHMD"] }
|
||||
{ "from": "Vive.RightArm", "to" : "Standard.RightArm", "when": [ "Application.InHMD" ] },
|
||||
{ "from": "Vive.LeftArm", "to" : "Standard.LeftArm", "when": [ "Application.InHMD" ] }
|
||||
]
|
||||
}
|
||||
|
|
|
@ -136,7 +136,7 @@ Item {
|
|||
for (var i = 0; i < sections.length; i++) {
|
||||
totalHeight += sections[i].height + sections[i].getPreferencesHeight();
|
||||
}
|
||||
var bottomPadding = 100;
|
||||
var bottomPadding = 170;
|
||||
return (totalHeight + bottomPadding);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,8 +42,6 @@ enum class Action {
|
|||
|
||||
LEFT_HAND = NUM_COMBINED_AXES,
|
||||
RIGHT_HAND,
|
||||
LEFT_ARM,
|
||||
RIGHT_ARM,
|
||||
LEFT_FOOT,
|
||||
RIGHT_FOOT,
|
||||
HIPS,
|
||||
|
@ -103,6 +101,8 @@ enum class Action {
|
|||
// Bisected aliases for TRANSLATE_CAMERA_Z
|
||||
BOOM_IN,
|
||||
BOOM_OUT,
|
||||
LEFT_ARM,
|
||||
RIGHT_ARM,
|
||||
|
||||
|
||||
NUM_ACTIONS,
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
#include "InputRecorder.h"
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonDocument>
|
||||
#include <QFile>
|
||||
#include <QDir>
|
||||
|
@ -19,9 +18,12 @@
|
|||
#include <QByteArray>
|
||||
#include <QStandardPaths>
|
||||
#include <PathUtils.h>
|
||||
#include <QUrl>
|
||||
|
||||
#include <BuildInfo.h>
|
||||
#include <GLMHelpers.h>
|
||||
#include <DependencyManager.h>
|
||||
#include "UserInputMapper.h"
|
||||
|
||||
QString SAVE_DIRECTORY = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/" + BuildInfo::MODIFIED_ORGANIZATION + "/" + BuildInfo::INTERFACE_NAME + "/hifi-input-recordings/";
|
||||
QString FILE_PREFIX_NAME = "input-recording-";
|
||||
|
@ -91,7 +93,7 @@ namespace controller {
|
|||
}
|
||||
|
||||
|
||||
void exportToFile(QJsonObject& object) {
|
||||
void exportToFile(const QJsonObject& object) {
|
||||
if (!QDir(SAVE_DIRECTORY).exists()) {
|
||||
QDir().mkdir(SAVE_DIRECTORY);
|
||||
}
|
||||
|
@ -108,6 +110,7 @@ namespace controller {
|
|||
QJsonDocument saveData(object);
|
||||
QByteArray compressedData = qCompress(saveData.toJson(QJsonDocument::Compact));
|
||||
saveFile.write(compressedData);
|
||||
saveFile.close();
|
||||
}
|
||||
|
||||
QJsonObject openFile(const QString& file, bool& status) {
|
||||
|
@ -122,6 +125,7 @@ namespace controller {
|
|||
QJsonDocument jsonDoc = QJsonDocument::fromJson(compressedData);
|
||||
object = jsonDoc.object();
|
||||
status = true;
|
||||
openFile.close();
|
||||
return object;
|
||||
}
|
||||
|
||||
|
@ -146,31 +150,44 @@ namespace controller {
|
|||
_actionStateList.clear();
|
||||
}
|
||||
|
||||
void InputRecorder::saveRecording() {
|
||||
QJsonObject InputRecorder::recordDataToJson() {
|
||||
QJsonObject data;
|
||||
data["frameCount"] = _framesRecorded;
|
||||
|
||||
data["version"] = "1.0";
|
||||
|
||||
QJsonArray actionArrayList;
|
||||
QJsonArray poseArrayList;
|
||||
for(const ActionStates actionState: _actionStateList) {
|
||||
QJsonArray actionArray;
|
||||
for (const float value: actionState) {
|
||||
actionArray.append(value);
|
||||
for (const auto action: actionState) {
|
||||
QJsonObject actionJson;
|
||||
actionJson["name"] = action.first;
|
||||
actionJson["value"] = action.second;
|
||||
actionArray.append(actionJson);
|
||||
}
|
||||
actionArrayList.append(actionArray);
|
||||
}
|
||||
|
||||
for (const PoseStates poseState: _poseStateList) {
|
||||
QJsonArray poseArray;
|
||||
for (const Pose pose: poseState) {
|
||||
poseArray.append(poseToJsonObject(pose));
|
||||
for (const auto pose: poseState) {
|
||||
QJsonObject poseJson;
|
||||
poseJson["name"] = pose.first;
|
||||
poseJson["pose"] = poseToJsonObject(pose.second);
|
||||
poseArray.append(poseJson);
|
||||
}
|
||||
poseArrayList.append(poseArray);
|
||||
}
|
||||
|
||||
data["actionList"] = actionArrayList;
|
||||
data["poseList"] = poseArrayList;
|
||||
exportToFile(data);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
void InputRecorder::saveRecording() {
|
||||
QJsonObject jsonData = recordDataToJson();
|
||||
exportToFile(jsonData);
|
||||
}
|
||||
|
||||
void InputRecorder::loadRecording(const QString& path) {
|
||||
|
@ -181,8 +198,8 @@ namespace controller {
|
|||
resetFrame();
|
||||
_poseStateList.clear();
|
||||
_actionStateList.clear();
|
||||
QString filePath = path;
|
||||
filePath.remove(0,8);
|
||||
QUrl urlPath(path);
|
||||
QString filePath = urlPath.toLocalFile();
|
||||
QFileInfo info(filePath);
|
||||
QString extension = info.suffix();
|
||||
if (extension != "gz") {
|
||||
|
@ -190,8 +207,9 @@ namespace controller {
|
|||
return;
|
||||
}
|
||||
bool success = false;
|
||||
QJsonObject data = openFile(info.absoluteFilePath(), success);
|
||||
if (success) {
|
||||
QJsonObject data = openFile(filePath, success);
|
||||
auto keyValue = data.find("version");
|
||||
if (success && keyValue != data.end()) {
|
||||
_framesRecorded = data["frameCount"].toInt();
|
||||
QJsonArray actionArrayList = data["actionList"].toArray();
|
||||
QJsonArray poseArrayList = data["poseList"].toArray();
|
||||
|
@ -199,30 +217,58 @@ namespace controller {
|
|||
for (int actionIndex = 0; actionIndex < actionArrayList.size(); actionIndex++) {
|
||||
QJsonArray actionState = actionArrayList[actionIndex].toArray();
|
||||
for (int index = 0; index < actionState.size(); index++) {
|
||||
_currentFrameActions[index] = actionState[index].toDouble();
|
||||
QJsonObject actionObject = actionState[index].toObject();
|
||||
_currentFrameActions[actionObject["name"].toString()] = actionObject["value"].toDouble();
|
||||
}
|
||||
_actionStateList.push_back(_currentFrameActions);
|
||||
_currentFrameActions = ActionStates(toInt(Action::NUM_ACTIONS));
|
||||
_currentFrameActions.clear();
|
||||
}
|
||||
|
||||
for (int poseIndex = 0; poseIndex < poseArrayList.size(); poseIndex++) {
|
||||
QJsonArray poseState = poseArrayList[poseIndex].toArray();
|
||||
for (int index = 0; index < poseState.size(); index++) {
|
||||
_currentFramePoses[index] = jsonObjectToPose(poseState[index].toObject());
|
||||
QJsonObject poseObject = poseState[index].toObject();
|
||||
_currentFramePoses[poseObject["name"].toString()] = jsonObjectToPose(poseObject["pose"].toObject());
|
||||
}
|
||||
_poseStateList.push_back(_currentFramePoses);
|
||||
_currentFramePoses = PoseStates(toInt(Action::NUM_ACTIONS));
|
||||
_currentFramePoses.clear();
|
||||
}
|
||||
} else if (success) {
|
||||
//convert recording to new reacording standard and rewrite file
|
||||
auto userInputMapper = DependencyManager::get<UserInputMapper>();
|
||||
_framesRecorded = data["frameCount"].toInt();
|
||||
QJsonArray actionArrayList = data["actionList"].toArray();
|
||||
QJsonArray poseArrayList = data["poseList"].toArray();
|
||||
|
||||
for (int actionIndex = 0; actionIndex < actionArrayList.size(); actionIndex++) {
|
||||
QJsonArray actionState = actionArrayList[actionIndex].toArray();
|
||||
for (int index = 0; index < actionState.size(); index++) {
|
||||
QString actionName = userInputMapper->getActionName(Action(index));
|
||||
_currentFrameActions[actionName] = actionState[index].toDouble();
|
||||
}
|
||||
_actionStateList.push_back(_currentFrameActions);
|
||||
_currentFrameActions.clear();
|
||||
}
|
||||
|
||||
for (int poseIndex = 0; poseIndex < poseArrayList.size(); poseIndex++) {
|
||||
QJsonArray poseState = poseArrayList[poseIndex].toArray();
|
||||
for (int index = 0; index < poseState.size(); index++) {
|
||||
QString actionName = userInputMapper->getActionName(Action(index));
|
||||
_currentFramePoses[actionName] = jsonObjectToPose(poseState[index].toObject());
|
||||
}
|
||||
_poseStateList.push_back(_currentFramePoses);
|
||||
_currentFramePoses.clear();
|
||||
}
|
||||
}
|
||||
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
|
||||
void InputRecorder::stopRecording() {
|
||||
_recording = false;
|
||||
_framesRecorded = (int)_actionStateList.size();
|
||||
}
|
||||
|
||||
|
||||
void InputRecorder::startPlayback() {
|
||||
_playback = true;
|
||||
_recording = false;
|
||||
|
@ -234,41 +280,36 @@ namespace controller {
|
|||
_playCount = 0;
|
||||
}
|
||||
|
||||
void InputRecorder::setActionState(controller::Action action, float value) {
|
||||
void InputRecorder::setActionState(const QString& action, float value) {
|
||||
if (_recording) {
|
||||
_currentFrameActions[toInt(action)] += value;
|
||||
_currentFrameActions[action] += value;
|
||||
}
|
||||
}
|
||||
|
||||
void InputRecorder::setActionState(controller::Action action, const controller::Pose pose) {
|
||||
void InputRecorder::setActionState(const QString& action, const controller::Pose& pose) {
|
||||
if (_recording) {
|
||||
_currentFramePoses[toInt(action)] = pose;
|
||||
_currentFramePoses[action] = pose;
|
||||
}
|
||||
}
|
||||
|
||||
void InputRecorder::resetFrame() {
|
||||
if (_recording) {
|
||||
for(auto& channel : _currentFramePoses) {
|
||||
channel = Pose();
|
||||
}
|
||||
|
||||
for(auto& channel : _currentFrameActions) {
|
||||
channel = 0.0f;
|
||||
}
|
||||
_currentFramePoses.clear();
|
||||
_currentFrameActions.clear();
|
||||
}
|
||||
}
|
||||
|
||||
float InputRecorder::getActionState(controller::Action action) {
|
||||
float InputRecorder::getActionState(const QString& action) {
|
||||
if (_actionStateList.size() > 0 ) {
|
||||
return _actionStateList[_playCount][toInt(action)];
|
||||
return _actionStateList[_playCount][action];
|
||||
}
|
||||
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
controller::Pose InputRecorder::getPoseState(controller::Action action) {
|
||||
controller::Pose InputRecorder::getPoseState(const QString& action) {
|
||||
if (_poseStateList.size() > 0) {
|
||||
return _poseStateList[_playCount][toInt(action)];
|
||||
return _poseStateList[_playCount][action];
|
||||
}
|
||||
|
||||
return Pose();
|
||||
|
|
|
@ -12,8 +12,10 @@
|
|||
#include <mutex>
|
||||
#include <atomic>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
#include <QString>
|
||||
#include <QJsonObject>
|
||||
|
||||
#include "Pose.h"
|
||||
#include "Actions.h"
|
||||
|
@ -21,8 +23,8 @@
|
|||
namespace controller {
|
||||
class InputRecorder {
|
||||
public:
|
||||
using PoseStates = std::vector<Pose>;
|
||||
using ActionStates = std::vector<float>;
|
||||
using PoseStates = std::map<QString, Pose>;
|
||||
using ActionStates = std::map<QString, float>;
|
||||
|
||||
InputRecorder();
|
||||
~InputRecorder();
|
||||
|
@ -40,20 +42,21 @@ namespace controller {
|
|||
void resetFrame();
|
||||
bool isRecording() { return _recording; }
|
||||
bool isPlayingback() { return (_playback && !_loading); }
|
||||
void setActionState(controller::Action action, float value);
|
||||
void setActionState(controller::Action action, const controller::Pose pose);
|
||||
float getActionState(controller::Action action);
|
||||
controller::Pose getPoseState(controller::Action action);
|
||||
void setActionState(const QString& action, float value);
|
||||
void setActionState(const QString& action, const controller::Pose& pose);
|
||||
float getActionState(const QString& action);
|
||||
controller::Pose getPoseState(const QString& action);
|
||||
QString getSaveDirectory();
|
||||
void frameTick();
|
||||
private:
|
||||
QJsonObject recordDataToJson();
|
||||
bool _recording { false };
|
||||
bool _playback { false };
|
||||
bool _loading { false };
|
||||
std::vector<PoseStates> _poseStateList = std::vector<PoseStates>();
|
||||
std::vector<ActionStates> _actionStateList = std::vector<ActionStates>();
|
||||
PoseStates _currentFramePoses = PoseStates(toInt(Action::NUM_ACTIONS));
|
||||
ActionStates _currentFrameActions = ActionStates(toInt(Action::NUM_ACTIONS));
|
||||
PoseStates _currentFramePoses;
|
||||
ActionStates _currentFrameActions;
|
||||
|
||||
int _framesRecorded { 0 };
|
||||
int _playCount { 0 };
|
||||
|
|
|
@ -17,31 +17,33 @@ using namespace controller;
|
|||
|
||||
void ActionEndpoint::apply(float newValue, const Pointer& source) {
|
||||
InputRecorder* inputRecorder = InputRecorder::getInstance();
|
||||
auto userInputMapper = DependencyManager::get<UserInputMapper>();
|
||||
QString actionName = userInputMapper->getActionName(Action(_input.getChannel()));
|
||||
if(inputRecorder->isPlayingback()) {
|
||||
newValue = inputRecorder->getActionState(Action(_input.getChannel()));
|
||||
newValue = inputRecorder->getActionState(actionName);
|
||||
}
|
||||
|
||||
_currentValue += newValue;
|
||||
if (_input != Input::INVALID_INPUT) {
|
||||
auto userInputMapper = DependencyManager::get<UserInputMapper>();
|
||||
userInputMapper->deltaActionState(Action(_input.getChannel()), newValue);
|
||||
}
|
||||
inputRecorder->setActionState(Action(_input.getChannel()), newValue);
|
||||
inputRecorder->setActionState(actionName, newValue);
|
||||
}
|
||||
|
||||
void ActionEndpoint::apply(const Pose& value, const Pointer& source) {
|
||||
_currentPose = value;
|
||||
InputRecorder* inputRecorder = InputRecorder::getInstance();
|
||||
inputRecorder->setActionState(Action(_input.getChannel()), _currentPose);
|
||||
auto userInputMapper = DependencyManager::get<UserInputMapper>();
|
||||
QString actionName = userInputMapper->getActionName(Action(_input.getChannel()));
|
||||
inputRecorder->setActionState(actionName, _currentPose);
|
||||
if (inputRecorder->isPlayingback()) {
|
||||
_currentPose = inputRecorder->getPoseState(Action(_input.getChannel()));
|
||||
_currentPose = inputRecorder->getPoseState(actionName);
|
||||
}
|
||||
|
||||
if (!_currentPose.isValid()) {
|
||||
return;
|
||||
}
|
||||
if (_input != Input::INVALID_INPUT) {
|
||||
auto userInputMapper = DependencyManager::get<UserInputMapper>();
|
||||
userInputMapper->setActionState(Action(_input.getChannel()), _currentPose);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -211,6 +211,8 @@ namespace khronos {
|
|||
|
||||
template <uint32_t ALIGNMENT>
|
||||
inline uint32_t evalAlignedCompressedBlockCount(uint32_t value) {
|
||||
enum { val = ALIGNMENT && !(ALIGNMENT & (ALIGNMENT - 1)) };
|
||||
static_assert(val, "template parameter ALIGNMENT must be a power of 2");
|
||||
// FIXME add static assert that ALIGNMENT is a power of 2
|
||||
static uint32_t ALIGNMENT_REMAINDER = ALIGNMENT - 1;
|
||||
return (value + ALIGNMENT_REMAINDER) / ALIGNMENT;
|
||||
|
|
|
@ -11,52 +11,81 @@
|
|||
|
||||
#include "FileCache.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cassert>
|
||||
#include <fstream>
|
||||
#include <unordered_set>
|
||||
|
||||
#include <QDir>
|
||||
#include <QSaveFile>
|
||||
#include <unordered_set>
|
||||
#include <queue>
|
||||
#include <cassert>
|
||||
|
||||
#include <QtCore/QDateTime>
|
||||
#include <QtCore/QDir>
|
||||
#include <QtCore/QSaveFile>
|
||||
#include <QtCore/QStorageInfo>
|
||||
|
||||
#include <PathUtils.h>
|
||||
#include <NumericalConstants.h>
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include <sys/utime.h>
|
||||
#else
|
||||
#include <utime.h>
|
||||
#endif
|
||||
|
||||
#ifdef NDEBUG
|
||||
Q_LOGGING_CATEGORY(file_cache, "hifi.file_cache", QtWarningMsg)
|
||||
|
||||
#else
|
||||
Q_LOGGING_CATEGORY(file_cache, "hifi.file_cache")
|
||||
#endif
|
||||
using namespace cache;
|
||||
|
||||
static const std::string MANIFEST_NAME = "manifest";
|
||||
static const char DIR_SEP = '/';
|
||||
static const char EXT_SEP = '.';
|
||||
|
||||
static const size_t BYTES_PER_MEGABYTES = 1024 * 1024;
|
||||
static const size_t BYTES_PER_GIGABYTES = 1024 * BYTES_PER_MEGABYTES;
|
||||
const size_t FileCache::DEFAULT_UNUSED_MAX_SIZE = 5 * BYTES_PER_GIGABYTES; // 5GB
|
||||
const size_t FileCache::MAX_UNUSED_MAX_SIZE = 100 * BYTES_PER_GIGABYTES; // 100GB
|
||||
const size_t FileCache::DEFAULT_OFFLINE_MAX_SIZE = 2 * BYTES_PER_GIGABYTES; // 2GB
|
||||
const size_t FileCache::DEFAULT_MAX_SIZE { GB_TO_BYTES(5) };
|
||||
const size_t FileCache::MAX_MAX_SIZE { GB_TO_BYTES(100) };
|
||||
const size_t FileCache::DEFAULT_MIN_FREE_STORAGE_SPACE { GB_TO_BYTES(1) };
|
||||
|
||||
void FileCache::setUnusedFileCacheSize(size_t unusedFilesMaxSize) {
|
||||
_unusedFilesMaxSize = std::min(unusedFilesMaxSize, MAX_UNUSED_MAX_SIZE);
|
||||
reserve(0);
|
||||
|
||||
std::string getCacheName(const std::string& dirname_str) {
|
||||
QString dirname { dirname_str.c_str() };
|
||||
QDir dir(dirname);
|
||||
if (!dir.isAbsolute()) {
|
||||
return dirname_str;
|
||||
}
|
||||
return dir.dirName().toStdString();
|
||||
}
|
||||
|
||||
std::string getCachePath(const std::string& dirname_str) {
|
||||
QString dirname { dirname_str.c_str() };
|
||||
QDir dir(dirname);
|
||||
if (dir.isAbsolute()) {
|
||||
return dirname_str;
|
||||
}
|
||||
return PathUtils::getAppLocalDataFilePath(dirname).toStdString();
|
||||
}
|
||||
|
||||
void FileCache::setMinFreeSize(size_t size) {
|
||||
_minFreeSpaceSize = size;
|
||||
clean();
|
||||
emit dirty();
|
||||
}
|
||||
|
||||
void FileCache::setOfflineFileCacheSize(size_t offlineFilesMaxSize) {
|
||||
_offlineFilesMaxSize = std::min(offlineFilesMaxSize, MAX_UNUSED_MAX_SIZE);
|
||||
void FileCache::setMaxSize(size_t maxSize) {
|
||||
_maxSize = std::min(maxSize, MAX_MAX_SIZE);
|
||||
clean();
|
||||
emit dirty();
|
||||
}
|
||||
|
||||
FileCache::FileCache(const std::string& dirname, const std::string& ext, QObject* parent) :
|
||||
QObject(parent),
|
||||
_ext(ext),
|
||||
_dirname(dirname),
|
||||
_dirpath(PathUtils::getAppLocalDataFilePath(dirname.c_str()).toStdString()) {}
|
||||
_dirname(getCacheName(dirname)),
|
||||
_dirpath(getCachePath(dirname)) {
|
||||
}
|
||||
|
||||
FileCache::~FileCache() {
|
||||
clear();
|
||||
}
|
||||
|
||||
void fileDeleter(File* file) {
|
||||
file->deleter();
|
||||
}
|
||||
|
||||
void FileCache::initialize() {
|
||||
QDir dir(_dirpath.c_str());
|
||||
|
||||
|
@ -84,7 +113,7 @@ void FileCache::initialize() {
|
|||
}
|
||||
|
||||
FilePointer FileCache::addFile(Metadata&& metadata, const std::string& filepath) {
|
||||
FilePointer file(createFile(std::move(metadata), filepath).release(), &fileDeleter);
|
||||
FilePointer file(createFile(std::move(metadata), filepath).release(), std::mem_fn(&File::deleter));
|
||||
if (file) {
|
||||
_numTotalFiles += 1;
|
||||
_totalFilesSize += file->getLength();
|
||||
|
@ -141,6 +170,7 @@ FilePointer FileCache::getFile(const Key& key) {
|
|||
if (it != _files.cend()) {
|
||||
file = it->second.lock();
|
||||
if (file) {
|
||||
file->touch();
|
||||
// if it exists, it is active - remove it from the cache
|
||||
removeUnusedFile(file);
|
||||
qCDebug(file_cache, "[%s] Found %s", _dirname.c_str(), key.c_str());
|
||||
|
@ -155,44 +185,74 @@ FilePointer FileCache::getFile(const Key& key) {
|
|||
}
|
||||
|
||||
std::string FileCache::getFilepath(const Key& key) {
|
||||
return _dirpath + '/' + key + '.' + _ext;
|
||||
return _dirpath + DIR_SEP + key + EXT_SEP + _ext;
|
||||
}
|
||||
|
||||
void FileCache::addUnusedFile(const FilePointer file) {
|
||||
void FileCache::addUnusedFile(const FilePointer& file) {
|
||||
{
|
||||
Lock lock(_filesMutex);
|
||||
_files[file->getKey()] = file;
|
||||
}
|
||||
|
||||
reserve(file->getLength());
|
||||
file->_LRUKey = ++_lastLRUKey;
|
||||
|
||||
{
|
||||
Lock lock(_unusedFilesMutex);
|
||||
_unusedFiles.insert({ file->_LRUKey, file });
|
||||
_unusedFiles.insert(file);
|
||||
_numUnusedFiles += 1;
|
||||
_unusedFilesSize += file->getLength();
|
||||
}
|
||||
clean();
|
||||
|
||||
emit dirty();
|
||||
}
|
||||
|
||||
void FileCache::removeUnusedFile(const FilePointer file) {
|
||||
void FileCache::removeUnusedFile(const FilePointer& file) {
|
||||
Lock lock(_unusedFilesMutex);
|
||||
const auto it = _unusedFiles.find(file->_LRUKey);
|
||||
if (it != _unusedFiles.cend()) {
|
||||
_unusedFiles.erase(it);
|
||||
if (_unusedFiles.erase(file)) {
|
||||
_numUnusedFiles -= 1;
|
||||
_unusedFilesSize -= file->getLength();
|
||||
}
|
||||
}
|
||||
|
||||
void FileCache::reserve(size_t length) {
|
||||
size_t FileCache::getOverbudgetAmount() const {
|
||||
size_t result = 0;
|
||||
|
||||
size_t currentFreeSpace = QStorageInfo(_dirpath.c_str()).bytesFree();
|
||||
if (_minFreeSpaceSize > currentFreeSpace) {
|
||||
result = _minFreeSpaceSize - currentFreeSpace;
|
||||
}
|
||||
|
||||
if (_totalFilesSize > _maxSize) {
|
||||
result = std::max(_totalFilesSize - _maxSize, result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
namespace cache {
|
||||
struct FilePointerComparator {
|
||||
bool operator()(const FilePointer& a, const FilePointer& b) {
|
||||
return a->_modified > b->_modified;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
void FileCache::clean() {
|
||||
size_t overbudgetAmount = getOverbudgetAmount();
|
||||
|
||||
// Avoid sorting the unused files by LRU if we're not over budget / under free space
|
||||
if (0 == overbudgetAmount) {
|
||||
return;
|
||||
}
|
||||
|
||||
Lock unusedLock(_unusedFilesMutex);
|
||||
while (!_unusedFiles.empty() &&
|
||||
_unusedFilesSize + length > _unusedFilesMaxSize) {
|
||||
auto it = _unusedFiles.begin();
|
||||
auto file = it->second;
|
||||
using Queue = std::priority_queue<FilePointer, std::vector<FilePointer>, FilePointerComparator>;
|
||||
Queue queue;
|
||||
for (const auto& file : _unusedFiles) {
|
||||
queue.push(file);
|
||||
}
|
||||
while (!queue.empty() && overbudgetAmount > 0) {
|
||||
auto file = queue.top();
|
||||
queue.pop();
|
||||
auto length = file->getLength();
|
||||
|
||||
unusedLock.unlock();
|
||||
|
@ -203,34 +263,32 @@ void FileCache::reserve(size_t length) {
|
|||
}
|
||||
unusedLock.lock();
|
||||
|
||||
_unusedFiles.erase(it);
|
||||
_unusedFiles.erase(file);
|
||||
_numTotalFiles -= 1;
|
||||
_numUnusedFiles -= 1;
|
||||
_totalFilesSize -= length;
|
||||
_unusedFilesSize -= length;
|
||||
overbudgetAmount -= std::min(length, overbudgetAmount);
|
||||
}
|
||||
}
|
||||
|
||||
void FileCache::clear() {
|
||||
Lock unusedFilesLock(_unusedFilesMutex);
|
||||
for (const auto& pair : _unusedFiles) {
|
||||
auto& file = pair.second;
|
||||
file->_cache = nullptr;
|
||||
// Eliminate any overbudget files
|
||||
clean();
|
||||
|
||||
if (_totalFilesSize > _offlineFilesMaxSize) {
|
||||
_totalFilesSize -= file->getLength();
|
||||
} else {
|
||||
file->_shouldPersist = true;
|
||||
qCDebug(file_cache, "[%s] Persisting %s", _dirname.c_str(), file->getKey().c_str());
|
||||
}
|
||||
// Mark everything remaining as persisted
|
||||
Lock unusedFilesLock(_unusedFilesMutex);
|
||||
for (auto& file : _unusedFiles) {
|
||||
file->_shouldPersist = true;
|
||||
file->_cache = nullptr;
|
||||
qCDebug(file_cache, "[%s] Persisting %s", _dirname.c_str(), file->getKey().c_str());
|
||||
}
|
||||
_unusedFiles.clear();
|
||||
}
|
||||
|
||||
void File::deleter() {
|
||||
if (_cache) {
|
||||
FilePointer self(this, &fileDeleter);
|
||||
_cache->addUnusedFile(self);
|
||||
_cache->addUnusedFile(FilePointer(this, std::mem_fn(&File::deleter)));
|
||||
} else {
|
||||
deleteLater();
|
||||
}
|
||||
|
@ -239,7 +297,9 @@ void File::deleter() {
|
|||
File::File(Metadata&& metadata, const std::string& filepath) :
|
||||
_key(std::move(metadata.key)),
|
||||
_length(metadata.length),
|
||||
_filepath(filepath) {}
|
||||
_filepath(filepath),
|
||||
_modified(QFileInfo(_filepath.c_str()).lastRead().toMSecsSinceEpoch()) {
|
||||
}
|
||||
|
||||
File::~File() {
|
||||
QFile file(getFilepath().c_str());
|
||||
|
@ -248,3 +308,8 @@ File::~File() {
|
|||
file.remove();
|
||||
}
|
||||
}
|
||||
|
||||
void File::touch() {
|
||||
utime(_filepath.c_str(), nullptr);
|
||||
_modified = std::max<int64_t>(QFileInfo(_filepath.c_str()).lastRead().toMSecsSinceEpoch(), _modified);
|
||||
}
|
|
@ -14,6 +14,7 @@
|
|||
#include <memory>
|
||||
#include <cstddef>
|
||||
#include <map>
|
||||
#include <unordered_set>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
@ -35,24 +36,28 @@ class FileCache : public QObject {
|
|||
Q_PROPERTY(size_t sizeTotal READ getSizeTotalFiles NOTIFY dirty)
|
||||
Q_PROPERTY(size_t sizeCached READ getSizeCachedFiles NOTIFY dirty)
|
||||
|
||||
static const size_t DEFAULT_UNUSED_MAX_SIZE;
|
||||
static const size_t MAX_UNUSED_MAX_SIZE;
|
||||
static const size_t DEFAULT_OFFLINE_MAX_SIZE;
|
||||
static const size_t DEFAULT_MAX_SIZE;
|
||||
static const size_t MAX_MAX_SIZE;
|
||||
static const size_t DEFAULT_MIN_FREE_STORAGE_SPACE;
|
||||
|
||||
public:
|
||||
// You can initialize the FileCache with a directory name (ex.: "temp_jpgs") that will be relative to the application local data, OR with a full path
|
||||
// The file cache will ignore any file that doesn't match the extension provided
|
||||
FileCache(const std::string& dirname, const std::string& ext, QObject* parent = nullptr);
|
||||
virtual ~FileCache();
|
||||
|
||||
size_t getNumTotalFiles() const { return _numTotalFiles; }
|
||||
size_t getNumCachedFiles() const { return _numUnusedFiles; }
|
||||
size_t getSizeTotalFiles() const { return _totalFilesSize; }
|
||||
size_t getSizeCachedFiles() const { return _unusedFilesSize; }
|
||||
|
||||
void setUnusedFileCacheSize(size_t unusedFilesMaxSize);
|
||||
size_t getUnusedFileCacheSize() const { return _unusedFilesSize; }
|
||||
// Set the maximum amount of disk space to use on disk
|
||||
void setMaxSize(size_t maxCacheSize);
|
||||
|
||||
void setOfflineFileCacheSize(size_t offlineFilesMaxSize);
|
||||
|
||||
// initialize FileCache with a directory name (not a path, ex.: "temp_jpgs") and an ext (ex.: "jpg")
|
||||
FileCache(const std::string& dirname, const std::string& ext, QObject* parent = nullptr);
|
||||
virtual ~FileCache();
|
||||
// Set the minumum amount of free disk space to retain. This supercedes the max size,
|
||||
// so if the cache is consuming all but 500 MB of the drive, unused entries will be ejected
|
||||
// to free up more space, regardless of the cache max size
|
||||
void setMinFreeSize(size_t size);
|
||||
|
||||
using Key = std::string;
|
||||
struct Metadata {
|
||||
|
@ -76,10 +81,11 @@ public:
|
|||
signals:
|
||||
void dirty();
|
||||
|
||||
protected:
|
||||
public:
|
||||
/// must be called after construction to create the cache on the fs and restore persisted files
|
||||
void initialize();
|
||||
|
||||
// Add file to the cache and return the cache entry.
|
||||
FilePointer writeFile(const char* data, Metadata&& metadata, bool overwrite = false);
|
||||
FilePointer getFile(const Key& key);
|
||||
|
||||
|
@ -95,11 +101,17 @@ private:
|
|||
std::string getFilepath(const Key& key);
|
||||
|
||||
FilePointer addFile(Metadata&& metadata, const std::string& filepath);
|
||||
void addUnusedFile(const FilePointer file);
|
||||
void removeUnusedFile(const FilePointer file);
|
||||
void reserve(size_t length);
|
||||
void addUnusedFile(const FilePointer& file);
|
||||
void removeUnusedFile(const FilePointer& file);
|
||||
void clean();
|
||||
void clear();
|
||||
|
||||
size_t getOverbudgetAmount() const;
|
||||
|
||||
// FIXME it might be desirable to have the min free space variable be static so it can be
|
||||
// shared among multiple instances of FileCache
|
||||
std::atomic<size_t> _minFreeSpaceSize { DEFAULT_MIN_FREE_STORAGE_SPACE };
|
||||
std::atomic<size_t> _maxSize { DEFAULT_MAX_SIZE };
|
||||
std::atomic<size_t> _numTotalFiles { 0 };
|
||||
std::atomic<size_t> _numUnusedFiles { 0 };
|
||||
std::atomic<size_t> _totalFilesSize { 0 };
|
||||
|
@ -113,12 +125,8 @@ private:
|
|||
std::unordered_map<Key, std::weak_ptr<File>> _files;
|
||||
Mutex _filesMutex;
|
||||
|
||||
std::map<int, FilePointer> _unusedFiles;
|
||||
std::unordered_set<FilePointer> _unusedFiles;
|
||||
Mutex _unusedFilesMutex;
|
||||
size_t _unusedFilesMaxSize { DEFAULT_UNUSED_MAX_SIZE };
|
||||
int _lastLRUKey { 0 };
|
||||
|
||||
size_t _offlineFilesMaxSize { DEFAULT_OFFLINE_MAX_SIZE };
|
||||
};
|
||||
|
||||
class File : public QObject {
|
||||
|
@ -142,13 +150,15 @@ protected:
|
|||
|
||||
private:
|
||||
friend class FileCache;
|
||||
friend struct FilePointerComparator;
|
||||
|
||||
const Key _key;
|
||||
const size_t _length;
|
||||
const std::string _filepath;
|
||||
|
||||
FileCache* _cache;
|
||||
int _LRUKey { 0 };
|
||||
void touch();
|
||||
FileCache* _cache { nullptr };
|
||||
int64_t _modified { 0 };
|
||||
|
||||
bool _shouldPersist { false };
|
||||
};
|
||||
|
|
|
@ -50,10 +50,13 @@ const int KILO_PER_MEGA = 1000;
|
|||
|
||||
#define KB_TO_BYTES_SHIFT 10
|
||||
#define MB_TO_BYTES_SHIFT 20
|
||||
#define GB_TO_BYTES_SHIFT 30
|
||||
|
||||
#define GB_TO_BYTES(X) ((size_t)(X) << GB_TO_BYTES_SHIFT)
|
||||
#define MB_TO_BYTES(X) ((size_t)(X) << MB_TO_BYTES_SHIFT)
|
||||
#define KB_TO_BYTES(X) ((size_t)(X) << KB_TO_BYTES_SHIFT)
|
||||
|
||||
#define BYTES_TO_GB(X) (X >> GB_TO_BYTES_SHIFT)
|
||||
#define BYTES_TO_MB(X) (X >> MB_TO_BYTES_SHIFT)
|
||||
#define BYTES_TO_KB(X) (X >> KB_TO_BYTES_SHIFT)
|
||||
|
||||
|
|
|
@ -50,11 +50,15 @@ static const char* RENDER_CONTROLLERS = "Render Hand Controllers";
|
|||
static const int MIN_PUCK_COUNT = 2;
|
||||
static const int MIN_FEET_AND_HIPS = 3;
|
||||
static const int MIN_FEET_HIPS_CHEST = 4;
|
||||
static const int MIN_FEET_HIPS_HEAD = 4;
|
||||
static const int MIN_FEET_HIPS_SHOULDERS = 5;
|
||||
static const int MIN_FEET_HIPS_CHEST_HEAD = 5;
|
||||
static const int FIRST_FOOT = 0;
|
||||
static const int SECOND_FOOT = 1;
|
||||
static const int HIP = 2;
|
||||
static const int CHEST = 3;
|
||||
static float HEAD_PUCK_Y_OFFSET = -0.0254f;
|
||||
static float HEAD_PUCK_Z_OFFSET = -0.152f;
|
||||
|
||||
const char* ViveControllerManager::NAME { "OpenVR" };
|
||||
|
||||
|
@ -80,6 +84,28 @@ static bool sortPucksXPosition(PuckPosePair firstPuck, PuckPosePair secondPuck)
|
|||
return (firstPuck.second.translation.x < secondPuck.second.translation.x);
|
||||
}
|
||||
|
||||
static bool determineFeetOrdering(const controller::Pose& poseA, const controller::Pose& poseB, glm::vec3 axis, glm::vec3 axisOrigin) {
|
||||
glm::vec3 poseAPosition = poseA.getTranslation();
|
||||
glm::vec3 poseBPosition = poseB.getTranslation();
|
||||
|
||||
glm::vec3 poseAVector = poseAPosition - axisOrigin;
|
||||
glm::vec3 poseBVector = poseBPosition - axisOrigin;
|
||||
|
||||
float poseAProjection = glm::dot(poseAVector, axis);
|
||||
float poseBProjection = glm::dot(poseBVector, axis);
|
||||
return (poseAProjection > poseBProjection);
|
||||
}
|
||||
|
||||
static glm::vec3 getReferenceHeadXAxis(glm::mat4 defaultToReferenceMat, glm::mat4 defaultHead) {
|
||||
glm::mat4 finalHead = defaultToReferenceMat * defaultHead;
|
||||
return glmExtractRotation(finalHead) * Vectors::UNIT_X;
|
||||
}
|
||||
|
||||
static glm::vec3 getReferenceHeadPosition(glm::mat4 defaultToReferenceMat, glm::mat4 defaultHead) {
|
||||
glm::mat4 finalHead = defaultToReferenceMat * defaultHead;
|
||||
return extractTranslation(finalHead);
|
||||
}
|
||||
|
||||
static QString deviceTrackingResultToString(vr::ETrackingResult trackingResult) {
|
||||
QString result;
|
||||
auto iterator = TRACKING_RESULT_TO_STRING.find(trackingResult);
|
||||
|
@ -180,6 +206,8 @@ ViveControllerManager::InputDevice::InputDevice(vr::IVRSystem*& system) : contro
|
|||
_configStringMap[Config::FeetAndHips] = QString("FeetAndHips");
|
||||
_configStringMap[Config::FeetHipsAndChest] = QString("FeetHipsAndChest");
|
||||
_configStringMap[Config::FeetHipsAndShoulders] = QString("FeetHipsAndShoulders");
|
||||
_configStringMap[Config::FeetHipsChestAndHead] = QString("FeetHipsChestAndHead");
|
||||
_configStringMap[Config::FeetHipsAndHead] = QString("FeetHipsAndHead");
|
||||
|
||||
if (openVrSupported()) {
|
||||
createPreferences();
|
||||
|
@ -360,6 +388,23 @@ void ViveControllerManager::InputDevice::calibrate(const controller::InputCalibr
|
|||
int firstShoulderIndex = 3;
|
||||
int secondShoulderIndex = 4;
|
||||
calibrateShoulders(defaultToReferenceMat, inputCalibration, firstShoulderIndex, secondShoulderIndex);
|
||||
} else if (_config == Config::FeetHipsAndHead && puckCount == MIN_FEET_HIPS_HEAD) {
|
||||
glm::mat4 headPuckDefaultToReferenceMat = recalculateDefaultToReferenceForHeadPuck(inputCalibration);
|
||||
glm::vec3 headXAxis = getReferenceHeadXAxis(headPuckDefaultToReferenceMat, inputCalibration.defaultHeadMat);
|
||||
glm::vec3 headPosition = getReferenceHeadPosition(headPuckDefaultToReferenceMat, inputCalibration.defaultHeadMat);
|
||||
calibrateFeet(headPuckDefaultToReferenceMat, inputCalibration, headXAxis, headPosition);
|
||||
calibrateHips(headPuckDefaultToReferenceMat, inputCalibration);
|
||||
calibrateHead(headPuckDefaultToReferenceMat, inputCalibration);
|
||||
_overrideHead = true;
|
||||
} else if (_config == Config::FeetHipsChestAndHead && puckCount == MIN_FEET_HIPS_CHEST_HEAD) {
|
||||
glm::mat4 headPuckDefaultToReferenceMat = recalculateDefaultToReferenceForHeadPuck(inputCalibration);
|
||||
glm::vec3 headXAxis = getReferenceHeadXAxis(headPuckDefaultToReferenceMat, inputCalibration.defaultHeadMat);
|
||||
glm::vec3 headPosition = getReferenceHeadPosition(headPuckDefaultToReferenceMat, inputCalibration.defaultHeadMat);
|
||||
calibrateFeet(headPuckDefaultToReferenceMat, inputCalibration, headXAxis, headPosition);
|
||||
calibrateHips(headPuckDefaultToReferenceMat, inputCalibration);
|
||||
calibrateChest(headPuckDefaultToReferenceMat, inputCalibration);
|
||||
calibrateHead(headPuckDefaultToReferenceMat, inputCalibration);
|
||||
_overrideHead = true;
|
||||
} else {
|
||||
qDebug() << "Puck Calibration: " << configToString(_config) << " Config Failed: Could not meet the minimal # of pucks";
|
||||
uncalibrate();
|
||||
|
@ -374,6 +419,7 @@ void ViveControllerManager::InputDevice::uncalibrate() {
|
|||
_pucksOffset.clear();
|
||||
_jointToPuckMap.clear();
|
||||
_calibrated = false;
|
||||
_overrideHead = false;
|
||||
}
|
||||
|
||||
void ViveControllerManager::InputDevice::updateCalibratedLimbs() {
|
||||
|
@ -383,6 +429,10 @@ void ViveControllerManager::InputDevice::updateCalibratedLimbs() {
|
|||
_poseStateMap[controller::SPINE2] = addOffsetToPuckPose(controller::SPINE2);
|
||||
_poseStateMap[controller::RIGHT_ARM] = addOffsetToPuckPose(controller::RIGHT_ARM);
|
||||
_poseStateMap[controller::LEFT_ARM] = addOffsetToPuckPose(controller::LEFT_ARM);
|
||||
|
||||
if (_overrideHead) {
|
||||
_poseStateMap[controller::HEAD] = addOffsetToPuckPose(controller::HEAD);
|
||||
}
|
||||
}
|
||||
|
||||
controller::Pose ViveControllerManager::InputDevice::addOffsetToPuckPose(int joint) const {
|
||||
|
@ -448,6 +498,43 @@ void ViveControllerManager::InputDevice::handleHandController(float deltaTime, u
|
|||
}
|
||||
}
|
||||
|
||||
glm::mat4 ViveControllerManager::InputDevice::recalculateDefaultToReferenceForHeadPuck(const controller::InputCalibrationData& inputCalibration) {
|
||||
glm::mat4 avatarToSensorMat = glm::inverse(inputCalibration.sensorToWorldMat) * inputCalibration.avatarMat;
|
||||
glm::mat4 sensorToAvatarMat = glm::inverse(inputCalibration.avatarMat) * inputCalibration.sensorToWorldMat;
|
||||
size_t headPuckIndex = _validTrackedObjects.size() - 1;
|
||||
controller::Pose headPuckPose = _validTrackedObjects[headPuckIndex].second;
|
||||
glm::mat4 headPuckAvatarMat = createMatFromQuatAndPos(headPuckPose.getRotation(), headPuckPose.getTranslation()) * Matrices::Y_180;
|
||||
glm::vec3 headPuckTranslation = extractTranslation(headPuckAvatarMat);
|
||||
glm::vec3 headPuckZAxis = cancelOutRollAndPitch(glmExtractRotation(headPuckAvatarMat)) * glm::vec3(0.0f, 0.0f, 1.0f);
|
||||
glm::vec3 worldUp = glm::vec3(0.0f, 1.0f, 0.0f);
|
||||
|
||||
// check that the head puck z axis is not parrallel to the world up
|
||||
const float EPSILON = 1.0e-4f;
|
||||
glm::vec3 zAxis = glmExtractRotation(headPuckAvatarMat) * glm::vec3(0.0f, 0.0f, 1.0f);
|
||||
if (fabsf(fabsf(glm::dot(glm::normalize(worldUp), glm::normalize(zAxis))) - 1.0f) < EPSILON) {
|
||||
headPuckZAxis = glm::vec3(1.0f, 0.0f, 0.0f);
|
||||
}
|
||||
|
||||
glm::vec3 yPrime = glm::vec3(0.0f, 1.0f, 0.0f);
|
||||
glm::vec3 xPrime = glm::normalize(glm::cross(worldUp, headPuckZAxis));
|
||||
glm::vec3 zPrime = glm::normalize(glm::cross(xPrime, yPrime));
|
||||
glm::mat4 newHeadPuck = glm::mat4(glm::vec4(xPrime, 0.0f), glm::vec4(yPrime, 0.0f),
|
||||
glm::vec4(zPrime, 0.0f), glm::vec4(headPuckTranslation, 1.0f));
|
||||
|
||||
glm::mat4 headPuckOffset = glm::mat4(glm::vec4(1.0f, 0.0f, 0.0f, 0.0f), glm::vec4(0.0f, 1.0f, 0.0f, 0.0f),
|
||||
glm::vec4(0.0f, 0.0f, 1.0f, 0.0f), glm::vec4(0.0f, HEAD_PUCK_Y_OFFSET, HEAD_PUCK_Z_OFFSET, 1.0f));
|
||||
|
||||
glm::mat4 finalHeadPuck = newHeadPuck * headPuckOffset;
|
||||
|
||||
glm::mat4 defaultHeadOffset = glm::inverse(inputCalibration.defaultCenterEyeMat) * inputCalibration.defaultHeadMat;
|
||||
|
||||
glm::mat4 currentHead = finalHeadPuck * defaultHeadOffset;
|
||||
|
||||
// calculate the defaultToRefrenceXform
|
||||
glm::mat4 defaultToReferenceMat = currentHead * glm::inverse(inputCalibration.defaultHeadMat);
|
||||
return defaultToReferenceMat;
|
||||
}
|
||||
|
||||
void ViveControllerManager::InputDevice::partitionTouchpad(int sButton, int xAxis, int yAxis, int centerPseudoButton, int xPseudoButton, int yPseudoButton) {
|
||||
// Populate the L/RS_CENTER/OUTER pseudo buttons, corresponding to a partition of the L/RS space based on the X/Y values.
|
||||
const float CENTER_DEADBAND = 0.6f;
|
||||
|
@ -635,6 +722,26 @@ void ViveControllerManager::InputDevice::calibrateFeet(glm::mat4& defaultToRefer
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
void ViveControllerManager::InputDevice::calibrateFeet(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration, glm::vec3 headXAxis, glm::vec3 headPosition) {
|
||||
auto& firstFoot = _validTrackedObjects[FIRST_FOOT];
|
||||
auto& secondFoot = _validTrackedObjects[SECOND_FOOT];
|
||||
controller::Pose& firstFootPose = firstFoot.second;
|
||||
controller::Pose& secondFootPose = secondFoot.second;
|
||||
|
||||
if (determineFeetOrdering(firstFootPose, secondFootPose, headXAxis, headPosition)) {
|
||||
_jointToPuckMap[controller::LEFT_FOOT] = firstFoot.first;
|
||||
_pucksOffset[firstFoot.first] = computeOffset(defaultToReferenceMat, inputCalibration.defaultLeftFoot, firstFootPose);
|
||||
_jointToPuckMap[controller::RIGHT_FOOT] = secondFoot.first;
|
||||
_pucksOffset[secondFoot.first] = computeOffset(defaultToReferenceMat, inputCalibration.defaultRightFoot, secondFootPose);
|
||||
} else {
|
||||
_jointToPuckMap[controller::LEFT_FOOT] = secondFoot.first;
|
||||
_pucksOffset[secondFoot.first] = computeOffset(defaultToReferenceMat, inputCalibration.defaultLeftFoot, secondFootPose);
|
||||
_jointToPuckMap[controller::RIGHT_FOOT] = firstFoot.first;
|
||||
_pucksOffset[firstFoot.first] = computeOffset(defaultToReferenceMat, inputCalibration.defaultRightFoot, firstFootPose);
|
||||
}
|
||||
}
|
||||
|
||||
void ViveControllerManager::InputDevice::calibrateHips(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration) {
|
||||
_jointToPuckMap[controller::HIPS] = _validTrackedObjects[HIP].first;
|
||||
_pucksOffset[_validTrackedObjects[HIP].first] = computeOffset(defaultToReferenceMat, inputCalibration.defaultHips, _validTrackedObjects[HIP].second);
|
||||
|
@ -663,7 +770,19 @@ void ViveControllerManager::InputDevice::calibrateShoulders(glm::mat4& defaultTo
|
|||
_jointToPuckMap[controller::RIGHT_ARM] = firstShoulder.first;
|
||||
_pucksOffset[firstShoulder.first] = computeOffset(defaultToReferenceMat, inputCalibration.defaultRightArm, firstShoulder.second);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ViveControllerManager::InputDevice::calibrateHead(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration) {
|
||||
size_t headIndex = _validTrackedObjects.size() - 1;
|
||||
const PuckPosePair& head = _validTrackedObjects[headIndex];
|
||||
|
||||
// assume the person is wearing the head puck on his/her forehead
|
||||
glm::mat4 defaultHeadOffset = glm::inverse(inputCalibration.defaultCenterEyeMat) * inputCalibration.defaultHeadMat;
|
||||
controller::Pose newHead = head.second.postTransform(defaultHeadOffset);
|
||||
|
||||
_jointToPuckMap[controller::HEAD] = head.first;
|
||||
_pucksOffset[head.first] = computeOffset(defaultToReferenceMat, inputCalibration.defaultHeadMat, newHead);
|
||||
}
|
||||
|
||||
|
||||
void ViveControllerManager::InputDevice::loadSettings() {
|
||||
|
@ -699,6 +818,10 @@ void ViveControllerManager::InputDevice::setConfigFromString(const QString& valu
|
|||
_preferedConfig = Config::FeetHipsAndChest;
|
||||
} else if (value == "FeetHipsAndShoulders") {
|
||||
_preferedConfig = Config::FeetHipsAndShoulders;
|
||||
} else if (value == "FeetHipsChestAndHead") {
|
||||
_preferedConfig = Config::FeetHipsChestAndHead;
|
||||
} else if (value == "FeetHipsAndHead") {
|
||||
_preferedConfig = Config::FeetHipsAndHead;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -707,11 +830,43 @@ void ViveControllerManager::InputDevice::createPreferences() {
|
|||
auto preferences = DependencyManager::get<Preferences>();
|
||||
static const QString VIVE_PUCKS_CONFIG = "Vive Pucks Configuration";
|
||||
|
||||
{
|
||||
static const float MIN_VALUE = -3.0f;
|
||||
static const float MAX_VALUE = 3.0f;
|
||||
static const float STEP = 0.01f;
|
||||
|
||||
auto getter = [this]()->float { return HEAD_PUCK_Y_OFFSET; };
|
||||
auto setter = [this](const float& value) { HEAD_PUCK_Y_OFFSET = value; };
|
||||
|
||||
auto preference = new SpinnerPreference(VIVE_PUCKS_CONFIG, "HeadPuckYOffset", getter, setter);
|
||||
preference->setMin(MIN_VALUE);
|
||||
preference->setMax(MAX_VALUE);
|
||||
preference->setDecimals(3);
|
||||
preference->setStep(STEP);
|
||||
preferences->addPreference(preference);
|
||||
}
|
||||
|
||||
{
|
||||
static const float MIN_VALUE = -3.0f;
|
||||
static const float MAX_VALUE = 3.0f;
|
||||
static const float STEP = 0.01f;
|
||||
|
||||
auto getter = [this]()->float { return HEAD_PUCK_Z_OFFSET; };
|
||||
auto setter = [this](const float& value) { HEAD_PUCK_Z_OFFSET = value; };
|
||||
|
||||
auto preference = new SpinnerPreference(VIVE_PUCKS_CONFIG, "HeadPuckXOffset", getter, setter);
|
||||
preference->setMin(MIN_VALUE);
|
||||
preference->setMax(MAX_VALUE);
|
||||
preference->setStep(STEP);
|
||||
preference->setDecimals(3);
|
||||
preferences->addPreference(preference);
|
||||
}
|
||||
|
||||
{
|
||||
auto getter = [this]()->QString { return _configStringMap[_preferedConfig]; };
|
||||
auto setter = [this](const QString& value) { setConfigFromString(value); saveSettings(); };
|
||||
auto preference = new ComboBoxPreference(VIVE_PUCKS_CONFIG, "Configuration", getter, setter);
|
||||
QStringList list = {"Auto", "Feet", "FeetAndHips", "FeetHipsAndChest", "FeetHipsAndShoulders"};
|
||||
QStringList list = {"Auto", "Feet", "FeetAndHips", "FeetHipsAndChest", "FeetHipsAndShoulders", "FeetHipsAndHead"};
|
||||
preference->setItems(list);
|
||||
preferences->addPreference(preference);
|
||||
|
||||
|
|
|
@ -68,6 +68,7 @@ private:
|
|||
void calibrate(const controller::InputCalibrationData& inputCalibration);
|
||||
void uncalibrate();
|
||||
controller::Pose addOffsetToPuckPose(int joint) const;
|
||||
glm::mat4 recalculateDefaultToReferenceForHeadPuck(const controller::InputCalibrationData& inputCalibration);
|
||||
void updateCalibratedLimbs();
|
||||
bool checkForCalibrationEvent();
|
||||
void handleHandController(float deltaTime, uint32_t deviceIndex, const controller::InputCalibrationData& inputCalibrationData, bool isLeftHand);
|
||||
|
@ -85,11 +86,14 @@ private:
|
|||
void loadSettings();
|
||||
void saveSettings() const;
|
||||
void calibrateFeet(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration);
|
||||
void calibrateFeet(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration, glm::vec3 headXAxis, glm::vec3 headPosition);
|
||||
void calibrateHips(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration);
|
||||
void calibrateChest(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration);
|
||||
|
||||
void calibrateShoulders(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration,
|
||||
int firstShoulderIndex, int secondShoulderIndex);
|
||||
void calibrateHead(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration);
|
||||
|
||||
|
||||
class FilteredStick {
|
||||
public:
|
||||
|
@ -120,6 +124,8 @@ private:
|
|||
FeetAndHips,
|
||||
FeetHipsAndChest,
|
||||
FeetHipsAndShoulders,
|
||||
FeetHipsChestAndHead,
|
||||
FeetHipsAndHead
|
||||
};
|
||||
Config _config { Config::Auto };
|
||||
Config _preferedConfig { Config::Auto };
|
||||
|
@ -146,6 +152,7 @@ private:
|
|||
bool _triggersPressedHandled { false };
|
||||
bool _calibrated { false };
|
||||
bool _timeTilCalibrationSet { false };
|
||||
bool _overrideHead { false };
|
||||
mutable std::recursive_mutex _lock;
|
||||
|
||||
QString configToString(Config config);
|
||||
|
|
|
@ -1,15 +1,9 @@
|
|||
# Declare dependencies
|
||||
macro (SETUP_TESTCASE_DEPENDENCIES)
|
||||
# link in the shared libraries
|
||||
link_hifi_libraries(shared ktx gpu image)
|
||||
|
||||
set(TARGET_NAME ktx-test)
|
||||
|
||||
if (WIN32)
|
||||
SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /ignore:4049 /ignore:4217")
|
||||
endif()
|
||||
package_libraries_for_deployment()
|
||||
endmacro ()
|
||||
|
||||
# This is not a testcase -- just set it up as a regular hifi project
|
||||
setup_hifi_project(Quick Gui OpenGL)
|
||||
set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/")
|
||||
|
||||
# link in the shared libraries
|
||||
link_hifi_libraries(shared octree ktx gl gpu gpu-gl render model model-networking networking render-utils fbx entities entities-renderer animation audio avatars script-engine physics image)
|
||||
|
||||
package_libraries_for_deployment()
|
||||
setup_hifi_testcase()
|
||||
|
|
195
tests/ktx/src/KtxTests.cpp
Normal file
195
tests/ktx/src/KtxTests.cpp
Normal file
|
@ -0,0 +1,195 @@
|
|||
//
|
||||
// Created by Bradley Austin Davis on 2016/07/01
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "KtxTests.h"
|
||||
|
||||
#include <mutex>
|
||||
|
||||
#include <QtTest/QtTest>
|
||||
|
||||
#include <ktx/KTX.h>
|
||||
#include <gpu/Texture.h>
|
||||
#include <image/Image.h>
|
||||
|
||||
|
||||
QTEST_GUILESS_MAIN(KtxTests)
|
||||
|
||||
QString getRootPath() {
|
||||
static std::once_flag once;
|
||||
static QString result;
|
||||
std::call_once(once, [&] {
|
||||
QFileInfo file(__FILE__);
|
||||
QDir parent = file.absolutePath();
|
||||
result = QDir::cleanPath(parent.currentPath() + "/../../..");
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
void KtxTests::initTestCase() {
|
||||
}
|
||||
|
||||
void KtxTests::cleanupTestCase() {
|
||||
}
|
||||
|
||||
void KtxTests::testKhronosCompressionFunctions() {
|
||||
using namespace khronos::gl::texture;
|
||||
QCOMPARE(evalAlignedCompressedBlockCount<4>(0), (uint32_t)0x0);
|
||||
QCOMPARE(evalAlignedCompressedBlockCount<4>(1), (uint32_t)0x1);
|
||||
QCOMPARE(evalAlignedCompressedBlockCount<4>(4), (uint32_t)0x1);
|
||||
QCOMPARE(evalAlignedCompressedBlockCount<4>(5), (uint32_t)0x2);
|
||||
QCOMPARE(evalCompressedBlockCount(InternalFormat::COMPRESSED_SRGB_S3TC_DXT1_EXT, 0x00), (uint32_t)0x00);
|
||||
QCOMPARE(evalCompressedBlockCount(InternalFormat::COMPRESSED_SRGB_S3TC_DXT1_EXT, 0x01), (uint32_t)0x01);
|
||||
QCOMPARE(evalCompressedBlockCount(InternalFormat::COMPRESSED_SRGB_S3TC_DXT1_EXT, 0x04), (uint32_t)0x01);
|
||||
QCOMPARE(evalCompressedBlockCount(InternalFormat::COMPRESSED_SRGB_S3TC_DXT1_EXT, 0x05), (uint32_t)0x02);
|
||||
QCOMPARE(evalCompressedBlockCount(InternalFormat::COMPRESSED_SRGB_S3TC_DXT1_EXT, 0x1000), (uint32_t)0x400);
|
||||
|
||||
QVERIFY_EXCEPTION_THROWN(evalCompressedBlockCount(InternalFormat::RGBA8, 0x00), std::runtime_error);
|
||||
}
|
||||
|
||||
void KtxTests::testKtxEvalFunctions() {
|
||||
QCOMPARE(sizeof(ktx::Header), (size_t)64);
|
||||
QCOMPARE(ktx::evalPadding(0x0), (uint8_t)0);
|
||||
QCOMPARE(ktx::evalPadding(0x1), (uint8_t)3);
|
||||
QCOMPARE(ktx::evalPadding(0x2), (uint8_t)2);
|
||||
QCOMPARE(ktx::evalPadding(0x3), (uint8_t)1);
|
||||
QCOMPARE(ktx::evalPadding(0x4), (uint8_t)0);
|
||||
QCOMPARE(ktx::evalPadding(0x400), (uint8_t)0);
|
||||
QCOMPARE(ktx::evalPadding(0x401), (uint8_t)3);
|
||||
QCOMPARE(ktx::evalPaddedSize(0x0), 0x0);
|
||||
QCOMPARE(ktx::evalPaddedSize(0x1), 0x4);
|
||||
QCOMPARE(ktx::evalPaddedSize(0x2), 0x4);
|
||||
QCOMPARE(ktx::evalPaddedSize(0x3), 0x4);
|
||||
QCOMPARE(ktx::evalPaddedSize(0x4), 0x4);
|
||||
QCOMPARE(ktx::evalPaddedSize(0x400), 0x400);
|
||||
QCOMPARE(ktx::evalPaddedSize(0x401), 0x404);
|
||||
QCOMPARE(ktx::evalAlignedCount((uint32_t)0x0), (uint32_t)0x0);
|
||||
QCOMPARE(ktx::evalAlignedCount((uint32_t)0x1), (uint32_t)0x1);
|
||||
QCOMPARE(ktx::evalAlignedCount((uint32_t)0x4), (uint32_t)0x1);
|
||||
QCOMPARE(ktx::evalAlignedCount((uint32_t)0x5), (uint32_t)0x2);
|
||||
}
|
||||
|
||||
void KtxTests::testKtxSerialization() {
|
||||
const QString TEST_IMAGE = getRootPath() + "/scripts/developer/tests/cube_texture.png";
|
||||
QImage image(TEST_IMAGE);
|
||||
gpu::TexturePointer testTexture = image::TextureUsage::process2DTextureColorFromImage(image, TEST_IMAGE.toStdString(), true);
|
||||
auto ktxMemory = gpu::Texture::serialize(*testTexture);
|
||||
QVERIFY(ktxMemory.get());
|
||||
|
||||
// Serialize the image to a file
|
||||
QTemporaryFile TEST_IMAGE_KTX;
|
||||
{
|
||||
const auto& ktxStorage = ktxMemory->getStorage();
|
||||
QVERIFY(ktx::KTX::validate(ktxStorage));
|
||||
QVERIFY(ktxMemory->isValid());
|
||||
|
||||
auto& outFile = TEST_IMAGE_KTX;
|
||||
if (!outFile.open()) {
|
||||
QFAIL("Unable to open file");
|
||||
}
|
||||
auto ktxSize = ktxStorage->size();
|
||||
outFile.resize(ktxSize);
|
||||
auto dest = outFile.map(0, ktxSize);
|
||||
memcpy(dest, ktxStorage->data(), ktxSize);
|
||||
outFile.unmap(dest);
|
||||
outFile.close();
|
||||
}
|
||||
|
||||
|
||||
{
|
||||
auto ktxStorage = std::make_shared<storage::FileStorage>(TEST_IMAGE_KTX.fileName());
|
||||
QVERIFY(ktx::KTX::validate(ktxStorage));
|
||||
auto ktxFile = ktx::KTX::create(ktxStorage);
|
||||
QVERIFY(ktxFile.get());
|
||||
QVERIFY(ktxFile->isValid());
|
||||
{
|
||||
const auto& memStorage = ktxMemory->getStorage();
|
||||
const auto& fileStorage = ktxFile->getStorage();
|
||||
QVERIFY(memStorage->size() == fileStorage->size());
|
||||
QVERIFY(memStorage->data() != fileStorage->data());
|
||||
QVERIFY(0 == memcmp(memStorage->data(), fileStorage->data(), memStorage->size()));
|
||||
QVERIFY(ktxFile->_images.size() == ktxMemory->_images.size());
|
||||
auto imageCount = ktxFile->_images.size();
|
||||
auto startMemory = ktxMemory->_storage->data();
|
||||
auto startFile = ktxFile->_storage->data();
|
||||
for (size_t i = 0; i < imageCount; ++i) {
|
||||
auto memImages = ktxMemory->_images[i];
|
||||
auto fileImages = ktxFile->_images[i];
|
||||
QVERIFY(memImages._padding == fileImages._padding);
|
||||
QVERIFY(memImages._numFaces == fileImages._numFaces);
|
||||
QVERIFY(memImages._imageSize == fileImages._imageSize);
|
||||
QVERIFY(memImages._faceSize == fileImages._faceSize);
|
||||
QVERIFY(memImages._faceBytes.size() == memImages._numFaces);
|
||||
QVERIFY(fileImages._faceBytes.size() == fileImages._numFaces);
|
||||
auto faceCount = fileImages._numFaces;
|
||||
for (uint32_t face = 0; face < faceCount; ++face) {
|
||||
auto memFace = memImages._faceBytes[face];
|
||||
auto memOffset = memFace - startMemory;
|
||||
auto fileFace = fileImages._faceBytes[face];
|
||||
auto fileOffset = fileFace - startFile;
|
||||
QVERIFY(memOffset % 4 == 0);
|
||||
QVERIFY(memOffset == fileOffset);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
testTexture->setKtxBacking(TEST_IMAGE_KTX.fileName().toStdString());
|
||||
}
|
||||
|
||||
#if 0
|
||||
|
||||
static const QString TEST_FOLDER { "H:/ktx_cacheold" };
|
||||
//static const QString TEST_FOLDER { "C:/Users/bdavis/Git/KTX/testimages" };
|
||||
|
||||
//static const QString EXTENSIONS { "4bbdf8f786470e4ab3e672d44b8e8df2.ktx" };
|
||||
static const QString EXTENSIONS { "*.ktx" };
|
||||
|
||||
int mainTemp(int, char**) {
|
||||
qInstallMessageHandler(messageHandler);
|
||||
auto fileInfoList = QDir { TEST_FOLDER }.entryInfoList(QStringList { EXTENSIONS });
|
||||
for (auto fileInfo : fileInfoList) {
|
||||
qDebug() << fileInfo.filePath();
|
||||
std::shared_ptr<storage::Storage> storage { new storage::FileStorage { fileInfo.filePath() } };
|
||||
|
||||
if (!ktx::KTX::validate(storage)) {
|
||||
qDebug() << "KTX invalid";
|
||||
}
|
||||
|
||||
auto ktxFile = ktx::KTX::create(storage);
|
||||
ktx::KTXDescriptor ktxDescriptor = ktxFile->toDescriptor();
|
||||
|
||||
qDebug() << "Contains " << ktxDescriptor.keyValues.size() << " key value pairs";
|
||||
for (const auto& kv : ktxDescriptor.keyValues) {
|
||||
qDebug() << "\t" << kv._key.c_str();
|
||||
}
|
||||
|
||||
auto offsetToMinMipKV = ktxDescriptor.getValueOffsetForKey(ktx::HIFI_MIN_POPULATED_MIP_KEY);
|
||||
if (offsetToMinMipKV) {
|
||||
auto data = storage->data() + ktx::KTX_HEADER_SIZE + offsetToMinMipKV;
|
||||
auto minMipLevelAvailable = *data;
|
||||
qDebug() << "\tMin mip available " << minMipLevelAvailable;
|
||||
assert(minMipLevelAvailable < ktxDescriptor.header.numberOfMipmapLevels);
|
||||
}
|
||||
auto storageSize = storage->size();
|
||||
for (const auto& faceImageDesc : ktxDescriptor.images) {
|
||||
//assert(0 == (faceImageDesc._faceSize % 4));
|
||||
for (const auto& faceOffset : faceImageDesc._faceOffsets) {
|
||||
assert(0 == (faceOffset % 4));
|
||||
auto faceEndOffset = faceOffset + faceImageDesc._faceSize;
|
||||
assert(faceEndOffset <= storageSize);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& faceImage : ktxFile->_images) {
|
||||
for (const ktx::Byte* faceBytes : faceImage._faceBytes) {
|
||||
assert(0 == (reinterpret_cast<size_t>(faceBytes) % 4));
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
#endif
|
21
tests/ktx/src/KtxTests.h
Normal file
21
tests/ktx/src/KtxTests.h
Normal file
|
@ -0,0 +1,21 @@
|
|||
//
|
||||
// Created by Bradley Austin Davis on 2016/07/01
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <QtCore/QObject>
|
||||
|
||||
class KtxTests : public QObject {
|
||||
Q_OBJECT
|
||||
private slots:
|
||||
void initTestCase();
|
||||
void cleanupTestCase();
|
||||
void testKtxEvalFunctions();
|
||||
void testKhronosCompressionFunctions();
|
||||
void testKtxSerialization();
|
||||
};
|
||||
|
||||
|
|
@ -1,223 +0,0 @@
|
|||
//
|
||||
// Created by Bradley Austin Davis on 2016/07/01
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <sstream>
|
||||
|
||||
#include <QProcessEnvironment>
|
||||
|
||||
#include <QtCore/QDir>
|
||||
#include <QtCore/QElapsedTimer>
|
||||
#include <QtCore/QLoggingCategory>
|
||||
#include <QtCore/QRegularExpression>
|
||||
#include <QtCore/QSettings>
|
||||
#include <QtCore/QTimer>
|
||||
#include <QtCore/QThread>
|
||||
#include <QtCore/QThreadPool>
|
||||
#include <QtCore/QSaveFile>
|
||||
|
||||
#include <QtGui/QGuiApplication>
|
||||
#include <QtGui/QResizeEvent>
|
||||
#include <QtGui/QWindow>
|
||||
|
||||
#include <QtWidgets/QFileDialog>
|
||||
#include <QtWidgets/QInputDialog>
|
||||
#include <QtWidgets/QMessageBox>
|
||||
#include <QtWidgets/QApplication>
|
||||
|
||||
#include <shared/RateCounter.h>
|
||||
#include <shared/NetworkUtils.h>
|
||||
#include <shared/FileLogger.h>
|
||||
#include <shared/FileUtils.h>
|
||||
#include <StatTracker.h>
|
||||
#include <LogHandler.h>
|
||||
|
||||
#include <gpu/Texture.h>
|
||||
#include <gl/Config.h>
|
||||
#include <model/TextureMap.h>
|
||||
#include <ktx/KTX.h>
|
||||
#include <image/Image.h>
|
||||
|
||||
QSharedPointer<FileLogger> logger;
|
||||
|
||||
gpu::Texture* cacheTexture(const std::string& name, gpu::Texture* srcTexture, bool write = true, bool read = true);
|
||||
|
||||
|
||||
void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) {
|
||||
QString logMessage = LogHandler::getInstance().printMessage((LogMsgType)type, context, message);
|
||||
|
||||
if (!logMessage.isEmpty()) {
|
||||
#ifdef Q_OS_WIN
|
||||
OutputDebugStringA(logMessage.toLocal8Bit().constData());
|
||||
OutputDebugStringA("\n");
|
||||
#endif
|
||||
if (logger) {
|
||||
logger->addMessage(qPrintable(logMessage + "\n"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const char * LOG_FILTER_RULES = R"V0G0N(
|
||||
hifi.gpu=true
|
||||
)V0G0N";
|
||||
|
||||
QString getRootPath() {
|
||||
static std::once_flag once;
|
||||
static QString result;
|
||||
std::call_once(once, [&] {
|
||||
QFileInfo file(__FILE__);
|
||||
QDir parent = file.absolutePath();
|
||||
result = QDir::cleanPath(parent.currentPath() + "/../../..");
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
const QString TEST_IMAGE = getRootPath() + "/scripts/developer/tests/cube_texture.png";
|
||||
const QString TEST_IMAGE_KTX = getRootPath() + "/scripts/developer/tests/cube_texture.ktx";
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
QApplication app(argc, argv);
|
||||
QCoreApplication::setApplicationName("KTX");
|
||||
QCoreApplication::setOrganizationName("High Fidelity");
|
||||
QCoreApplication::setOrganizationDomain("highfidelity.com");
|
||||
logger.reset(new FileLogger());
|
||||
|
||||
Q_ASSERT(ktx::evalPadding(0) == 0);
|
||||
Q_ASSERT(ktx::evalPadding(1) == 3);
|
||||
Q_ASSERT(ktx::evalPadding(2) == 2);
|
||||
Q_ASSERT(ktx::evalPadding(3) == 1);
|
||||
Q_ASSERT(ktx::evalPadding(4) == 0);
|
||||
Q_ASSERT(ktx::evalPadding(1024) == 0);
|
||||
Q_ASSERT(ktx::evalPadding(1025) == 3);
|
||||
Q_ASSERT(ktx::evalPaddedSize(0) == 0);
|
||||
Q_ASSERT(ktx::evalPaddedSize(1) == 4);
|
||||
Q_ASSERT(ktx::evalPaddedSize(2) == 4);
|
||||
Q_ASSERT(ktx::evalPaddedSize(3) == 4);
|
||||
Q_ASSERT(ktx::evalPaddedSize(4) == 4);
|
||||
Q_ASSERT(ktx::evalPaddedSize(1024) == 1024);
|
||||
Q_ASSERT(ktx::evalPaddedSize(1025) == 1028);
|
||||
Q_ASSERT(sizeof(ktx::Header) == 12 + (sizeof(uint32_t) * 13));
|
||||
|
||||
DependencyManager::set<tracing::Tracer>();
|
||||
qInstallMessageHandler(messageHandler);
|
||||
QLoggingCategory::setFilterRules(LOG_FILTER_RULES);
|
||||
|
||||
QImage image(TEST_IMAGE);
|
||||
gpu::TexturePointer testTexture = image::TextureUsage::process2DTextureColorFromImage(image, TEST_IMAGE.toStdString(), true);
|
||||
|
||||
auto ktxMemory = gpu::Texture::serialize(*testTexture);
|
||||
{
|
||||
const auto& ktxStorage = ktxMemory->getStorage();
|
||||
Q_ASSERT_X(ktx::KTX::validate(ktxStorage), __FUNCTION__, "KTX storage validation failed");
|
||||
Q_ASSERT_X(ktxMemory->isValid(), __FUNCTION__, "KTX self-validation failed");
|
||||
QSaveFile outFile(TEST_IMAGE_KTX);
|
||||
if (!outFile.open(QFile::WriteOnly)) {
|
||||
throw std::runtime_error("Unable to open file");
|
||||
}
|
||||
auto ktxSize = ktxStorage->size();
|
||||
outFile.resize(ktxSize);
|
||||
auto dest = outFile.map(0, ktxSize);
|
||||
memcpy(dest, ktxStorage->data(), ktxSize);
|
||||
outFile.unmap(dest);
|
||||
outFile.commit();
|
||||
}
|
||||
|
||||
{
|
||||
auto ktxFile = ktx::KTX::create(std::shared_ptr<storage::Storage>(new storage::FileStorage(TEST_IMAGE_KTX)));
|
||||
{
|
||||
const auto& memStorage = ktxMemory->getStorage();
|
||||
const auto& fileStorage = ktxFile->getStorage();
|
||||
Q_ASSERT(memStorage->size() == fileStorage->size());
|
||||
Q_ASSERT(memStorage->data() != fileStorage->data());
|
||||
Q_ASSERT(0 == memcmp(memStorage->data(), fileStorage->data(), memStorage->size()));
|
||||
Q_ASSERT(ktxFile->_images.size() == ktxMemory->_images.size());
|
||||
auto imageCount = ktxFile->_images.size();
|
||||
auto startMemory = ktxMemory->_storage->data();
|
||||
auto startFile = ktxFile->_storage->data();
|
||||
for (size_t i = 0; i < imageCount; ++i) {
|
||||
auto memImages = ktxMemory->_images[i];
|
||||
auto fileImages = ktxFile->_images[i];
|
||||
Q_ASSERT(memImages._padding == fileImages._padding);
|
||||
Q_ASSERT(memImages._numFaces == fileImages._numFaces);
|
||||
Q_ASSERT(memImages._imageSize == fileImages._imageSize);
|
||||
Q_ASSERT(memImages._faceSize == fileImages._faceSize);
|
||||
Q_ASSERT(memImages._faceBytes.size() == memImages._numFaces);
|
||||
Q_ASSERT(fileImages._faceBytes.size() == fileImages._numFaces);
|
||||
auto faceCount = fileImages._numFaces;
|
||||
for (uint32_t face = 0; face < faceCount; ++face) {
|
||||
auto memFace = memImages._faceBytes[face];
|
||||
auto memOffset = memFace - startMemory;
|
||||
auto fileFace = fileImages._faceBytes[face];
|
||||
auto fileOffset = fileFace - startFile;
|
||||
Q_ASSERT(memOffset % 4 == 0);
|
||||
Q_ASSERT(memOffset == fileOffset);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
testTexture->setKtxBacking(TEST_IMAGE_KTX.toStdString());
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if 0
|
||||
static const QString TEST_FOLDER { "H:/ktx_cacheold" };
|
||||
//static const QString TEST_FOLDER { "C:/Users/bdavis/Git/KTX/testimages" };
|
||||
|
||||
//static const QString EXTENSIONS { "4bbdf8f786470e4ab3e672d44b8e8df2.ktx" };
|
||||
static const QString EXTENSIONS { "*.ktx" };
|
||||
|
||||
int mainTemp(int, char**) {
|
||||
qInstallMessageHandler(messageHandler);
|
||||
auto fileInfoList = QDir { TEST_FOLDER }.entryInfoList(QStringList { EXTENSIONS });
|
||||
for (auto fileInfo : fileInfoList) {
|
||||
qDebug() << fileInfo.filePath();
|
||||
std::shared_ptr<storage::Storage> storage { new storage::FileStorage { fileInfo.filePath() } };
|
||||
|
||||
if (!ktx::KTX::validate(storage)) {
|
||||
qDebug() << "KTX invalid";
|
||||
}
|
||||
|
||||
auto ktxFile = ktx::KTX::create(storage);
|
||||
ktx::KTXDescriptor ktxDescriptor = ktxFile->toDescriptor();
|
||||
|
||||
qDebug() << "Contains " << ktxDescriptor.keyValues.size() << " key value pairs";
|
||||
for (const auto& kv : ktxDescriptor.keyValues) {
|
||||
qDebug() << "\t" << kv._key.c_str();
|
||||
}
|
||||
|
||||
auto offsetToMinMipKV = ktxDescriptor.getValueOffsetForKey(ktx::HIFI_MIN_POPULATED_MIP_KEY);
|
||||
if (offsetToMinMipKV) {
|
||||
auto data = storage->data() + ktx::KTX_HEADER_SIZE + offsetToMinMipKV;
|
||||
auto minMipLevelAvailable = *data;
|
||||
qDebug() << "\tMin mip available " << minMipLevelAvailable;
|
||||
assert(minMipLevelAvailable < ktxDescriptor.header.numberOfMipmapLevels);
|
||||
}
|
||||
auto storageSize = storage->size();
|
||||
for (const auto& faceImageDesc : ktxDescriptor.images) {
|
||||
//assert(0 == (faceImageDesc._faceSize % 4));
|
||||
for (const auto& faceOffset : faceImageDesc._faceOffsets) {
|
||||
assert(0 == (faceOffset % 4));
|
||||
auto faceEndOffset = faceOffset + faceImageDesc._faceSize;
|
||||
assert(faceEndOffset <= storageSize);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& faceImage : ktxFile->_images) {
|
||||
for (const ktx::Byte* faceBytes : faceImage._faceBytes) {
|
||||
assert(0 == (reinterpret_cast<size_t>(faceBytes) % 4));
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#include "main.moc"
|
||||
|
170
tests/networking/src/FileCacheTests.cpp
Normal file
170
tests/networking/src/FileCacheTests.cpp
Normal file
|
@ -0,0 +1,170 @@
|
|||
//
|
||||
// ResoruceTests.cpp
|
||||
//
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
|
||||
#include "FileCacheTests.h"
|
||||
|
||||
#include <FileCache.h>
|
||||
|
||||
QTEST_GUILESS_MAIN(FileCacheTests)
|
||||
|
||||
using namespace cache;
|
||||
|
||||
// Limit the file size to 10 MB
|
||||
static const size_t MAX_UNUSED_SIZE { 1024 * 1024 * 10 };
|
||||
static const QByteArray TEST_DATA { 1024 * 1024, '0' };
|
||||
|
||||
static std::string getFileKey(int i) {
|
||||
return QString(QByteArray { 1, (char)i }.toHex()).toStdString();
|
||||
}
|
||||
|
||||
class TestFile : public File {
|
||||
using Parent = File;
|
||||
public:
|
||||
TestFile(Metadata&& metadata, const std::string& filepath)
|
||||
: Parent(std::move(metadata), filepath) {
|
||||
}
|
||||
};
|
||||
|
||||
class TestFileCache : public FileCache {
|
||||
using Parent = FileCache;
|
||||
|
||||
public:
|
||||
TestFileCache(const std::string& dirname, const std::string& ext, QObject* parent = nullptr) : Parent(dirname, ext, nullptr) {
|
||||
initialize();
|
||||
}
|
||||
|
||||
std::unique_ptr<File> createFile(Metadata&& metadata, const std::string& filepath) override {
|
||||
qCInfo(file_cache) << "Wrote KTX" << metadata.key.c_str();
|
||||
return std::unique_ptr<File>(new TestFile(std::move(metadata), filepath));
|
||||
}
|
||||
};
|
||||
|
||||
using CachePointer = std::shared_ptr<TestFileCache>;
|
||||
|
||||
// The FileCache relies on deleteLater to clear unused files, but QTest classes don't run a conventional event loop
|
||||
// so we need to call this function to force any pending deletes to occur in the File destructor
|
||||
static void forceDeletes() {
|
||||
while (QCoreApplication::hasPendingEvents()) {
|
||||
QCoreApplication::sendPostedEvents();
|
||||
QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete);
|
||||
QCoreApplication::processEvents();
|
||||
}
|
||||
}
|
||||
|
||||
size_t FileCacheTests::getCacheDirectorySize() const {
|
||||
size_t result = 0;
|
||||
QDir dir(_testDir.path());
|
||||
for (const auto& file : dir.entryList({ "*.tmp" })) {
|
||||
result += QFileInfo(dir.absoluteFilePath(file)).size();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
CachePointer makeFileCache(QString& location) {
|
||||
auto result = std::make_shared<TestFileCache>(location.toStdString(), "tmp");
|
||||
result->setMaxSize(MAX_UNUSED_SIZE);
|
||||
return result;
|
||||
}
|
||||
|
||||
void FileCacheTests::initTestCase() {
|
||||
}
|
||||
|
||||
void FileCacheTests::testUnusedFiles() {
|
||||
auto cache = makeFileCache(_testDir.path());
|
||||
std::list<FilePointer> inUseFiles;
|
||||
|
||||
{
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
std::string key = getFileKey(i);
|
||||
auto file = cache->writeFile(TEST_DATA.data(), TestFileCache::Metadata(key, TEST_DATA.size()));
|
||||
inUseFiles.push_back(file);
|
||||
forceDeletes();
|
||||
QThread::msleep(10);
|
||||
}
|
||||
QCOMPARE(cache->getNumCachedFiles(), (size_t)0);
|
||||
QCOMPARE(cache->getNumTotalFiles(), (size_t)100);
|
||||
// Release the in-use files
|
||||
inUseFiles.clear();
|
||||
// Cache state is updated, but the directory state is unchanged,
|
||||
// because the file deletes are triggered by an event loop
|
||||
QCOMPARE(cache->getNumCachedFiles(), (size_t)10);
|
||||
QCOMPARE(cache->getNumTotalFiles(), (size_t)10);
|
||||
QVERIFY(getCacheDirectorySize() > MAX_UNUSED_SIZE);
|
||||
forceDeletes();
|
||||
QCOMPARE(cache->getNumCachedFiles(), (size_t)10);
|
||||
QCOMPARE(cache->getNumTotalFiles(), (size_t)10);
|
||||
QVERIFY(getCacheDirectorySize() <= MAX_UNUSED_SIZE);
|
||||
}
|
||||
|
||||
// Reset the cache
|
||||
cache = makeFileCache(_testDir.path());
|
||||
{
|
||||
// Test files 0 to 89 are missing, because the LRU algorithm deleted them when we released the files
|
||||
for (int i = 0; i < 90; ++i) {
|
||||
std::string key = getFileKey(i);
|
||||
auto file = cache->getFile(key);
|
||||
QVERIFY(!file.get());
|
||||
}
|
||||
|
||||
QThread::msleep(1000);
|
||||
// Test files 90 to 99 are present
|
||||
for (int i = 90; i < 100; ++i) {
|
||||
std::string key = getFileKey(i);
|
||||
auto file = cache->getFile(key);
|
||||
QVERIFY(file.get());
|
||||
inUseFiles.push_back(file);
|
||||
// Each access touches the file, so we need to sleep here to ensure that the files are
|
||||
// spaced out in numeric order, otherwise later tests can't reliably determine the order
|
||||
// for cache ejection
|
||||
QThread::msleep(1000);
|
||||
}
|
||||
QCOMPARE(cache->getNumCachedFiles(), (size_t)0);
|
||||
QCOMPARE(cache->getNumTotalFiles(), (size_t)10);
|
||||
inUseFiles.clear();
|
||||
QCOMPARE(cache->getNumCachedFiles(), (size_t)10);
|
||||
QCOMPARE(cache->getNumTotalFiles(), (size_t)10);
|
||||
}
|
||||
}
|
||||
|
||||
size_t FileCacheTests::getFreeSpace() const {
|
||||
return QStorageInfo(_testDir.path()).bytesFree();
|
||||
}
|
||||
|
||||
// FIXME if something else is changing the amount of free space on the target drive concurrently with this test
|
||||
// running, then it may fail
|
||||
void FileCacheTests::testFreeSpacePreservation() {
|
||||
QCOMPARE(getCacheDirectorySize(), MAX_UNUSED_SIZE);
|
||||
// Set the target free space to slightly above whatever the current free space is...
|
||||
size_t targetFreeSpace = getFreeSpace() + MAX_UNUSED_SIZE / 2;
|
||||
|
||||
// Reset the cache
|
||||
auto cache = makeFileCache(_testDir.path());
|
||||
// Setting the min free space causes it to eject the oldest files that cause the cache to exceed the minimum space
|
||||
cache->setMinFreeSize(targetFreeSpace);
|
||||
QVERIFY(getFreeSpace() < targetFreeSpace);
|
||||
forceDeletes();
|
||||
QCOMPARE(cache->getNumCachedFiles(), (size_t)5);
|
||||
QCOMPARE(cache->getNumTotalFiles(), (size_t)5);
|
||||
QVERIFY(getFreeSpace() >= targetFreeSpace);
|
||||
for (int i = 0; i < 95; ++i) {
|
||||
std::string key = getFileKey(i);
|
||||
auto file = cache->getFile(key);
|
||||
QVERIFY(!file.get());
|
||||
}
|
||||
for (int i = 95; i < 100; ++i) {
|
||||
std::string key = getFileKey(i);
|
||||
auto file = cache->getFile(key);
|
||||
QVERIFY(file.get());
|
||||
}
|
||||
}
|
||||
|
||||
void FileCacheTests::cleanupTestCase() {
|
||||
}
|
||||
|
30
tests/networking/src/FileCacheTests.h
Normal file
30
tests/networking/src/FileCacheTests.h
Normal file
|
@ -0,0 +1,30 @@
|
|||
//
|
||||
// ResourceTests.h
|
||||
//
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_ResourceTests_h
|
||||
#define hifi_ResourceTests_h
|
||||
|
||||
#include <QtTest/QtTest>
|
||||
#include <QtCore/QTemporaryDir>
|
||||
|
||||
class FileCacheTests : public QObject {
|
||||
Q_OBJECT
|
||||
private slots:
|
||||
void initTestCase();
|
||||
void testUnusedFiles();
|
||||
void testFreeSpacePreservation();
|
||||
void cleanupTestCase();
|
||||
|
||||
private:
|
||||
size_t getFreeSpace() const;
|
||||
size_t getCacheDirectorySize() const;
|
||||
QTemporaryDir _testDir;
|
||||
};
|
||||
|
||||
#endif // hifi_ResourceTests_h
|
Loading…
Reference in a new issue