Merge branch 'master' of https://github.com/highfidelity/hifi into recording_audio_offset

This commit is contained in:
Atlante45 2014-09-30 11:24:55 -07:00
commit c599bed176
21 changed files with 419 additions and 337 deletions

View file

@ -116,7 +116,7 @@
"advanced": true
},
{
"name": "I-print-stream-stats",
"name": "print-stream-stats",
"type": "checkbox",
"label": "Print Stream Stats:",
"help": "audio upstream and downstream stats of each agent printed to audio-mixer stdout",

View file

@ -52,6 +52,10 @@ span.port {
color: red;
}
.locked {
color: blue;
}
.advanced-setting {
display: none;
}

View file

@ -3,9 +3,11 @@ var Settings = {
};
var viewHelpers = {
getFormGroup: function(groupName, setting, values, isAdvanced) {
getFormGroup: function(groupName, setting, values, isAdvanced, isLocked) {
setting_id = groupName + "_" + setting.name
console.log(setting.name + " in " + groupName + " is " + isLocked)
form_group = "<div class='form-group" + (isAdvanced ? " advanced-setting" : "") + "'>"
if (_.has(values, groupName) && _.has(values[groupName], setting.name)) {
@ -16,20 +18,26 @@ var viewHelpers = {
setting_value = ""
}
label_class = 'control-label'
if (isLocked) {
label_class += ' locked'
}
if (setting.type === 'checkbox') {
form_group += "<label class='control-label'>" + setting.label + "</label>"
form_group += "<div class='checkbox'>"
form_group += "<label class='" + label_class + "'>" + setting.label + "</label>"
form_group += "<div class='checkbox" + (isLocked ? " disabled" : "") + "'>"
form_group += "<label for='" + setting_id + "'>"
form_group += "<input type='checkbox' id='" + setting_id + "' " + (setting_value ? "checked" : "") + "/>"
form_group += "<input type='checkbox' id='" + setting_id + "' " +
(setting_value ? "checked" : "") + (isLocked ? " disabled" : "") + "/>"
form_group += " " + setting.help + "</label>";
form_group += "</div>"
} else {
input_type = _.has(setting, 'type') ? setting.type : "text"
form_group += "<label for='" + setting_id + "' class='control-label'>" + setting.label + "</label>";
form_group += "<label for='" + setting_id + "' class='" + label_class + "'>" + setting.label + "</label>";
form_group += "<input type='" + input_type + "' class='form-control' id='" + setting_id +
"' placeholder='" + (_.has(setting, 'placeholder') ? setting.placeholder : "") +
"' value='" + setting_value + "'/>"
"' value='" + setting_value + "'" + (isLocked ? " disabled" : "") + "/>"
form_group += "<span class='help-block'>" + setting.help + "</span>"
}
@ -101,6 +109,12 @@ function reloadSettings() {
$('#panels').html(Settings.panelsTemplate(data))
Settings.initialValues = form2js('settings-form', "_", false, cleanupFormValues, true);
// add tooltip to locked settings
$('label.locked').tooltip({
placement: 'right',
title: 'This setting is in the master config file and cannot be changed'
})
});
}

View file

@ -38,10 +38,12 @@
<div class="panel-body">
<% split_settings = _.partition(group.settings, function(value, index) { return !value.advanced }) %>
<% _.each(split_settings[0], function(setting) { %>
<%= getFormGroup(group.name, setting, values, false) %>
<%= getFormGroup(group.name, setting, values, false,
(_.has(locked, group.name) && _.has(locked[group.name], setting.name))) %>
<% }); %>
<% _.each(split_settings[1], function(setting) { %>
<%= getFormGroup(group.name, setting, values, true) %>
<%= getFormGroup(group.name, setting, values, true,
(_.has(locked, group.name) && _.has(locked[group.name], setting.name))) %>
<% }); %>
</div>
</div>

View file

@ -57,7 +57,7 @@ DomainServer::DomainServer(int argc, char* argv[]) :
setApplicationName("domain-server");
QSettings::setDefaultFormat(QSettings::IniFormat);
_settingsManager.loadSettingsMap(arguments());
_settingsManager.setupConfigMap(arguments());
installNativeEventFilter(&_shutdownEventListener);
connect(&_shutdownEventListener, SIGNAL(receivedCloseEvent()), SLOT(quit()));

View file

@ -28,7 +28,7 @@ const QString SETTINGS_DESCRIPTION_RELATIVE_PATH = "/resources/describe-settings
DomainServerSettingsManager::DomainServerSettingsManager() :
_descriptionArray(),
_settingsMap()
_configMap()
{
// load the description object from the settings description
QFile descriptionFile(QCoreApplication::applicationDirPath() + SETTINGS_DESCRIPTION_RELATIVE_PATH);
@ -37,11 +37,8 @@ DomainServerSettingsManager::DomainServerSettingsManager() :
_descriptionArray = QJsonDocument::fromJson(descriptionFile.readAll()).array();
}
void DomainServerSettingsManager::loadSettingsMap(const QStringList& argumentList) {
_settingsMap = HifiConfigVariantMap::mergeMasterConfigWithUserConfig(argumentList);
// figure out where we are supposed to persist our settings to
_settingsFilepath = HifiConfigVariantMap::userConfigFilepath(argumentList);
void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList) {
_configMap.loadMasterAndUserConfig(argumentList);
}
const QString SETTINGS_PATH = "/settings.json";
@ -76,7 +73,7 @@ bool DomainServerSettingsManager::handleAuthenticatedHTTPRequest(HTTPConnection
QJsonObject postedObject = postedDocument.object();
// we recurse one level deep below each group for the appropriate setting
recurseJSONObjectAndOverwriteSettings(postedObject, _settingsMap, _descriptionArray);
recurseJSONObjectAndOverwriteSettings(postedObject, _configMap.getUserConfig(), _descriptionArray);
// store whatever the current _settingsMap is to file
persistToFile();
@ -94,10 +91,13 @@ bool DomainServerSettingsManager::handleAuthenticatedHTTPRequest(HTTPConnection
// setup a JSON Object with descriptions and non-omitted settings
const QString SETTINGS_RESPONSE_DESCRIPTION_KEY = "descriptions";
const QString SETTINGS_RESPONSE_VALUE_KEY = "values";
const QString SETTINGS_RESPONSE_LOCKED_VALUES_KEY = "locked";
QJsonObject rootObject;
rootObject[SETTINGS_RESPONSE_DESCRIPTION_KEY] = _descriptionArray;
rootObject[SETTINGS_RESPONSE_VALUE_KEY] = responseObjectForType("", true);
rootObject[SETTINGS_RESPONSE_LOCKED_VALUES_KEY] = QJsonDocument::fromVariant(_configMap.getMasterConfig()).object();
connection->respond(HTTPConnection::StatusCode200, QJsonDocument(rootObject).toJson(), "application/json");
}
@ -145,7 +145,8 @@ QJsonObject DomainServerSettingsManager::responseObjectForType(const QString& ty
// we need to check if the settings map has a value for this setting
QVariant variantValue;
QVariant settingsMapGroupValue = _settingsMap.value(groupObject[DESCRIPTION_NAME_KEY].toString());
QVariant settingsMapGroupValue = _configMap.getMergedConfig()
.value(groupObject[DESCRIPTION_NAME_KEY].toString());
if (!settingsMapGroupValue.isNull()) {
variantValue = settingsMapGroupValue.toMap().value(settingName);
@ -239,23 +240,19 @@ void DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ
}
}
QByteArray DomainServerSettingsManager::getJSONSettingsMap() const {
return QJsonDocument::fromVariant(_settingsMap).toJson();
}
void DomainServerSettingsManager::persistToFile() {
// make sure we have the dir the settings file is supposed to live in
QFileInfo settingsFileInfo(_settingsFilepath);
QFileInfo settingsFileInfo(_configMap.getUserConfigFilename());
if (!settingsFileInfo.dir().exists()) {
settingsFileInfo.dir().mkpath(".");
}
QFile settingsFile(_settingsFilepath);
QFile settingsFile(_configMap.getUserConfigFilename());
if (settingsFile.open(QIODevice::WriteOnly)) {
settingsFile.write(getJSONSettingsMap());
settingsFile.write(QJsonDocument::fromVariant(_configMap.getUserConfig()).toJson());
} else {
qCritical("Could not write to JSON settings file. Unable to persist settings.");
}

View file

@ -15,6 +15,7 @@
#include <QtCore/QJsonArray>
#include <QtCore/QJsonDocument>
#include <HifiConfigVariantMap.h>
#include <HTTPManager.h>
class DomainServerSettingsManager : public QObject {
@ -24,10 +25,9 @@ public:
bool handlePublicHTTPRequest(HTTPConnection* connection, const QUrl& url);
bool handleAuthenticatedHTTPRequest(HTTPConnection* connection, const QUrl& url);
void loadSettingsMap(const QStringList& argumentList);
void setupConfigMap(const QStringList& argumentList);
QByteArray getJSONSettingsMap() const;
QVariantMap& getSettingsMap() { return _settingsMap; }
QVariantMap& getSettingsMap() { return _configMap.getMergedConfig(); }
private:
QJsonObject responseObjectForType(const QString& typeValue, bool isAuthenticated = false);
void recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject, QVariantMap& settingsVariant,
@ -35,8 +35,7 @@ private:
void persistToFile();
QJsonArray _descriptionArray;
QVariantMap _settingsMap;
QString _settingsFilepath;
HifiConfigVariantMap _configMap;
};
#endif // hifi_DomainServerSettingsManager_h

View file

@ -43,7 +43,7 @@ var noFly = true;
var fixedWalkVelocity = true;
//var roomLimits = { xMin: 618, xMax: 635.5, zMin: 528, zMax: 552.5 };
var roomLimits = { xMin: 100.0, xMax: 206.5, zMin: 251.4, zMax: 269.5 };
var roomLimits = { xMin: -1.0, xMax: -1.0, zMin: -1.0, zMax: -1.0 };
function isInRoom(position) {
var BUFFER = 2.0;

View file

@ -137,8 +137,9 @@ foreach(EXTERNAL ${OPTIONAL_EXTERNALS})
set(${${EXTERNAL}_UPPERCASE}_LIBRARIES ${${${EXTERNAL}_UPPERCASE}_LIBRARY})
endif ()
target_link_libraries(${TARGET_NAME} ${${${EXTERNAL}_UPPERCASE}_LIBRARIES})
if (NOT APPLE OR NOT ${${EXTERNAL}_UPPERCASE} MATCHES "SIXENSE")
target_link_libraries(${TARGET_NAME} ${${${EXTERNAL}_UPPERCASE}_LIBRARIES})
endif ()
endif ()
endforeach()

View file

@ -182,7 +182,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
_lastNackTime(usecTimestampNow()),
_lastSendDownstreamAudioStats(usecTimestampNow())
{
// read the ApplicationInfo.ini file for Name/Version/Domain information
QSettings applicationInfo(Application::resourcesPath() + "info/ApplicationInfo.ini", QSettings::IniFormat);
@ -303,9 +302,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
// connect to the domainChangeRequired signal on AddressManager
connect(&addressManager, &AddressManager::possibleDomainChangeRequired,
this, &Application::changeDomainHostname);
// when -url in command line, teleport to location
addressManager.handleLookupString(getCmdOption(argc, constArgv, "-url"));
_settings = new QSettings(this);
_numChangedSettings = 0;
@ -381,12 +377,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
_particleEditSender.setPacketsPerSecond(3000); // super high!!
_entityEditSender.setPacketsPerSecond(3000); // super high!!
// Set the sixense filtering
_sixenseManager.setFilter(Menu::getInstance()->isOptionChecked(MenuOption::FilterSixense));
// Set hand controller velocity filtering
_sixenseManager.setLowVelocityFilter(Menu::getInstance()->isOptionChecked(MenuOption::LowVelocityFilter));
checkVersion();
_overlays.init(_glWidget); // do this before scripts load
@ -1484,7 +1474,7 @@ void Application::setRenderVoxels(bool voxelRender) {
}
void Application::setLowVelocityFilter(bool lowVelocityFilter) {
getSixenseManager()->setLowVelocityFilter(lowVelocityFilter);
SixenseManager::getInstance().setLowVelocityFilter(lowVelocityFilter);
}
void Application::doKillLocalVoxels() {
@ -1797,7 +1787,25 @@ void Application::init() {
Menu::getInstance()->loadSettings();
_audio.setReceivedAudioStreamSettings(Menu::getInstance()->getReceivedAudioStreamSettings());
qDebug("Loaded settings");
qDebug() << "Loaded settings";
// when --url in command line, teleport to location
const QString HIFI_URL_COMMAND_LINE_KEY = "--url";
int urlIndex = arguments().indexOf(HIFI_URL_COMMAND_LINE_KEY);
if (urlIndex != -1) {
AddressManager::getInstance().handleLookupString(arguments().value(urlIndex + 1));
}
#ifdef __APPLE__
if (Menu::getInstance()->isOptionChecked(MenuOption::SixenseEnabled)) {
// on OS X we only setup sixense if the user wants it on - this allows running without the hid_init crash
// if hydra support is temporarily not required
Menu::getInstance()->toggleSixense(true);
}
#else
// setup sixense
Menu::getInstance()->toggleSixense(true);
#endif
// initialize our face trackers after loading the menu settings
_faceshift.init();
@ -2174,7 +2182,7 @@ void Application::update(float deltaTime) {
DeviceTracker::updateAll();
updateFaceshift();
updateVisage();
_sixenseManager.update(deltaTime);
SixenseManager::getInstance().update(deltaTime);
JoystickScriptingInterface::getInstance().update();
_prioVR.update(deltaTime);

View file

@ -219,7 +219,6 @@ public:
DdeFaceTracker* getDDE() { return &_dde; }
CaraFaceTracker* getCara() { return &_cara; }
FaceTracker* getActiveFaceTracker();
SixenseManager* getSixenseManager() { return &_sixenseManager; }
PrioVR* getPrioVR() { return &_prioVR; }
BandwidthMeter* getBandwidthMeter() { return &_bandwidthMeter; }
QUndoStack* getUndoStack() { return &_undoStack; }
@ -510,7 +509,6 @@ private:
CaraFaceTracker _cara;
DdeFaceTracker _dde;
SixenseManager _sixenseManager;
PrioVR _prioVR;
Camera _myCamera; // My view onto the world

View file

@ -431,11 +431,18 @@ Menu::Menu() :
addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::ShowIKConstraints, 0, false);
QMenu* sixenseOptionsMenu = handOptionsMenu->addMenu("Sixense");
#ifdef __APPLE__
addCheckableActionToQMenuAndActionHash(sixenseOptionsMenu,
MenuOption::SixenseEnabled,
0, false,
this,
SLOT(toggleSixense(bool)));
#endif
addCheckableActionToQMenuAndActionHash(sixenseOptionsMenu,
MenuOption::FilterSixense,
0,
true,
appInstance->getSixenseManager(),
&SixenseManager::getInstance(),
SLOT(setFilter(bool)));
addCheckableActionToQMenuAndActionHash(sixenseOptionsMenu,
MenuOption::LowVelocityFilter,
@ -1134,6 +1141,18 @@ void Menu::editAnimations() {
}
}
void Menu::toggleSixense(bool shouldEnable) {
SixenseManager& sixenseManager = SixenseManager::getInstance();
if (shouldEnable && !sixenseManager.isInitialized()) {
sixenseManager.initialize();
sixenseManager.setFilter(isOptionChecked(MenuOption::FilterSixense));
sixenseManager.setLowVelocityFilter(isOptionChecked(MenuOption::LowVelocityFilter));
}
sixenseManager.setIsEnabled(shouldEnable);
}
void Menu::changePrivateKey() {
// setup the dialog
QInputDialog privateKeyDialog(Application::getInstance()->getWindow());

View file

@ -186,6 +186,7 @@ public slots:
void pasteToVoxel();
void toggleLoginMenuItem();
void toggleSixense(bool shouldEnable);
QMenu* addMenu(const QString& menuName);
void removeMenu(const QString& menuName);
@ -448,6 +449,7 @@ namespace MenuOption {
const QString ShowBordersVoxelNodes = "Show Voxel Nodes";
const QString ShowIKConstraints = "Show IK Constraints";
const QString SimpleShadows = "Simple";
const QString SixenseEnabled = "Enable Hydra Support";
const QString SixenseMouseInput = "Enable Sixense Mouse Input";
const QString SixenseLasers = "Enable Sixense UI Lasers";
const QString StandOnNearbyFloors = "Stand on nearby floors";

View file

@ -1204,7 +1204,8 @@ int PointAugmentVisitor::visit(MetavoxelInfo& info) {
_points.swap(swapPoints);
buffer = new PointBuffer(swapPoints);
}
info.outputValues[0] = AttributeValue(_outputs.at(0), encodeInline(BufferDataPointer(buffer)));
BufferDataPointer pointer(buffer);
info.outputValues[0] = AttributeValue(_outputs.at(0), encodeInline(pointer));
}
return STOP_RECURSION;
}
@ -1219,7 +1220,8 @@ bool PointAugmentVisitor::postVisit(MetavoxelInfo& info) {
_points.swap(swapPoints);
buffer = new PointBuffer(swapPoints);
}
info.outputValues[0] = AttributeValue(_outputs.at(0), encodeInline(BufferDataPointer(buffer)));
BufferDataPointer pointer(buffer);
info.outputValues[0] = AttributeValue(_outputs.at(0), encodeInline(pointer));
return true;
}
@ -1446,7 +1448,8 @@ int HeightfieldRegionVisitor::visit(MetavoxelInfo& info) {
_data->guide(_fetchVisitor);
}
}
info.outputValues[0] = AttributeValue(_outputs.at(0), encodeInline(BufferDataPointer(buffer)));
BufferDataPointer pointer(buffer);
info.outputValues[0] = AttributeValue(_outputs.at(0), encodeInline(pointer));
return STOP_RECURSION;
}
@ -1505,7 +1508,8 @@ int HeightfieldUpdateVisitor::visit(MetavoxelInfo& info) {
buffer->getHeight(), buffer->getColor(), buffer->getMaterial(), buffer->getMaterials());
_fetchVisitor.init(newBuffer);
_data->guide(_fetchVisitor);
info.outputValues[0] = AttributeValue(_outputs.at(0), encodeInline(BufferDataPointer(newBuffer)));
BufferDataPointer pointer(newBuffer);
info.outputValues[0] = AttributeValue(_outputs.at(0), encodeInline(pointer));
return STOP_RECURSION;
}
@ -1878,114 +1882,42 @@ int VoxelAugmentVisitor::visit(MetavoxelInfo& info) {
glm::mat3 atrans = glm::transpose(a);
glm::mat3 ata = atrans * a;
// compute the SVD of ata; first, find the eigenvalues
// (see http://en.wikipedia.org/wiki/Eigenvalue_algorithm#3.C3.973_matrices)
glm::vec3 eigenvalues;
float p1 = ata[0][1] * ata[0][1] + ata[0][2] * ata[0][2] + ata[1][2] * ata[1][2];
if (p1 < EPSILON) {
eigenvalues = glm::vec3(ata[0][0], ata[1][1], ata[2][2]);
if (eigenvalues[2] < eigenvalues[1]) {
qSwap(eigenvalues[2], eigenvalues[1]);
// find the eigenvalues and eigenvectors of ata
// (see http://en.wikipedia.org/wiki/Jacobi_eigenvalue_algorithm)
glm::mat3 d = ata;
glm::quat combinedRotation;
const int MAX_ITERATIONS = 20;
for (int i = 0; i < MAX_ITERATIONS; i++) {
glm::vec3 offDiagonals = glm::abs(glm::vec3(d[1][0], d[2][0], d[2][1]));
int largestIndex = (offDiagonals[0] > offDiagonals[1]) ? (offDiagonals[0] > offDiagonals[2] ? 0 : 2) :
(offDiagonals[1] > offDiagonals[2] ? 1 : 2);
const float DESIRED_PRECISION = 0.00001f;
if (offDiagonals[largestIndex] < DESIRED_PRECISION) {
break;
}
if (eigenvalues[1] < eigenvalues[0]) {
qSwap(eigenvalues[1], eigenvalues[0]);
int largestJ = (largestIndex == 2) ? 1 : 0;
int largestI = (largestIndex == 0) ? 1 : 2;
float sjj = d[largestJ][largestJ];
float sii = d[largestI][largestI];
float angle = (sii == sjj ? PI_OVER_TWO : glm::atan(2.0f * d[largestJ][largestI], sjj - sii)) / 2.0f;
glm::quat rotation = glm::angleAxis(angle, largestIndex == 0 ? glm::vec3(0.0f, 0.0f, -1.0f) :
(largestIndex == 1 ? glm::vec3(0.0f, 1.0f, 0.0f) : glm::vec3(-1.0f, 0.0f, 0.0f)));
if (rotation.w == 0.0f) {
break;
}
if (eigenvalues[2] < eigenvalues[1]) {
qSwap(eigenvalues[2], eigenvalues[1]);
}
} else {
float q = (ata[0][0] + ata[1][1] + ata[2][2]) / 3.0f;
float d1 = ata[0][0] - q, d2 = ata[1][1] - q, d3 = ata[2][2] - q;
float p2 = d1 * d1 + d2 * d2 + d3 * d3 + 2.0f * p1;
float p = glm::sqrt(p2 / 6.0f);
glm::mat3 b = (ata - glm::mat3(q)) / p;
float r = glm::determinant(b) / 2.0f;
float phi;
if (r <= -1.0f) {
phi = PI / 3.0f;
} else if (r >= 1.0f) {
phi = 0.0f;
} else {
phi = glm::acos(r) / 3.0f;
}
eigenvalues[2] = q + 2.0f * p * glm::cos(phi);
eigenvalues[0] = q + 2.0f * p * glm::cos(phi + (2.0f * PI / 3.0f));
eigenvalues[1] = 3.0f * q - eigenvalues[0] - eigenvalues[2];
combinedRotation = glm::normalize(rotation * combinedRotation);
glm::mat3 matrix = glm::mat3_cast(combinedRotation);
d = matrix * ata * glm::transpose(matrix);
}
// form the singular matrix from the eigenvalues
glm::mat3 d;
const float MIN_SINGULAR_THRESHOLD = 0.1f;
d[0][0] = (eigenvalues[0] < MIN_SINGULAR_THRESHOLD) ? 0.0f : 1.0f / eigenvalues[0];
d[1][1] = (eigenvalues[1] < MIN_SINGULAR_THRESHOLD) ? 0.0f : 1.0f / eigenvalues[1];
d[2][2] = (eigenvalues[2] < MIN_SINGULAR_THRESHOLD) ? 0.0f : 1.0f / eigenvalues[2];
glm::mat3 m[] = { ata - glm::mat3(eigenvalues[0]), ata - glm::mat3(eigenvalues[1]),
ata - glm::mat3(eigenvalues[2]) };
// form the orthogonal matrix from the eigenvectors
// see http://www.geometrictools.com/Documentation/EigenSymmetric3x3.pdf
bool same01 = glm::abs(eigenvalues[0] - eigenvalues[1]) < EPSILON;
bool same12 = glm::abs(eigenvalues[1] - eigenvalues[2]) < EPSILON;
glm::mat3 u;
if (!(same01 && same12)) {
if (same01 || same12) {
int i = same01 ? 2 : 0;
for (int j = 0; j < 3; j++) {
glm::vec3 first = glm::vec3(m[i][0][j], m[i][1][j], m[i][2][j]);
int j2 = (j + 1) % 3;
glm::vec3 second = glm::vec3(m[i][0][j2], m[i][1][j2], m[i][2][j2]);
glm::vec3 cross = glm::cross(first, second);
float length = glm::length(cross);
if (length > EPSILON) {
u[0][i] = cross[0] / length;
u[1][i] = cross[1] / length;
u[2][i] = cross[2] / length;
break;
}
}
i = (i + 1) % 3;
for (int j = 0; j < 3; j++) {
glm::vec3 first = glm::vec3(m[i][0][j], m[i][1][j], m[i][2][j]);
float length = glm::length(first);
if (length > EPSILON) {
glm::vec3 second = glm::cross(first, glm::vec3(1.0f, 0.0f, 0.0f));
length = glm::length(second);
if (length < EPSILON) {
second = glm::cross(first, glm::vec3(0.0f, 1.0f, 0.0f));
length = glm::length(second);
}
u[0][i] = second[0] / length;
u[1][i] = second[1] / length;
u[2][i] = second[2] / length;
second = glm::normalize(glm::cross(second, first));
i = (i + 1) % 3;
u[0][i] = second[0];
u[1][i] = second[1];
u[2][i] = second[2];
break;
}
}
} else {
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
glm::vec3 first = glm::vec3(m[i][0][j], m[i][1][j], m[i][2][j]);
int j2 = (j + 1) % 3;
glm::vec3 second = glm::vec3(m[i][0][j2], m[i][1][j2], m[i][2][j2]);
glm::vec3 cross = glm::cross(first, second);
float length = glm::length(cross);
if (length > EPSILON) {
u[0][i] = cross[0] / length;
u[1][i] = cross[1] / length;
u[2][i] = cross[2] / length;
break;
}
}
}
}
}
d[0][0] = (d[0][0] < MIN_SINGULAR_THRESHOLD) ? 0.0f : 1.0f / d[0][0];
d[1][1] = (d[1][1] < MIN_SINGULAR_THRESHOLD) ? 0.0f : 1.0f / d[1][1];
d[2][2] = (d[2][2] < MIN_SINGULAR_THRESHOLD) ? 0.0f : 1.0f / d[2][2];
// compute the pseudo-inverse, ataplus, and use to find the minimizing solution
glm::mat3 u = glm::mat3_cast(combinedRotation);
glm::mat3 ataplus = glm::transpose(u) * d * u;
glm::vec3 solution = (ataplus * atrans * b) + center;
@ -2078,7 +2010,8 @@ int VoxelAugmentVisitor::visit(MetavoxelInfo& info) {
buffer = new VoxelBuffer(vertices, indices, material->getMaterials());
}
info.outputValues[0] = AttributeValue(_outputs.at(0), encodeInline(BufferDataPointer(buffer)));
BufferDataPointer pointer(buffer);
info.outputValues[0] = AttributeValue(_outputs.at(0), encodeInline(pointer));
return STOP_RECURSION;
}

View file

@ -30,23 +30,28 @@ const int CALIBRATION_STATE_COMPLETE = 4;
const float NECK_X = 0.25f; // meters
const float NECK_Y = 0.3f; // meters
const float NECK_Z = 0.3f; // meters
#ifdef __APPLE__
typedef int (*SixenseBaseFunction)();
typedef int (*SixenseTakeIntFunction)(int);
typedef int (*SixenseTakeIntAndSixenseControllerData)(int, sixenseControllerData*);
#endif
SixenseManager::SixenseManager() {
#ifdef HAVE_SIXENSE
_lastMovement = 0;
_amountMoved = glm::vec3(0.0f);
_lowVelocityFilter = false;
_calibrationState = CALIBRATION_STATE_IDLE;
// By default we assume the _neckBase (in orb frame) is as high above the orb
// as the "torso" is below it.
_neckBase = glm::vec3(NECK_X, -NECK_Y, NECK_Z);
sixenseInit();
#endif
_hydrasConnected = false;
SixenseManager& SixenseManager::getInstance() {
static SixenseManager sharedInstance;
return sharedInstance;
}
SixenseManager::SixenseManager() :
#ifdef __APPLE__
_sixenseLibrary(NULL),
#endif
_isInitialized(false),
_isEnabled(true),
_hydrasConnected(false)
{
_triggerPressed[0] = false;
_bumperPressed[0] = false;
_oldX[0] = -1;
@ -58,155 +63,229 @@ SixenseManager::SixenseManager() {
}
SixenseManager::~SixenseManager() {
#ifdef HAVE_SIXENSE_
if (_isInitialized) {
#ifdef __APPLE__
SixenseBaseFunction sixenseExit = (SixenseBaseFunction) _sixenseLibrary->resolve("sixenseExit");
#endif
sixenseExit();
}
#ifdef __APPLE__
delete _sixenseLibrary;
#endif
#endif
}
void SixenseManager::initialize() {
#ifdef HAVE_SIXENSE
sixenseExit();
if (!_isInitialized) {
_lastMovement = 0;
_amountMoved = glm::vec3(0.0f);
_lowVelocityFilter = false;
_calibrationState = CALIBRATION_STATE_IDLE;
// By default we assume the _neckBase (in orb frame) is as high above the orb
// as the "torso" is below it.
_neckBase = glm::vec3(NECK_X, -NECK_Y, NECK_Z);
#ifdef __APPLE__
if (!_sixenseLibrary) {
const QString SIXENSE_LIBRARY_NAME = "libsixense_x64.dylib";
_sixenseLibrary = new QLibrary(SIXENSE_LIBRARY_NAME);
}
qDebug() << "Initializing sixense library for hydra support - libsixense_x64.dylib load state is"
<< _sixenseLibrary->isLoaded();
SixenseBaseFunction sixenseInit = (SixenseBaseFunction) _sixenseLibrary->resolve("sixenseInit");
#endif
sixenseInit();
_isInitialized = true;
}
#endif
}
void SixenseManager::setFilter(bool filter) {
#ifdef HAVE_SIXENSE
if (filter) {
sixenseSetFilterEnabled(1);
} else {
sixenseSetFilterEnabled(0);
if (_isInitialized) {
#ifdef __APPLE__
SixenseTakeIntFunction sixenseSetFilterEnabled = (SixenseTakeIntFunction) _sixenseLibrary->resolve("sixenseSetFilterEnabled");
#endif
if (filter) {
sixenseSetFilterEnabled(1);
} else {
sixenseSetFilterEnabled(0);
}
}
#endif
}
void SixenseManager::update(float deltaTime) {
#ifdef HAVE_SIXENSE
// if the controllers haven't been moved in a while, disable
const unsigned int MOVEMENT_DISABLE_SECONDS = 3;
if (usecTimestampNow() - _lastMovement > (MOVEMENT_DISABLE_SECONDS * USECS_PER_SECOND)) {
Hand* hand = Application::getInstance()->getAvatar()->getHand();
for (std::vector<PalmData>::iterator it = hand->getPalms().begin(); it != hand->getPalms().end(); it++) {
it->setActive(false);
}
_lastMovement = usecTimestampNow();
}
if (sixenseGetNumActiveControllers() == 0) {
_hydrasConnected = false;
return;
}
PerformanceTimer perfTimer("sixense");
if (!_hydrasConnected) {
_hydrasConnected = true;
UserActivityLogger::getInstance().connectedDevice("spatial_controller", "hydra");
}
MyAvatar* avatar = Application::getInstance()->getAvatar();
Hand* hand = avatar->getHand();
int maxControllers = sixenseGetMaxControllers();
// we only support two controllers
sixenseControllerData controllers[2];
int numActiveControllers = 0;
for (int i = 0; i < maxControllers && numActiveControllers < 2; i++) {
if (!sixenseIsControllerEnabled(i)) {
continue;
}
sixenseControllerData* data = controllers + numActiveControllers;
++numActiveControllers;
sixenseGetNewestData(i, data);
// Set palm position and normal based on Hydra position/orientation
// Either find a palm matching the sixense controller, or make a new one
PalmData* palm;
bool foundHand = false;
for (size_t j = 0; j < hand->getNumPalms(); j++) {
if (hand->getPalms()[j].getSixenseID() == data->controller_index) {
palm = &(hand->getPalms()[j]);
foundHand = true;
if (_isInitialized && _isEnabled) {
// if the controllers haven't been moved in a while, disable
const unsigned int MOVEMENT_DISABLE_SECONDS = 3;
if (usecTimestampNow() - _lastMovement > (MOVEMENT_DISABLE_SECONDS * USECS_PER_SECOND)) {
Hand* hand = Application::getInstance()->getAvatar()->getHand();
for (std::vector<PalmData>::iterator it = hand->getPalms().begin(); it != hand->getPalms().end(); it++) {
it->setActive(false);
}
}
if (!foundHand) {
PalmData newPalm(hand);
hand->getPalms().push_back(newPalm);
palm = &(hand->getPalms()[hand->getNumPalms() - 1]);
palm->setSixenseID(data->controller_index);
qDebug("Found new Sixense controller, ID %i", data->controller_index);
}
palm->setActive(true);
// Read controller buttons and joystick into the hand
palm->setControllerButtons(data->buttons);
palm->setTrigger(data->trigger);
palm->setJoystick(data->joystick_x, data->joystick_y);
// Emulate the mouse so we can use scripts
if (Menu::getInstance()->isOptionChecked(MenuOption::SixenseMouseInput)) {
emulateMouse(palm, numActiveControllers - 1);
}
// NOTE: Sixense API returns pos data in millimeters but we IMMEDIATELY convert to meters.
glm::vec3 position(data->pos[0], data->pos[1], data->pos[2]);
position *= METERS_PER_MILLIMETER;
// Transform the measured position into body frame.
glm::vec3 neck = _neckBase;
// Zeroing y component of the "neck" effectively raises the measured position a little bit.
neck.y = 0.f;
position = _orbRotation * (position - neck);
// Rotation of Palm
glm::quat rotation(data->rot_quat[3], -data->rot_quat[0], data->rot_quat[1], -data->rot_quat[2]);
rotation = glm::angleAxis(PI, glm::vec3(0.f, 1.f, 0.f)) * _orbRotation * rotation;
// Compute current velocity from position change
glm::vec3 rawVelocity;
if (deltaTime > 0.f) {
rawVelocity = (position - palm->getRawPosition()) / deltaTime;
} else {
rawVelocity = glm::vec3(0.0f);
}
palm->setRawVelocity(rawVelocity); // meters/sec
// adjustment for hydra controllers fit into hands
float sign = (i == 0) ? -1.0f : 1.0f;
rotation *= glm::angleAxis(sign * PI/4.0f, glm::vec3(0.0f, 0.0f, 1.0f));
if (_lowVelocityFilter) {
// Use a velocity sensitive filter to damp small motions and preserve large ones with
// no latency.
float velocityFilter = glm::clamp(1.0f - glm::length(rawVelocity), 0.0f, 1.0f);
position = palm->getRawPosition() * velocityFilter + position * (1.0f - velocityFilter);
rotation = safeMix(palm->getRawRotation(), rotation, 1.0f - velocityFilter);
palm->setRawPosition(position);
palm->setRawRotation(rotation);
} else {
palm->setRawPosition(position);
palm->setRawRotation(rotation);
}
// use the velocity to determine whether there's any movement (if the hand isn't new)
const float MOVEMENT_DISTANCE_THRESHOLD = 0.003f;
_amountMoved += rawVelocity * deltaTime;
if (glm::length(_amountMoved) > MOVEMENT_DISTANCE_THRESHOLD && foundHand) {
_lastMovement = usecTimestampNow();
_amountMoved = glm::vec3(0.0f);
}
// Store the one fingertip in the palm structure so we can track velocity
const float FINGER_LENGTH = 0.3f; // meters
const glm::vec3 FINGER_VECTOR(0.0f, 0.0f, FINGER_LENGTH);
const glm::vec3 newTipPosition = position + rotation * FINGER_VECTOR;
glm::vec3 oldTipPosition = palm->getTipRawPosition();
if (deltaTime > 0.f) {
palm->setTipVelocity((newTipPosition - oldTipPosition) / deltaTime);
} else {
palm->setTipVelocity(glm::vec3(0.f));
#ifdef __APPLE__
SixenseBaseFunction sixenseGetNumActiveControllers =
(SixenseBaseFunction) _sixenseLibrary->resolve("sixenseGetNumActiveControllers");
#endif
if (sixenseGetNumActiveControllers() == 0) {
_hydrasConnected = false;
return;
}
PerformanceTimer perfTimer("sixense");
if (!_hydrasConnected) {
_hydrasConnected = true;
UserActivityLogger::getInstance().connectedDevice("spatial_controller", "hydra");
}
MyAvatar* avatar = Application::getInstance()->getAvatar();
Hand* hand = avatar->getHand();
#ifdef __APPLE__
SixenseBaseFunction sixenseGetMaxControllers =
(SixenseBaseFunction) _sixenseLibrary->resolve("sixenseGetMaxControllers");
#endif
int maxControllers = sixenseGetMaxControllers();
// we only support two controllers
sixenseControllerData controllers[2];
#ifdef __APPLE__
SixenseTakeIntFunction sixenseIsControllerEnabled =
(SixenseTakeIntFunction) _sixenseLibrary->resolve("sixenseIsControllerEnabled");
SixenseTakeIntAndSixenseControllerData sixenseGetNewestData =
(SixenseTakeIntAndSixenseControllerData) _sixenseLibrary->resolve("sixenseGetNewestData");
#endif
int numActiveControllers = 0;
for (int i = 0; i < maxControllers && numActiveControllers < 2; i++) {
if (!sixenseIsControllerEnabled(i)) {
continue;
}
sixenseControllerData* data = controllers + numActiveControllers;
++numActiveControllers;
sixenseGetNewestData(i, data);
// Set palm position and normal based on Hydra position/orientation
// Either find a palm matching the sixense controller, or make a new one
PalmData* palm;
bool foundHand = false;
for (size_t j = 0; j < hand->getNumPalms(); j++) {
if (hand->getPalms()[j].getSixenseID() == data->controller_index) {
palm = &(hand->getPalms()[j]);
foundHand = true;
}
}
if (!foundHand) {
PalmData newPalm(hand);
hand->getPalms().push_back(newPalm);
palm = &(hand->getPalms()[hand->getNumPalms() - 1]);
palm->setSixenseID(data->controller_index);
qDebug("Found new Sixense controller, ID %i", data->controller_index);
}
palm->setActive(true);
// Read controller buttons and joystick into the hand
palm->setControllerButtons(data->buttons);
palm->setTrigger(data->trigger);
palm->setJoystick(data->joystick_x, data->joystick_y);
// Emulate the mouse so we can use scripts
if (Menu::getInstance()->isOptionChecked(MenuOption::SixenseMouseInput)) {
emulateMouse(palm, numActiveControllers - 1);
}
// NOTE: Sixense API returns pos data in millimeters but we IMMEDIATELY convert to meters.
glm::vec3 position(data->pos[0], data->pos[1], data->pos[2]);
position *= METERS_PER_MILLIMETER;
// Transform the measured position into body frame.
glm::vec3 neck = _neckBase;
// Zeroing y component of the "neck" effectively raises the measured position a little bit.
neck.y = 0.f;
position = _orbRotation * (position - neck);
// Rotation of Palm
glm::quat rotation(data->rot_quat[3], -data->rot_quat[0], data->rot_quat[1], -data->rot_quat[2]);
rotation = glm::angleAxis(PI, glm::vec3(0.f, 1.f, 0.f)) * _orbRotation * rotation;
// Compute current velocity from position change
glm::vec3 rawVelocity;
if (deltaTime > 0.f) {
rawVelocity = (position - palm->getRawPosition()) / deltaTime;
} else {
rawVelocity = glm::vec3(0.0f);
}
palm->setRawVelocity(rawVelocity); // meters/sec
// adjustment for hydra controllers fit into hands
float sign = (i == 0) ? -1.0f : 1.0f;
rotation *= glm::angleAxis(sign * PI/4.0f, glm::vec3(0.0f, 0.0f, 1.0f));
if (_lowVelocityFilter) {
// Use a velocity sensitive filter to damp small motions and preserve large ones with
// no latency.
float velocityFilter = glm::clamp(1.0f - glm::length(rawVelocity), 0.0f, 1.0f);
position = palm->getRawPosition() * velocityFilter + position * (1.0f - velocityFilter);
rotation = safeMix(palm->getRawRotation(), rotation, 1.0f - velocityFilter);
palm->setRawPosition(position);
palm->setRawRotation(rotation);
} else {
palm->setRawPosition(position);
palm->setRawRotation(rotation);
}
// use the velocity to determine whether there's any movement (if the hand isn't new)
const float MOVEMENT_DISTANCE_THRESHOLD = 0.003f;
_amountMoved += rawVelocity * deltaTime;
if (glm::length(_amountMoved) > MOVEMENT_DISTANCE_THRESHOLD && foundHand) {
_lastMovement = usecTimestampNow();
_amountMoved = glm::vec3(0.0f);
}
// Store the one fingertip in the palm structure so we can track velocity
const float FINGER_LENGTH = 0.3f; // meters
const glm::vec3 FINGER_VECTOR(0.0f, 0.0f, FINGER_LENGTH);
const glm::vec3 newTipPosition = position + rotation * FINGER_VECTOR;
glm::vec3 oldTipPosition = palm->getTipRawPosition();
if (deltaTime > 0.f) {
palm->setTipVelocity((newTipPosition - oldTipPosition) / deltaTime);
} else {
palm->setTipVelocity(glm::vec3(0.f));
}
palm->setTipPosition(newTipPosition);
}
if (numActiveControllers == 2) {
updateCalibration(controllers);
}
palm->setTipPosition(newTipPosition);
}
if (numActiveControllers == 2) {
updateCalibration(controllers);
}
#endif // HAVE_SIXENSE
}

View file

@ -18,6 +18,11 @@
#include <glm/glm.hpp>
#include <glm/gtc/quaternion.hpp>
#include "sixense.h"
#ifdef __APPLE__
#include <qlibrary.h>
#endif
#endif
const unsigned int BUTTON_0 = 1U << 0; // the skinny button between 1 and 2
@ -38,9 +43,12 @@ const bool DEFAULT_INVERT_SIXENSE_MOUSE_BUTTONS = false;
class SixenseManager : public QObject {
Q_OBJECT
public:
static SixenseManager& getInstance();
SixenseManager();
~SixenseManager();
void initialize();
bool isInitialized() const { return _isInitialized; }
void setIsEnabled(bool isEnabled) { _isEnabled = isEnabled; }
void update(float deltaTime);
float getCursorPixelRangeMult() const;
@ -51,6 +59,9 @@ public slots:
void setLowVelocityFilter(bool lowVelocityFilter) { _lowVelocityFilter = lowVelocityFilter; };
private:
SixenseManager();
~SixenseManager();
#ifdef HAVE_SIXENSE
void updateCalibration(const sixenseControllerData* controllers);
void emulateMouse(PalmData* palm, int index);
@ -72,7 +83,13 @@ private:
glm::vec3 _reachForward;
float _lastDistance;
#ifdef __APPLE__
QLibrary* _sixenseLibrary;
#endif
#endif
bool _isInitialized;
bool _isEnabled;
bool _hydrasConnected;
quint64 _lastMovement;
glm::vec3 _amountMoved;

View file

@ -1335,11 +1335,7 @@ void Model::renderMeshes(RenderMode mode, bool translucent, float alphaThreshold
} else {
glm::vec4 diffuse = glm::vec4(part.diffuseColor, part.opacity);
if (!(translucent && alphaThreshold == 0.0f)) {
float emissive = (part.emissiveColor.r + part.emissiveColor.g + part.emissiveColor.b) / 3.0f;
diffuse.a = qMax(Application::getInstance()->getGlowEffect()->getIntensity(), emissive);
glAlphaFunc(GL_EQUAL, diffuse.a);
diffuse = glm::vec4(qMax(diffuse.r, part.emissiveColor.r), qMax(diffuse.g, part.emissiveColor.g),
qMax(diffuse.b, part.emissiveColor.b), diffuse.a);
glAlphaFunc(GL_EQUAL, diffuse.a = Application::getInstance()->getGlowEffect()->getIntensity());
}
glm::vec4 specular = glm::vec4(part.specularColor, 1.0f);
glMaterialfv(GL_FRONT, GL_AMBIENT, (const float*)&diffuse);

View file

@ -671,7 +671,7 @@ void ApplicationOverlay::renderControllerPointers() {
float yAngle = 0.5f - ((atan2(direction.z, direction.y) + M_PI_2));
// Get the pixel range over which the xAngle and yAngle are scaled
float cursorRange = glWidget->width() * application->getSixenseManager()->getCursorPixelRangeMult();
float cursorRange = glWidget->width() * SixenseManager::getInstance().getCursorPixelRangeMult();
mouseX = (glWidget->width() / 2.0f + cursorRange * xAngle);
mouseY = (glWidget->height() / 2.0f + cursorRange * yAngle);

View file

@ -21,7 +21,7 @@
#include "LoginDialog.h"
const QString FORGOT_PASSWORD_URL = "https://data.highfidelity.io/password/new";
const QString FORGOT_PASSWORD_URL = "https://data.highfidelity.io/users/password/new";
LoginDialog::LoginDialog(QWidget* parent) :
FramelessDialog(parent, 0, FramelessDialog::POSITION_TOP),

View file

@ -87,43 +87,48 @@ QVariantMap HifiConfigVariantMap::mergeCLParametersWithJSONConfig(const QStringL
return mergedMap;
}
QVariantMap HifiConfigVariantMap::mergeMasterConfigWithUserConfig(const QStringList& argumentList) {
HifiConfigVariantMap::HifiConfigVariantMap() :
_userConfigFilename(),
_masterConfig(),
_userConfig(),
_mergedConfig()
{
}
void HifiConfigVariantMap::loadMasterAndUserConfig(const QStringList& argumentList) {
// check if there is a master config file
const QString MASTER_CONFIG_FILE_OPTION = "--master-config";
QVariantMap configVariantMap;
int masterConfigIndex = argumentList.indexOf(MASTER_CONFIG_FILE_OPTION);
if (masterConfigIndex != -1) {
QString masterConfigFilepath = argumentList[masterConfigIndex + 1];
mergeMapWithJSONFile(configVariantMap, masterConfigFilepath);
loadMapFromJSONFile(_masterConfig, masterConfigFilepath);
}
// merge the existing configVariantMap with the user config file
mergeMapWithJSONFile(configVariantMap, userConfigFilepath(argumentList));
return configVariantMap;
}
QString HifiConfigVariantMap::userConfigFilepath(const QStringList& argumentList) {
// we've loaded up the master config file, now fill in anything it didn't have with the user config file
// load the user config
const QString USER_CONFIG_FILE_OPTION = "--user-config";
int userConfigIndex = argumentList.indexOf(USER_CONFIG_FILE_OPTION);
QString userConfigFilepath;
if (userConfigIndex != -1) {
userConfigFilepath = argumentList[userConfigIndex + 1];
_userConfigFilename = argumentList[userConfigIndex + 1];
} else {
userConfigFilepath = QString("%1/%2/%3/config.json").arg(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation),
QCoreApplication::organizationName(),
QCoreApplication::applicationName());
_userConfigFilename = QString("%1/%2/%3/config.json").arg(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation),
QCoreApplication::organizationName(),
QCoreApplication::applicationName());
}
return userConfigFilepath;
loadMapFromJSONFile(_userConfig, _userConfigFilename);
// the merged config is initially matched to the master config
_mergedConfig = _masterConfig;
// then we merge in anything missing from the user config
addMissingValuesToExistingMap(_mergedConfig, _userConfig);
}
void HifiConfigVariantMap::mergeMapWithJSONFile(QVariantMap& existingMap, const QString& filename) {
void HifiConfigVariantMap::loadMapFromJSONFile(QVariantMap& existingMap, const QString& filename) {
QFile configFile(filename);
if (configFile.exists()) {
@ -131,12 +136,7 @@ void HifiConfigVariantMap::mergeMapWithJSONFile(QVariantMap& existingMap, const
configFile.open(QIODevice::ReadOnly);
QJsonDocument configDocument = QJsonDocument::fromJson(configFile.readAll());
if (existingMap.isEmpty()) {
existingMap = configDocument.toVariant().toMap();
} else {
addMissingValuesToExistingMap(existingMap, configDocument.toVariant().toMap());
}
existingMap = configDocument.toVariant().toMap();
} else {
qDebug() << "Could not find JSON config file at" << filename;

View file

@ -17,11 +17,24 @@
class HifiConfigVariantMap {
public:
static QVariantMap mergeCLParametersWithJSONConfig(const QStringList& argumentList);
static QVariantMap mergeMasterConfigWithUserConfig(const QStringList& argumentList);
static QString userConfigFilepath(const QStringList& argumentList);
HifiConfigVariantMap();
void loadMasterAndUserConfig(const QStringList& argumentList);
const QVariantMap& getMasterConfig() const { return _masterConfig; }
QVariantMap& getUserConfig() { return _userConfig; }
QVariantMap& getMergedConfig() { return _mergedConfig; }
const QString& getUserConfigFilename() const { return _userConfigFilename; }
private:
static void mergeMapWithJSONFile(QVariantMap& existingMap, const QString& filename);
static void addMissingValuesToExistingMap(QVariantMap& existingMap, const QVariantMap& newMap);
QString _userConfigFilename;
QVariantMap _masterConfig;
QVariantMap _userConfig;
QVariantMap _mergedConfig;
void loadMapFromJSONFile(QVariantMap& existingMap, const QString& filename);
void addMissingValuesToExistingMap(QVariantMap& existingMap, const QVariantMap& newMap);
};
const QVariant* valueForKeyPath(QVariantMap& variantMap, const QString& keyPath);