Merge branch 'ui' of github.com:SamGondelman/hifi into ui

This commit is contained in:
Dante Ruiz 2017-11-06 16:39:10 -08:00
commit 3e9eaa727b
93 changed files with 2507 additions and 1496 deletions

View file

@ -50,9 +50,9 @@ static const int INTERFACE_RUNNING_CHECK_FREQUENCY_MS = 1000;
const QString ASSET_SERVER_LOGGING_TARGET_NAME = "asset-server"; const QString ASSET_SERVER_LOGGING_TARGET_NAME = "asset-server";
static const QStringList BAKEABLE_MODEL_EXTENSIONS = {"fbx"}; static const QStringList BAKEABLE_MODEL_EXTENSIONS = { "fbx" };
static QStringList BAKEABLE_TEXTURE_EXTENSIONS; static QStringList BAKEABLE_TEXTURE_EXTENSIONS;
static const QStringList BAKEABLE_SCRIPT_EXTENSIONS = {"js"}; static const QStringList BAKEABLE_SCRIPT_EXTENSIONS = {};
static const QString BAKED_MODEL_SIMPLE_NAME = "asset.fbx"; static const QString BAKED_MODEL_SIMPLE_NAME = "asset.fbx";
static const QString BAKED_TEXTURE_SIMPLE_NAME = "texture.ktx"; static const QString BAKED_TEXTURE_SIMPLE_NAME = "texture.ktx";
static const QString BAKED_SCRIPT_SIMPLE_NAME = "asset.js"; static const QString BAKED_SCRIPT_SIMPLE_NAME = "asset.js";

View file

@ -41,8 +41,15 @@ EntityServer::EntityServer(ReceivedMessage& message) :
DependencyManager::set<ScriptCache>(); DependencyManager::set<ScriptCache>();
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver(); auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
packetReceiver.registerListenerForTypes({ PacketType::EntityAdd, PacketType::EntityEdit, PacketType::EntityErase, PacketType::EntityPhysics, PacketType::ChallengeOwnership }, packetReceiver.registerListenerForTypes({ PacketType::EntityAdd,
this, "handleEntityPacket"); PacketType::EntityEdit,
PacketType::EntityErase,
PacketType::EntityPhysics,
PacketType::ChallengeOwnership,
PacketType::ChallengeOwnershipRequest,
PacketType::ChallengeOwnershipReply },
this,
"handleEntityPacket");
connect(&_dynamicDomainVerificationTimer, &QTimer::timeout, this, &EntityServer::startDynamicDomainVerification); connect(&_dynamicDomainVerificationTimer, &QTimer::timeout, this, &EntityServer::startDynamicDomainVerification);
_dynamicDomainVerificationTimer.setSingleShot(true); _dynamicDomainVerificationTimer.setSingleShot(true);
@ -459,7 +466,7 @@ void EntityServer::startDynamicDomainVerification() {
EntityItemPointer entity = tree->findEntityByEntityItemID(i.value()); EntityItemPointer entity = tree->findEntityByEntityItemID(i.value());
if (entity) { if (entity) {
if (!entity->verifyStaticCertificateProperties()) { if (!entity->getProperties().verifyStaticCertificateProperties()) {
qCDebug(entities) << "During Dynamic Domain Verification, a certified entity with ID" << i.value() << "failed" qCDebug(entities) << "During Dynamic Domain Verification, a certified entity with ID" << i.value() << "failed"
<< "static certificate verification."; << "static certificate verification.";
// Delete the entity if it doesn't pass static certificate verification // Delete the entity if it doesn't pass static certificate verification

View file

@ -96,6 +96,14 @@ void OctreeInboundPacketProcessor::processPacket(QSharedPointer<ReceivedMessage>
_myServer->getOctree()->withWriteLock([&] { _myServer->getOctree()->withWriteLock([&] {
_myServer->getOctree()->processChallengeOwnershipPacket(*message, sendingNode); _myServer->getOctree()->processChallengeOwnershipPacket(*message, sendingNode);
}); });
} else if (packetType == PacketType::ChallengeOwnershipRequest) {
_myServer->getOctree()->withWriteLock([&] {
_myServer->getOctree()->processChallengeOwnershipRequestPacket(*message, sendingNode);
});
} else if (packetType == PacketType::ChallengeOwnershipReply) {
_myServer->getOctree()->withWriteLock([&] {
_myServer->getOctree()->processChallengeOwnershipReplyPacket(*message, sendingNode);
});
} else if (_myServer->getOctree()->handlesEditPacketType(packetType)) { } else if (_myServer->getOctree()->handlesEditPacketType(packetType)) {
PerformanceWarning warn(debugProcessPacket, "processPacket KNOWN TYPE", debugProcessPacket); PerformanceWarning warn(debugProcessPacket, "processPacket KNOWN TYPE", debugProcessPacket);
_receivedPacketCount++; _receivedPacketCount++;

View file

@ -6,8 +6,8 @@ if (WIN32)
include(ExternalProject) include(ExternalProject)
ExternalProject_Add( ExternalProject_Add(
${EXTERNAL_NAME} ${EXTERNAL_NAME}
URL http://hifi-public.s3.amazonaws.com/dependencies/qtaudio_wasapi9.zip URL http://hifi-public.s3.amazonaws.com/dependencies/qtaudio_wasapi10.zip
URL_MD5 94f4765bdbcd53cd099f349ae031e769 URL_MD5 4f40e49715a420fb67b45b9cee19052c
CONFIGURE_COMMAND "" CONFIGURE_COMMAND ""
BUILD_COMMAND "" BUILD_COMMAND ""
INSTALL_COMMAND "" INSTALL_COMMAND ""

View file

@ -60,7 +60,7 @@ if (WIN32 AND NOT CYGWIN)
select_library_configurations(LIB_EAY) select_library_configurations(LIB_EAY)
select_library_configurations(SSL_EAY) select_library_configurations(SSL_EAY)
set(OPENSSL_LIBRARIES ${SSL_EAY_LIBRARY} ${LIB_EAY_LIBRARY}) set(OPENSSL_LIBRARIES ${SSL_EAY_LIBRARY} ${LIB_EAY_LIBRARY})
find_path(OPENSSL_DLL_PATH NAMES ssleay32.dll PATH_SUFFIXES "bin" ${_OPENSSL_ROOT_HINTS_AND_PATHS}) find_path(OPENSSL_DLL_PATH NAMES ssleay32.dll PATH_SUFFIXES "bin" HINTS ${_OPENSSL_ROOT_HINTS_AND_PATHS} NO_DEFAULT_PATH)
endif() endif()
else() else()

View file

@ -146,187 +146,256 @@ function sendUpdatePlaceRequest(id, path, domainID, clearDomainID, onSuccess, on
}); });
} }
var pendingDomainRequest = null;
function getDomainFromAPI(callback) {
if (pendingDomainRequest !== null) {
pendingDomainRequest.success(callback);
pendingDomainRequest.error(function() { callback({ status: 'fail' }) });
return pendingDomainRequest;
}
if (callback === undefined) {
callback = function() {};
}
var domainID = Settings.data.values.metaverse.id;
if (domainID === null || domainID === undefined || domainID === '') {
callback({ status: 'fail' });
return null;
}
pendingDomainRequest = $.ajax({
url: "/api/domains/" + domainID,
dataType: 'json',
success: function(data) {
pendingDomainRequest = null;
if (data.status === 'success') {
DomainInfo = data.domain;
} else {
DomainInfo = null;
}
callback(data);
},
error: function() {
pendingDomainRequest = null;
DomainInfo = null;
callback({ status: 'fail' });
}
});
return pendingDomainRequest;
}
function chooseFromHighFidelityPlaces(accessToken, forcePathTo, onSuccessfullyAdded) { function chooseFromHighFidelityPlaces(accessToken, forcePathTo, onSuccessfullyAdded) {
if (accessToken) { if (accessToken) {
var loadingDialog = showLoadingDialog(Strings.ADD_PLACE_LOADING_DIALOG); var loadingDialog = showLoadingDialog(Strings.ADD_PLACE_LOADING_DIALOG);
$.ajax("/api/places", { function loadPlaces() {
dataType: 'json', $.ajax("/api/places", {
jsonp: false, dataType: 'json',
success: function(data) { jsonp: false,
if (data.status == 'success') { success: function(data) {
var modal_buttons = { if (data.status == 'success') {
cancel: { var modal_buttons = {
label: Strings.ADD_PLACE_CANCEL_BUTTON, cancel: {
className: 'add-place-cancel-button btn-default' label: Strings.ADD_PLACE_CANCEL_BUTTON,
} className: 'add-place-cancel-button btn-default'
};
var dialog;
var modal_body;
if (data.data.places.length) {
var places_by_id = {};
modal_body = $('<div>');
modal_body.append($("<p>Choose a place name that you own or <a href='" + URLs.METAVERSE_URL + "/user/places' target='_blank'>register a new place name</a></p>"));
var currentDomainIDType = getCurrentDomainIDType();
if (currentDomainIDType === DOMAIN_ID_TYPE_TEMP) {
var warning = "<div class='domain-loading-error alert alert-warning'>";
warning += "If you choose a place name it will replace your current temporary place name.";
warning += "</div>";
modal_body.append(warning);
}
// setup a select box for the returned places
modal_body.append($("<label for='place-name-select'>Places</label>"));
place_select = $("<select id='place-name-select' class='form-control'></select>");
_.each(data.data.places, function(place) {
places_by_id[place.id] = place;
place_select.append("<option value='" + place.id + "'>" + place.name + "</option>");
})
modal_body.append(place_select);
modal_body.append($("<p id='place-name-warning' class='warning-text' style='display: none'>This place name already points to a place or path. Saving this would overwrite the previous settings associated with it.</p>"));
if (forcePathTo === undefined || forcePathTo === null) {
var path = "<div class='form-group'>";
path += "<label for='place-path-input' class='control-label'>Path</label>";
path += "<input type='text' id='place-path-input' class='form-control' value='/'>";
path += "</div>";
modal_body.append($(path));
}
var place_select = modal_body.find("#place-name-select")
place_select.change(function(ev) {
var warning = modal_body.find("#place-name-warning");
var place = places_by_id[$(this).val()];
if (place === undefined || place.pointee === null) {
warning.hide();
} else {
warning.show();
} }
}); };
place_select.trigger('change');
modal_buttons["success"] = { var dialog;
label: Strings.ADD_PLACE_CONFIRM_BUTTON, var modal_body;
className: 'add-place-confirm-button btn btn-primary',
callback: function() {
var placeID = $('#place-name-select').val();
// set the place ID on the form
$(Settings.place_ID_SELECTOR).val(placeID).change();
if (forcePathTo === undefined || forcePathTo === null) { if (data.data.places.length) {
var placePath = $('#place-path-input').val(); var places_by_id = {};
modal_body = $('<div>');
modal_body.append($("<p>Choose a place name that you own or <a href='" + URLs.METAVERSE_URL + "/user/places' target='_blank'>register a new place name</a></p>"));
var currentDomainIDType = getCurrentDomainIDType();
if (currentDomainIDType === DOMAIN_ID_TYPE_TEMP) {
var warning = "<div class='domain-loading-error alert alert-warning'>";
warning += "If you choose a place name it will replace your current temporary place name.";
warning += "</div>";
modal_body.append(warning);
}
// setup a select box for the returned places
modal_body.append($("<label for='place-name-select'>Places</label>"));
place_select = $("<select id='place-name-select' class='form-control'></select>");
_.each(data.data.places, function(place) {
places_by_id[place.id] = place;
place_select.append("<option value='" + place.id + "'>" + place.name + "</option>");
})
modal_body.append(place_select);
modal_body.append($("<p id='place-name-warning' class='warning-text' style='display: none'>This place name already points to a place or path. Saving this would overwrite the previous settings associated with it.</p>"));
if (forcePathTo === undefined || forcePathTo === null) {
var path = "<div class='form-group'>";
path += "<label for='place-path-input' class='control-label'>Path</label>";
path += "<input type='text' id='place-path-input' class='form-control' value='/'>";
path += "</div>";
modal_body.append($(path));
}
var place_select = modal_body.find("#place-name-select")
place_select.change(function(ev) {
var warning = modal_body.find("#place-name-warning");
var place = places_by_id[$(this).val()];
if (place === undefined || place.pointee === null) {
warning.hide();
} else { } else {
var placePath = forcePathTo; warning.show();
} }
});
place_select.trigger('change');
$('.add-place-confirm-button').attr('disabled', 'disabled'); modal_buttons["success"] = {
$('.add-place-confirm-button').html(Strings.ADD_PLACE_CONFIRM_BUTTON_PENDING); label: Strings.ADD_PLACE_CONFIRM_BUTTON,
$('.add-place-cancel-button').attr('disabled', 'disabled'); className: 'add-place-confirm-button btn btn-primary',
callback: function() {
var placeID = $('#place-name-select').val();
// set the place ID on the form
$(Settings.place_ID_SELECTOR).val(placeID).change();
function finalizeSaveDomainID(domainID) { if (forcePathTo === undefined || forcePathTo === null) {
var jsonSettings = { var placePath = $('#place-path-input').val();
metaverse: { } else {
id: domainID var placePath = forcePathTo;
}
$('.add-place-confirm-button').attr('disabled', 'disabled');
$('.add-place-confirm-button').html(Strings.ADD_PLACE_CONFIRM_BUTTON_PENDING);
$('.add-place-cancel-button').attr('disabled', 'disabled');
function finalizeSaveDomainID(domainID) {
var jsonSettings = {
metaverse: {
id: domainID
}
}
var dialog = showLoadingDialog("Waiting for Domain Server to restart...");
$.ajax('/settings.json', {
data: JSON.stringify(jsonSettings),
contentType: 'application/json',
type: 'POST'
}).done(function(data) {
if (data.status == "success") {
waitForDomainServerRestart(function() {
dialog.modal('hide');
if (onSuccessfullyAdded) {
onSuccessfullyAdded(places_by_id[placeID].name, domainID);
}
});
} else {
bootbox.alert("Failed to add place");
}
}).fail(function() {
bootbox.alert("Failed to add place");
});
}
// If domainID is not specified, the current domain id will be used.
function finishSettingUpPlace(domainID) {
sendUpdatePlaceRequest(
placeID,
placePath,
domainID,
false,
function(data) {
dialog.modal('hide')
if (domainID) {
$(Settings.DOMAIN_ID_SELECTOR).val(domainID).change();
finalizeSaveDomainID(domainID);
} else {
if (onSuccessfullyAdded) {
onSuccessfullyAdded(places_by_id[placeID].name);
}
}
},
function(data) {
$('.add-place-confirm-button').removeAttr('disabled');
$('.add-place-confirm-button').html(Strings.ADD_PLACE_CONFIRM_BUTTON);
$('.add-place-cancel-button').removeAttr('disabled');
bootbox.alert(Strings.ADD_PLACE_UNKNOWN_ERROR);
}
);
}
function maybeCreateNewDomainID() {
console.log("Maybe creating domain id", currentDomainIDType)
if (currentDomainIDType === DOMAIN_ID_TYPE_FULL) {
finishSettingUpPlace();
} else {
sendCreateDomainRequest(function(domainID) {
console.log("Created domain", domainID);
finishSettingUpPlace(domainID);
}, function() {
$('.add-place-confirm-button').removeAttr('disabled');
$('.add-place-confirm-button').html(Strings.ADD_PLACE_CONFIRM_BUTTON);
$('.add-place-cancel-button').removeAttr('disabled');
bootbox.alert(Strings.ADD_PLACE_UNKNOWN_ERROR);
bootbox.alert("FAIL");
});
} }
} }
var dialog = showLoadingDialog("Waiting for Domain Server to restart...");
$.ajax('/settings.json', {
data: JSON.stringify(jsonSettings),
contentType: 'application/json',
type: 'POST'
}).done(function(data) {
if (data.status == "success") {
waitForDomainServerRestart(function() {
dialog.modal('hide');
if (onSuccessfullyAdded) {
onSuccessfullyAdded(places_by_id[placeID].name, domainID);
}
});
} else {
bootbox.alert("Failed to add place");
}
}).fail(function() {
bootbox.alert("Failed to add place");
});
}
function finishSettingUpPlace(domainID) { maybeCreateNewDomainID();
sendUpdatePlaceRequest(
placeID,
placePath,
domainID,
false,
function(data) {
$(Settings.DOMAIN_ID_SELECTOR).val(domainID).change();
dialog.modal('hide')
if (domainID) {
finalizeSaveDomainID(domainID);
} else {
if (onSuccessfullyAdded) {
onSuccessfullyAdded(places_by_id[placeID].name);
}
}
},
function(data) {
$('.add-place-confirm-button').removeAttr('disabled');
$('.add-place-confirm-button').html(Strings.ADD_PLACE_CONFIRM_BUTTON);
$('.add-place-cancel-button').removeAttr('disabled');
bootbox.alert(Strings.ADD_PLACE_UNKNOWN_ERROR);
}
);
}
if (currentDomainIDType === DOMAIN_ID_TYPE_FULL) { return false;
finishSettingUpPlace();
} else {
sendCreateDomainRequest(function(domainID) {
console.log("Created domain", domainID);
finishSettingUpPlace(domainID);
}, function() {
$('.add-place-confirm-button').removeAttr('disabled');
$('.add-place-confirm-button').html(Strings.ADD_PLACE_CONFIRM_BUTTON);
$('.add-place-cancel-button').removeAttr('disabled');
bootbox.alert(Strings.ADD_PLACE_UNKNOWN_ERROR);
bootbox.alert("FAIL");
});
} }
return false;
} }
} else {
modal_buttons["success"] = {
label: Strings.ADD_PLACE_NO_PLACES_BUTTON,
callback: function() {
window.open(URLs.METAVERSE_URL + "/user/places", '_blank');
}
}
modal_body = Strings.ADD_PLACE_NO_PLACES_MESSAGE;
} }
dialog = bootbox.dialog({
title: Strings.ADD_PLACE_TITLE,
message: modal_body,
closeButton: false,
buttons: modal_buttons
});
} else { } else {
modal_buttons["success"] = { bootbox.alert(Strings.ADD_PLACE_UNABLE_TO_LOAD_ERROR);
label: Strings.ADD_PLACE_NO_PLACES_BUTTON,
callback: function() {
window.open(URLs.METAVERSE_URL + "/user/places", '_blank');
}
}
modal_body = Strings.ADD_PLACE_NO_PLACES_MESSAGE;
} }
},
dialog = bootbox.dialog({ error: function() {
title: Strings.ADD_PLACE_TITLE,
message: modal_body,
closeButton: false,
buttons: modal_buttons
});
} else {
bootbox.alert(Strings.ADD_PLACE_UNABLE_TO_LOAD_ERROR); bootbox.alert(Strings.ADD_PLACE_UNABLE_TO_LOAD_ERROR);
},
complete: function() {
loadingDialog.modal('hide');
} }
}, });
error: function() { }
bootbox.alert(Strings.ADD_PLACE_UNABLE_TO_LOAD_ERROR);
}, var domainType = getCurrentDomainIDType();
complete: function() { if (domainType !== DOMAIN_ID_TYPE_UNKNOWN) {
loadingDialog.modal('hide'); loadPlaces();
} } else {
}); getDomainFromAPI(function(data) {
if (data.status === 'success') {
var domainType = getCurrentDomainIDType();
loadPlaces();
} else {
loadingDialog.modal('hide');
bootbox.confirm("We were not able to load your domain information from the Metaverse. Would you like to retry?", function(response) {
if (response) {
chooseFromHighFidelityPlaces(accessToken, forcePathTo, onSuccessfullyAdded);
}
});
}
})
}
} else { } else {
bootbox.alert({ bootbox.alert({

View file

@ -980,20 +980,6 @@ function placeTableRowForPlaceObject(place) {
return placeTableRow(place.name, placePathOrIndex, false, place.id); return placeTableRow(place.name, placePathOrIndex, false, place.id);
} }
function getDomainFromAPI(callback) {
var domainID = Settings.data.values.metaverse.id;
$.ajax({
url: "/api/domains/" + domainID,
dataType: 'json',
success: function(data) {
callback(data);
},
error: function() {
callback({ status: 'fail' });
}
});
}
function reloadDomainInfo() { function reloadDomainInfo() {
$('#' + Settings.PLACES_TABLE_ID + " tbody tr").not('.headers').remove(); $('#' + Settings.PLACES_TABLE_ID + " tbody tr").not('.headers').remove();
@ -1010,7 +996,6 @@ function reloadDomainInfo() {
// check if we have owner_places (for a real domain) or a name (for a temporary domain) // check if we have owner_places (for a real domain) or a name (for a temporary domain)
if (data.status == "success") { if (data.status == "success") {
$('.domain-loading-hide').show(); $('.domain-loading-hide').show();
DomainInfo = data.domain;
if (data.domain.owner_places) { if (data.domain.owner_places) {
// add a table row for each of these names // add a table row for each of these names
_.each(data.domain.owner_places, function(place){ _.each(data.domain.owner_places, function(place){
@ -1043,7 +1028,6 @@ function reloadDomainInfo() {
appendAddButtonToPlacesTable(); appendAddButtonToPlacesTable();
} else { } else {
DomainInfo = null;
$('.domain-loading-error').show(); $('.domain-loading-error').show();
} }
}) })

View file

@ -58,6 +58,7 @@ $(document).ready(function(){
reloadSettings(function(success) { reloadSettings(function(success) {
if (success) { if (success) {
getDomainFromAPI();
setupWizardSteps(); setupWizardSteps();
updatePlaceNameDisplay(); updatePlaceNameDisplay();
updateUsernameDisplay(); updateUsernameDisplay();

View file

@ -830,26 +830,6 @@ void DomainServer::setupICEHeartbeatForFullNetworking() {
void DomainServer::updateICEServerAddresses() { void DomainServer::updateICEServerAddresses() {
if (_iceAddressLookupID == INVALID_ICE_LOOKUP_ID) { if (_iceAddressLookupID == INVALID_ICE_LOOKUP_ID) {
_iceAddressLookupID = QHostInfo::lookupHost(_iceServerAddr, this, SLOT(handleICEHostInfo(QHostInfo))); _iceAddressLookupID = QHostInfo::lookupHost(_iceServerAddr, this, SLOT(handleICEHostInfo(QHostInfo)));
// there seems to be a 5.9 bug where lookupHost never calls our slot
// so we add a single shot manual "timeout" to fire it off again if it hasn't called back yet
static const int ICE_ADDRESS_LOOKUP_TIMEOUT_MS = 5000;
QTimer::singleShot(ICE_ADDRESS_LOOKUP_TIMEOUT_MS, this, &DomainServer::timeoutICEAddressLookup);
}
}
void DomainServer::timeoutICEAddressLookup() {
if (_iceAddressLookupID != INVALID_ICE_LOOKUP_ID) {
// we waited 5s and didn't hear back for our ICE DNS lookup
// so time that one out and kick off another
qDebug() << "IP address lookup timed out for" << _iceServerAddr << "- retrying";
QHostInfo::abortHostLookup(_iceAddressLookupID);
_iceAddressLookupID = INVALID_ICE_LOOKUP_ID;
updateICEServerAddresses();
} }
} }
@ -3007,9 +2987,20 @@ void DomainServer::handleKeypairChange() {
void DomainServer::handleICEHostInfo(const QHostInfo& hostInfo) { void DomainServer::handleICEHostInfo(const QHostInfo& hostInfo) {
// clear the ICE address lookup ID so that it can fire again // clear the ICE address lookup ID so that it can fire again
_iceAddressLookupID = -1; _iceAddressLookupID = INVALID_ICE_LOOKUP_ID;
if (hostInfo.error() != QHostInfo::NoError) { // enumerate the returned addresses and collect only valid IPv4 addresses
QList<QHostAddress> sanitizedAddresses = hostInfo.addresses();
auto it = sanitizedAddresses.begin();
while (it != sanitizedAddresses.end()) {
if (!it->isNull() && it->protocol() == QAbstractSocket::IPv4Protocol) {
++it;
} else {
it = sanitizedAddresses.erase(it);
}
}
if (hostInfo.error() != QHostInfo::NoError || sanitizedAddresses.empty()) {
qWarning() << "IP address lookup failed for" << _iceServerAddr << ":" << hostInfo.errorString(); qWarning() << "IP address lookup failed for" << _iceServerAddr << ":" << hostInfo.errorString();
// if we don't have an ICE server to use yet, trigger a retry // if we don't have an ICE server to use yet, trigger a retry
@ -3022,7 +3013,7 @@ void DomainServer::handleICEHostInfo(const QHostInfo& hostInfo) {
} else { } else {
int countBefore = _iceServerAddresses.count(); int countBefore = _iceServerAddresses.count();
_iceServerAddresses = hostInfo.addresses(); _iceServerAddresses = sanitizedAddresses;
if (countBefore == 0) { if (countBefore == 0) {
qInfo() << "Found" << _iceServerAddresses.count() << "ice-server IP addresses for" << _iceServerAddr; qInfo() << "Found" << _iceServerAddresses.count() << "ice-server IP addresses for" << _iceServerAddr;

View file

@ -116,8 +116,6 @@ private slots:
void tokenGrantFinished(); void tokenGrantFinished();
void profileRequestFinished(); void profileRequestFinished();
void timeoutICEAddressLookup();
signals: signals:
void iceServerChanged(); void iceServerChanged();
void userConnected(); void userConnected();

View file

@ -29,6 +29,7 @@
#include <NLPacketList.h> #include <NLPacketList.h>
#include <NumericalConstants.h> #include <NumericalConstants.h>
#include <SettingHandle.h> #include <SettingHandle.h>
#include <SettingHelpers.h>
#include <AvatarData.h> //for KillAvatarReason #include <AvatarData.h> //for KillAvatarReason
#include <FingerprintUtils.h> #include <FingerprintUtils.h>
#include "DomainServerNodeData.h" #include "DomainServerNodeData.h"
@ -43,12 +44,7 @@ const QString DESCRIPTION_COLUMNS_KEY = "columns";
const QString SETTINGS_VIEWPOINT_KEY = "viewpoint"; const QString SETTINGS_VIEWPOINT_KEY = "viewpoint";
static Setting::Handle<double> JSON_SETTING_VERSION("json-settings/version", 0.0); DomainServerSettingsManager::DomainServerSettingsManager() {
DomainServerSettingsManager::DomainServerSettingsManager() :
_descriptionArray(),
_configMap()
{
// load the description object from the settings description // load the description object from the settings description
QFile descriptionFile(QCoreApplication::applicationDirPath() + SETTINGS_DESCRIPTION_RELATIVE_PATH); QFile descriptionFile(QCoreApplication::applicationDirPath() + SETTINGS_DESCRIPTION_RELATIVE_PATH);
descriptionFile.open(QIODevice::ReadOnly); descriptionFile.open(QIODevice::ReadOnly);
@ -100,12 +96,34 @@ void DomainServerSettingsManager::processSettingsRequestPacket(QSharedPointer<Re
void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList) { void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList) {
_argumentList = argumentList; _argumentList = argumentList;
// after 1.7 we no longer use the master or merged configs - this is kept in place for migration _configMap.loadConfig(_argumentList);
_configMap.loadMasterAndUserConfig(_argumentList);
static const auto VERSION_SETTINGS_KEYPATH = "version";
QVariant* versionVariant = _configMap.valueForKeyPath(VERSION_SETTINGS_KEYPATH);
if (!versionVariant) {
versionVariant = _configMap.valueForKeyPath(VERSION_SETTINGS_KEYPATH, true);
*versionVariant = _descriptionVersion;
persistToFile();
qDebug() << "No version in config file, setting to current version" << _descriptionVersion;
}
{
// Backward compatibility migration code
// The config version used to be stored in a different file
// This moves it to the actual config file.
Setting::Handle<double> JSON_SETTING_VERSION("json-settings/version", 0.0);
if (JSON_SETTING_VERSION.isSet()) {
auto version = JSON_SETTING_VERSION.get();
*versionVariant = version;
persistToFile();
QFile::remove(settingsFilename());
}
}
// What settings version were we before and what are we using now? // What settings version were we before and what are we using now?
// Do we need to do any re-mapping? // Do we need to do any re-mapping?
double oldVersion = JSON_SETTING_VERSION.get(); double oldVersion = versionVariant->toDouble();
if (oldVersion != _descriptionVersion) { if (oldVersion != _descriptionVersion) {
const QString ALLOWED_USERS_SETTINGS_KEYPATH = "security.allowed_users"; const QString ALLOWED_USERS_SETTINGS_KEYPATH = "security.allowed_users";
@ -137,12 +155,6 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList
QVariant* restrictedAccess = _configMap.valueForKeyPath(RESTRICTED_ACCESS_SETTINGS_KEYPATH, true); QVariant* restrictedAccess = _configMap.valueForKeyPath(RESTRICTED_ACCESS_SETTINGS_KEYPATH, true);
*restrictedAccess = QVariant(true); *restrictedAccess = QVariant(true);
// write the new settings to the json file
persistToFile();
// reload the master and user config so that the merged config is right
_configMap.loadMasterAndUserConfig(_argumentList);
} }
} }
@ -172,12 +184,6 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList
*entityServerVariant = entityServerMap; *entityServerVariant = entityServerMap;
} }
// write the new settings to the json file
persistToFile();
// reload the master and user config so that the merged config is right
_configMap.loadMasterAndUserConfig(_argumentList);
} }
} }
@ -195,12 +201,6 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList
qDebug() << "Migrating plaintext password to SHA256 hash in domain-server settings."; qDebug() << "Migrating plaintext password to SHA256 hash in domain-server settings.";
*passwordVariant = QCryptographicHash::hash(plaintextPassword.toUtf8(), QCryptographicHash::Sha256).toHex(); *passwordVariant = QCryptographicHash::hash(plaintextPassword.toUtf8(), QCryptographicHash::Sha256).toHex();
// write the new settings to file
persistToFile();
// reload the master and user config so the merged config is correct
_configMap.loadMasterAndUserConfig(_argumentList);
} }
} }
@ -283,19 +283,6 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList
packPermissions(); packPermissions();
} }
if (oldVersion < 1.7) {
// This was prior to the removal of the master config file
// So we write the merged config to the user config file, and stop reading from the user config file
qDebug() << "Migrating merged config to user config file. The master config file is deprecated.";
// replace the user config by the merged config
_configMap.getConfig() = _configMap.getMergedConfig();
// persist the new config so the user config file has the correctly merged config
persistToFile();
}
if (oldVersion < 1.8) { if (oldVersion < 1.8) {
unpackPermissions(); unpackPermissions();
// This was prior to addition of domain content replacement, add that to localhost permissions by default // This was prior to addition of domain content replacement, add that to localhost permissions by default
@ -316,16 +303,16 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList
QVariant* wizardCompletedOnce = _configMap.valueForKeyPath(WIZARD_COMPLETED_ONCE, true); QVariant* wizardCompletedOnce = _configMap.valueForKeyPath(WIZARD_COMPLETED_ONCE, true);
*wizardCompletedOnce = QVariant(true); *wizardCompletedOnce = QVariant(true);
// write the new settings to the json file
persistToFile();
} }
// write the current description version to our settings
*versionVariant = _descriptionVersion;
// write the new settings to the json file
persistToFile();
} }
unpackPermissions(); unpackPermissions();
// write the current description version to our settings
JSON_SETTING_VERSION.set(_descriptionVersion);
} }
QVariantMap& DomainServerSettingsManager::getDescriptorsMap() { QVariantMap& DomainServerSettingsManager::getDescriptorsMap() {
@ -1289,9 +1276,6 @@ bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ
} }
} }
// re-merge the user and master configs after a settings change
_configMap.mergeMasterAndUserConfigs();
return needRestart; return needRestart;
} }

View file

@ -64,11 +64,12 @@ Item {
keyItem.state = "mouseOver"; keyItem.state = "mouseOver";
var globalPosition = keyItem.mapToGlobal(mouseArea1.mouseX, mouseArea1.mouseY); var globalPosition = keyItem.mapToGlobal(mouseArea1.mouseX, mouseArea1.mouseY);
var deviceId = Web3DOverlay.deviceIdByTouchPoint(globalPosition.x, globalPosition.y); var pointerID = Web3DOverlay.deviceIdByTouchPoint(globalPosition.x, globalPosition.y);
var hand = deviceId - 1; // based on touchEventUtils.js, deviceId is 'hand + 1', so 'hand' is 'deviceId' - 1
if (hand == leftHand || hand == rightHand) { if (Pointers.isLeftHand(pointerID)) {
Controller.triggerHapticPulse(_HAPTIC_STRENGTH, _HAPTIC_DURATION, hand); Controller.triggerHapticPulse(_HAPTIC_STRENGTH, _HAPTIC_DURATION, leftHand);
} else if (Pointers.isRightHand(pointerID)) {
Controller.triggerHapticPulse(_HAPTIC_STRENGTH, _HAPTIC_DURATION, rightHand);
} }
} }

View file

@ -432,7 +432,8 @@ Item {
anchors.verticalCenter: nameCardRemoveConnectionImage.verticalCenter anchors.verticalCenter: nameCardRemoveConnectionImage.verticalCenter
x: 240 x: 240
onClicked: { onClicked: {
AddressManager.goToUser(thisNameCard.userName); console.log("Vist user button clicked."); // Remove after debugging.
AddressManager.goToUser(thisNameCard.userName, false);
UserActivityLogger.palAction("go_to_user", thisNameCard.userName); UserActivityLogger.palAction("go_to_user", thisNameCard.userName);
} }
} }
@ -594,7 +595,10 @@ Item {
// the avatar goes into fly mode rather than falling. However, that is not exposed to Javascript right now. // the avatar goes into fly mode rather than falling. However, that is not exposed to Javascript right now.
// FIXME: it would be nice if this used the same teleport steps and smoothing as in the teleport.js script. // FIXME: it would be nice if this used the same teleport steps and smoothing as in the teleport.js script.
// Note, however, that this script allows teleporting to a person in the air, while teleport.js is going to a grounded target. // Note, however, that this script allows teleporting to a person in the air, while teleport.js is going to a grounded target.
// Position avatar 2 metres from the target in the direction that target avatar was facing.
MyAvatar.position = Vec3.sum(avatar.position, Vec3.multiplyQbyV(avatar.orientation, {x: 0, y: 0, z: -2})); MyAvatar.position = Vec3.sum(avatar.position, Vec3.multiplyQbyV(avatar.orientation, {x: 0, y: 0, z: -2}));
MyAvatar.orientation = Quat.multiply(avatar.orientation, {y: 1});
// Rotate avatar on Y axis to face target avatar and cancel out any inherited roll and pitch.
MyAvatar.orientation = Quat.cancelOutRollAndPitch(Quat.multiply(avatar.orientation, {y: 1}));
} }
} }

View file

@ -827,7 +827,7 @@ Rectangle {
hoverEnabled: enabled hoverEnabled: enabled
enabled: connectionsNameCard.selected && pal.activeTab == "connectionsTab" enabled: connectionsNameCard.selected && pal.activeTab == "connectionsTab"
onClicked: { onClicked: {
AddressManager.goToUser(model.userName); AddressManager.goToUser(model.userName, false);
UserActivityLogger.palAction("go_to_user", model.userName); UserActivityLogger.palAction("go_to_user", model.userName);
} }
onEntered: connectionsLocationData.color = hifi.colors.blueHighlight; onEntered: connectionsLocationData.color = hifi.colors.blueHighlight;

View file

@ -213,8 +213,8 @@ Rectangle {
anchors.right: parent.right anchors.right: parent.right
peak: model.peak; peak: model.peak;
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
visible: (bar.currentIndex === 1 && selectedHMD && isVR) || visible: ((bar.currentIndex === 1 && isVR) ||
(bar.currentIndex === 0 && selectedDesktop && !isVR) && (bar.currentIndex === 0 && !isVR)) &&
Audio.devices.input.peakValuesAvailable; Audio.devices.input.peakValuesAvailable;
} }
} }

View file

@ -33,6 +33,7 @@ Rectangle {
property string dateOfPurchase: "--"; property string dateOfPurchase: "--";
property bool isLightbox: false; property bool isLightbox: false;
property bool isMyCert: false; property bool isMyCert: false;
property bool isCertificateInvalid: false;
// Style // Style
color: hifi.colors.faintGray; color: hifi.colors.faintGray;
Hifi.QmlCommerce { Hifi.QmlCommerce {
@ -44,10 +45,11 @@ Rectangle {
} else { } else {
root.marketplaceUrl = result.data.marketplace_item_url; root.marketplaceUrl = result.data.marketplace_item_url;
root.isMyCert = result.isMyCert ? result.isMyCert : false; root.isMyCert = result.isMyCert ? result.isMyCert : false;
root.itemOwner = root.isMyCert ? Account.username : root.itemOwner = root.isCertificateInvalid ? "--" : (root.isMyCert ? Account.username :
"\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022"; "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022");
root.itemEdition = result.data.edition_number + "/" + (result.data.limited_run === -1 ? "\u221e" : result.data.limited_run); root.itemEdition = root.isCertificateInvalid ? "Uncertified Copy" :
root.dateOfPurchase = getFormattedDate(result.data.transfer_created_at * 1000); (result.data.edition_number + "/" + (result.data.limited_run === -1 ? "\u221e" : result.data.limited_run));
root.dateOfPurchase = root.isCertificateInvalid ? "" : getFormattedDate(result.data.transfer_created_at * 1000);
root.itemName = result.data.marketplace_item_name; root.itemName = result.data.marketplace_item_name;
if (result.data.invalid_reason || result.data.transfer_status[0] === "failed") { if (result.data.invalid_reason || result.data.transfer_status[0] === "failed") {
@ -65,6 +67,44 @@ Rectangle {
} }
} }
} }
onUpdateCertificateStatus: {
if (certStatus === 1) { // CERTIFICATE_STATUS_VERIFICATION_SUCCESS
// NOP
} else if (certStatus === 2) { // CERTIFICATE_STATUS_VERIFICATION_TIMEOUT
root.isCertificateInvalid = true;
errorText.text = "Verification of this certificate timed out.";
errorText.color = hifi.colors.redHighlight;
} else if (certStatus === 3) { // CERTIFICATE_STATUS_STATIC_VERIFICATION_FAILED
root.isCertificateInvalid = true;
titleBarText.text = "Invalid Certificate";
titleBarText.color = hifi.colors.redHighlight;
popText.text = "";
root.itemOwner = "";
dateOfPurchaseHeader.text = "";
root.dateOfPurchase = "";
root.itemEdition = "Uncertified Copy";
errorText.text = "The information associated with this item has been modified and it no longer matches the original certified item.";
errorText.color = hifi.colors.baseGray;
} else if (certStatus === 4) { // CERTIFICATE_STATUS_OWNER_VERIFICATION_FAILED
root.isCertificateInvalid = true;
titleBarText.text = "Invalid Certificate";
titleBarText.color = hifi.colors.redHighlight;
popText.text = "";
root.itemOwner = "";
dateOfPurchaseHeader.text = "";
root.dateOfPurchase = "";
root.itemEdition = "Uncertified Copy";
errorText.text = "The avatar who rezzed this item doesn't own it.";
errorText.color = hifi.colors.baseGray;
} else {
console.log("Unknown certificate status received from ledger signal!");
}
}
} }
onCertificateIdChanged: { onCertificateIdChanged: {
@ -216,7 +256,7 @@ Rectangle {
} }
AnonymousProRegular { AnonymousProRegular {
id: isMyCertText; id: isMyCertText;
visible: root.isMyCert; visible: root.isMyCert && !root.isCertificateInvalid;
text: "(Private)"; text: "(Private)";
size: 18; size: 18;
// Anchors // Anchors

View file

@ -1825,14 +1825,14 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
DependencyManager::get<EntityTreeRenderer>()->setMouseRayPickResultOperator([&](unsigned int rayPickID) { DependencyManager::get<EntityTreeRenderer>()->setMouseRayPickResultOperator([&](unsigned int rayPickID) {
RayToEntityIntersectionResult entityResult; RayToEntityIntersectionResult entityResult;
entityResult.intersects = false; entityResult.intersects = false;
QVariantMap result = DependencyManager::get<PickManager>()->getPrevPickResult(rayPickID); auto pickResult = DependencyManager::get<PickManager>()->getPrevPickResultTyped<RayPickResult>(rayPickID);
if (result["type"].isValid()) { if (pickResult) {
entityResult.intersects = result["type"] != PickScriptingInterface::INTERSECTED_NONE(); entityResult.intersects = pickResult->type != IntersectionType::NONE;
if (entityResult.intersects) { if (entityResult.intersects) {
entityResult.intersection = vec3FromVariant(result["intersection"]); entityResult.intersection = pickResult->intersection;
entityResult.distance = result["distance"].toFloat(); entityResult.distance = pickResult->distance;
entityResult.surfaceNormal = vec3FromVariant(result["surfaceNormal"]); entityResult.surfaceNormal = pickResult->surfaceNormal;
entityResult.entityID = result["objectID"].toUuid(); entityResult.entityID = pickResult->objectID;
entityResult.entity = DependencyManager::get<EntityTreeRenderer>()->getTree()->findEntityByID(entityResult.entityID); entityResult.entity = DependencyManager::get<EntityTreeRenderer>()->getTree()->findEntityByID(entityResult.entityID);
} }
} }
@ -3296,7 +3296,7 @@ void Application::mouseMoveEvent(QMouseEvent* event) {
auto offscreenUi = DependencyManager::get<OffscreenUi>(); auto offscreenUi = DependencyManager::get<OffscreenUi>();
auto eventPosition = compositor.getMouseEventPosition(event); auto eventPosition = compositor.getMouseEventPosition(event);
QPointF transformedPos = offscreenUi->mapToVirtualScreen(eventPosition, _glWidget); QPointF transformedPos = offscreenUi->mapToVirtualScreen(eventPosition);
auto button = event->button(); auto button = event->button();
auto buttons = event->buttons(); auto buttons = event->buttons();
// Determine if the ReticleClick Action is 1 and if so, fake include the LeftMouseButton // Determine if the ReticleClick Action is 1 and if so, fake include the LeftMouseButton
@ -3342,7 +3342,7 @@ void Application::mousePressEvent(QMouseEvent* event) {
offscreenUi->unfocusWindows(); offscreenUi->unfocusWindows();
auto eventPosition = getApplicationCompositor().getMouseEventPosition(event); auto eventPosition = getApplicationCompositor().getMouseEventPosition(event);
QPointF transformedPos = offscreenUi->mapToVirtualScreen(eventPosition, _glWidget); QPointF transformedPos = offscreenUi->mapToVirtualScreen(eventPosition);
QMouseEvent mappedEvent(event->type(), QMouseEvent mappedEvent(event->type(),
transformedPos, transformedPos,
event->screenPos(), event->button(), event->screenPos(), event->button(),
@ -3372,7 +3372,7 @@ void Application::mousePressEvent(QMouseEvent* event) {
void Application::mouseDoublePressEvent(QMouseEvent* event) { void Application::mouseDoublePressEvent(QMouseEvent* event) {
auto offscreenUi = DependencyManager::get<OffscreenUi>(); auto offscreenUi = DependencyManager::get<OffscreenUi>();
auto eventPosition = getApplicationCompositor().getMouseEventPosition(event); auto eventPosition = getApplicationCompositor().getMouseEventPosition(event);
QPointF transformedPos = offscreenUi->mapToVirtualScreen(eventPosition, _glWidget); QPointF transformedPos = offscreenUi->mapToVirtualScreen(eventPosition);
QMouseEvent mappedEvent(event->type(), QMouseEvent mappedEvent(event->type(),
transformedPos, transformedPos,
event->screenPos(), event->button(), event->screenPos(), event->button(),
@ -3398,7 +3398,7 @@ void Application::mouseReleaseEvent(QMouseEvent* event) {
auto offscreenUi = DependencyManager::get<OffscreenUi>(); auto offscreenUi = DependencyManager::get<OffscreenUi>();
auto eventPosition = getApplicationCompositor().getMouseEventPosition(event); auto eventPosition = getApplicationCompositor().getMouseEventPosition(event);
QPointF transformedPos = offscreenUi->mapToVirtualScreen(eventPosition, _glWidget); QPointF transformedPos = offscreenUi->mapToVirtualScreen(eventPosition);
QMouseEvent mappedEvent(event->type(), QMouseEvent mappedEvent(event->type(),
transformedPos, transformedPos,
event->screenPos(), event->button(), event->screenPos(), event->button(),
@ -4963,7 +4963,7 @@ void Application::update(float deltaTime) {
{ {
PROFILE_RANGE(app, "PointerManager"); PROFILE_RANGE(app, "PointerManager");
DependencyManager::get<PointerManager>()->update(); DependencyManager::get<PointerManager>()->update(deltaTime);
} }
{ {

View file

@ -179,6 +179,12 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
const AvatarPriority& sortData = sortedAvatars.top(); const AvatarPriority& sortData = sortedAvatars.top();
const auto& avatar = std::static_pointer_cast<Avatar>(sortData.avatar); const auto& avatar = std::static_pointer_cast<Avatar>(sortData.avatar);
bool ignoring = DependencyManager::get<NodeList>()->isPersonalMutingNode(avatar->getID());
if (ignoring) {
sortedAvatars.pop();
continue;
}
// for ALL avatars... // for ALL avatars...
if (_shouldRender) { if (_shouldRender) {
avatar->ensureInScene(avatar, qApp->getMain3DScene()); avatar->ensureInScene(avatar, qApp->getMain3DScene());

View file

@ -90,7 +90,7 @@ void Ledger::buy(const QString& hfc_key, int cost, const QString& asset_id, cons
signedSend("transaction", transactionString, hfc_key, "buy", "buySuccess", "buyFailure", controlled_failure); signedSend("transaction", transactionString, hfc_key, "buy", "buySuccess", "buyFailure", controlled_failure);
} }
bool Ledger::receiveAt(const QString& hfc_key, const QString& old_key) { bool Ledger::receiveAt(const QString& hfc_key, const QString& old_key, const QString& machine_fingerprint) {
auto accountManager = DependencyManager::get<AccountManager>(); auto accountManager = DependencyManager::get<AccountManager>();
if (!accountManager->isLoggedIn()) { if (!accountManager->isLoggedIn()) {
qCWarning(commerce) << "Cannot set receiveAt when not logged in."; qCWarning(commerce) << "Cannot set receiveAt when not logged in.";
@ -99,7 +99,13 @@ bool Ledger::receiveAt(const QString& hfc_key, const QString& old_key) {
return false; // We know right away that we will fail, so tell the caller. return false; // We know right away that we will fail, so tell the caller.
} }
signedSend("public_key", hfc_key.toUtf8(), old_key, "receive_at", "receiveAtSuccess", "receiveAtFailure"); QJsonObject transaction;
transaction["hfc_key"] = hfc_key;
transaction["machine_fingerprint"] = machine_fingerprint;
QJsonDocument transactionDoc{ transaction };
auto transactionString = transactionDoc.toJson(QJsonDocument::Compact);
signedSend("transaction", transactionString, old_key, "receive_at", "receiveAtSuccess", "receiveAtFailure");
return true; // Note that there may still be an asynchronous signal of failure that callers might be interested in. return true; // Note that there may still be an asynchronous signal of failure that callers might be interested in.
} }

View file

@ -26,7 +26,7 @@ class Ledger : public QObject, public Dependency {
public: public:
void buy(const QString& hfc_key, int cost, const QString& asset_id, const QString& inventory_key, const bool controlled_failure = false); void buy(const QString& hfc_key, int cost, const QString& asset_id, const QString& inventory_key, const bool controlled_failure = false);
bool receiveAt(const QString& hfc_key, const QString& old_key); bool receiveAt(const QString& hfc_key, const QString& old_key, const QString& machine_fingerprint);
void balance(const QStringList& keys); void balance(const QStringList& keys);
void inventory(const QStringList& keys); void inventory(const QStringList& keys);
void history(const QStringList& keys); void history(const QStringList& keys);
@ -35,6 +35,14 @@ public:
void updateLocation(const QString& asset_id, const QString location, const bool controlledFailure = false); void updateLocation(const QString& asset_id, const QString location, const bool controlledFailure = false);
void certificateInfo(const QString& certificateId); void certificateInfo(const QString& certificateId);
enum CertificateStatus {
CERTIFICATE_STATUS_UNKNOWN = 0,
CERTIFICATE_STATUS_VERIFICATION_SUCCESS,
CERTIFICATE_STATUS_VERIFICATION_TIMEOUT,
CERTIFICATE_STATUS_STATIC_VERIFICATION_FAILED,
CERTIFICATE_STATUS_OWNER_VERIFICATION_FAILED,
};
signals: signals:
void buyResult(QJsonObject result); void buyResult(QJsonObject result);
void receiveAtResult(QJsonObject result); void receiveAtResult(QJsonObject result);
@ -45,6 +53,8 @@ signals:
void locationUpdateResult(QJsonObject result); void locationUpdateResult(QJsonObject result);
void certificateInfoResult(QJsonObject result); void certificateInfoResult(QJsonObject result);
void updateCertificateStatus(const QString& certID, uint certStatus);
public slots: public slots:
void buySuccess(QNetworkReply& reply); void buySuccess(QNetworkReply& reply);
void buyFailure(QNetworkReply& reply); void buyFailure(QNetworkReply& reply);

View file

@ -30,6 +30,12 @@ QmlCommerce::QmlCommerce(QQuickItem* parent) : OffscreenQmlDialog(parent) {
connect(ledger.data(), &Ledger::accountResult, this, &QmlCommerce::accountResult); connect(ledger.data(), &Ledger::accountResult, this, &QmlCommerce::accountResult);
connect(wallet.data(), &Wallet::walletStatusResult, this, &QmlCommerce::walletStatusResult); connect(wallet.data(), &Wallet::walletStatusResult, this, &QmlCommerce::walletStatusResult);
connect(ledger.data(), &Ledger::certificateInfoResult, this, &QmlCommerce::certificateInfoResult); connect(ledger.data(), &Ledger::certificateInfoResult, this, &QmlCommerce::certificateInfoResult);
connect(ledger.data(), &Ledger::updateCertificateStatus, this, &QmlCommerce::updateCertificateStatus);
auto accountManager = DependencyManager::get<AccountManager>();
connect(accountManager.data(), &AccountManager::usernameChanged, [&]() {
setPassphrase("");
});
} }
void QmlCommerce::getWalletStatus() { void QmlCommerce::getWalletStatus() {

View file

@ -45,6 +45,8 @@ signals:
void accountResult(QJsonObject result); void accountResult(QJsonObject result);
void certificateInfoResult(QJsonObject result); void certificateInfoResult(QJsonObject result);
void updateCertificateStatus(const QString& certID, uint certStatus);
protected: protected:
Q_INVOKABLE void getWalletStatus(); Q_INVOKABLE void getWalletStatus();

View file

@ -16,6 +16,7 @@
#include "ui/ImageProvider.h" #include "ui/ImageProvider.h"
#include "scripting/HMDScriptingInterface.h" #include "scripting/HMDScriptingInterface.h"
#include <FingerprintUtils.h>
#include <PathUtils.h> #include <PathUtils.h>
#include <OffscreenUi.h> #include <OffscreenUi.h>
#include <AccountManager.h> #include <AccountManager.h>
@ -319,6 +320,7 @@ Wallet::Wallet() {
auto& packetReceiver = nodeList->getPacketReceiver(); auto& packetReceiver = nodeList->getPacketReceiver();
packetReceiver.registerListener(PacketType::ChallengeOwnership, this, "handleChallengeOwnershipPacket"); packetReceiver.registerListener(PacketType::ChallengeOwnership, this, "handleChallengeOwnershipPacket");
packetReceiver.registerListener(PacketType::ChallengeOwnershipRequest, this, "handleChallengeOwnershipPacket");
connect(ledger.data(), &Ledger::accountResult, this, [&]() { connect(ledger.data(), &Ledger::accountResult, this, [&]() {
auto wallet = DependencyManager::get<Wallet>(); auto wallet = DependencyManager::get<Wallet>();
@ -540,7 +542,8 @@ bool Wallet::generateKeyPair() {
// 2. It is maximally private, and we can step back from that later if desired. // 2. It is maximally private, and we can step back from that later if desired.
// 3. It maximally exercises all the machinery, so we are most likely to surface issues now. // 3. It maximally exercises all the machinery, so we are most likely to surface issues now.
auto ledger = DependencyManager::get<Ledger>(); auto ledger = DependencyManager::get<Ledger>();
return ledger->receiveAt(key, oldKey); QString machineFingerprint = uuidStringWithoutCurlyBraces(FingerprintUtils::getMachineFingerprint());
return ledger->receiveAt(key, oldKey, machineFingerprint);
} }
QStringList Wallet::listPublicKeys() { QStringList Wallet::listPublicKeys() {
@ -717,50 +720,86 @@ bool Wallet::changePassphrase(const QString& newPassphrase) {
} }
void Wallet::handleChallengeOwnershipPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode) { void Wallet::handleChallengeOwnershipPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode) {
auto nodeList = DependencyManager::get<NodeList>();
bool challengeOriginatedFromClient = packet->getType() == PacketType::ChallengeOwnershipRequest;
unsigned char decryptedText[64]; unsigned char decryptedText[64];
int certIDByteArraySize; int certIDByteArraySize;
int encryptedTextByteArraySize; int encryptedTextByteArraySize;
int challengingNodeUUIDByteArraySize;
packet->readPrimitive(&certIDByteArraySize); packet->readPrimitive(&certIDByteArraySize);
packet->readPrimitive(&encryptedTextByteArraySize); packet->readPrimitive(&encryptedTextByteArraySize);
if (challengeOriginatedFromClient) {
packet->readPrimitive(&challengingNodeUUIDByteArraySize);
}
QByteArray certID = packet->read(certIDByteArraySize); QByteArray certID = packet->read(certIDByteArraySize);
QByteArray encryptedText = packet->read(encryptedTextByteArraySize); QByteArray encryptedText = packet->read(encryptedTextByteArraySize);
QByteArray challengingNodeUUID;
if (challengeOriginatedFromClient) {
challengingNodeUUID = packet->read(challengingNodeUUIDByteArraySize);
}
RSA* rsa = readKeys(keyFilePath().toStdString().c_str()); RSA* rsa = readKeys(keyFilePath().toStdString().c_str());
int decryptionStatus = -1;
if (rsa) { if (rsa) {
const int decryptionStatus = RSA_private_decrypt(encryptedTextByteArraySize, ERR_clear_error();
decryptionStatus = RSA_private_decrypt(encryptedTextByteArraySize,
reinterpret_cast<const unsigned char*>(encryptedText.constData()), reinterpret_cast<const unsigned char*>(encryptedText.constData()),
decryptedText, decryptedText,
rsa, rsa,
RSA_PKCS1_OAEP_PADDING); RSA_PKCS1_OAEP_PADDING);
RSA_free(rsa); RSA_free(rsa);
if (decryptionStatus != -1) {
auto nodeList = DependencyManager::get<NodeList>();
QByteArray decryptedTextByteArray = QByteArray(reinterpret_cast<const char*>(decryptedText), decryptionStatus);
int decryptedTextByteArraySize = decryptedTextByteArray.size();
int certIDSize = certID.size();
// setup the packet
auto decryptedTextPacket = NLPacket::create(PacketType::ChallengeOwnership, certIDSize + decryptedTextByteArraySize + 2 * sizeof(int), true);
decryptedTextPacket->writePrimitive(certIDSize);
decryptedTextPacket->writePrimitive(decryptedTextByteArraySize);
decryptedTextPacket->write(certID);
decryptedTextPacket->write(decryptedTextByteArray);
qCDebug(commerce) << "Sending ChallengeOwnership Packet containing decrypted text" << decryptedTextByteArray << "for CertID" << certID;
nodeList->sendPacket(std::move(decryptedTextPacket), *sendingNode);
} else {
qCDebug(commerce) << "During entity ownership challenge, decrypting the encrypted text failed.";
}
} else { } else {
qCDebug(commerce) << "During entity ownership challenge, creating the RSA object failed."; qCDebug(commerce) << "During entity ownership challenge, creating the RSA object failed.";
} }
QByteArray decryptedTextByteArray;
if (decryptionStatus > -1) {
decryptedTextByteArray = QByteArray(reinterpret_cast<const char*>(decryptedText), decryptionStatus);
}
int decryptedTextByteArraySize = decryptedTextByteArray.size();
int certIDSize = certID.size();
// setup the packet
if (challengeOriginatedFromClient) {
auto decryptedTextPacket = NLPacket::create(PacketType::ChallengeOwnershipReply,
certIDSize + decryptedTextByteArraySize + challengingNodeUUIDByteArraySize + 3 * sizeof(int),
true);
decryptedTextPacket->writePrimitive(certIDSize);
decryptedTextPacket->writePrimitive(decryptedTextByteArraySize);
decryptedTextPacket->writePrimitive(challengingNodeUUIDByteArraySize);
decryptedTextPacket->write(certID);
decryptedTextPacket->write(decryptedTextByteArray);
decryptedTextPacket->write(challengingNodeUUID);
qCDebug(commerce) << "Sending ChallengeOwnershipReply Packet containing decrypted text" << decryptedTextByteArray << "for CertID" << certID;
nodeList->sendPacket(std::move(decryptedTextPacket), *sendingNode);
} else {
auto decryptedTextPacket = NLPacket::create(PacketType::ChallengeOwnership, certIDSize + decryptedTextByteArraySize + 2 * sizeof(int), true);
decryptedTextPacket->writePrimitive(certIDSize);
decryptedTextPacket->writePrimitive(decryptedTextByteArraySize);
decryptedTextPacket->write(certID);
decryptedTextPacket->write(decryptedTextByteArray);
qCDebug(commerce) << "Sending ChallengeOwnership Packet containing decrypted text" << decryptedTextByteArray << "for CertID" << certID;
nodeList->sendPacket(std::move(decryptedTextPacket), *sendingNode);
}
if (decryptionStatus == -1) {
qCDebug(commerce) << "During entity ownership challenge, decrypting the encrypted text failed.";
long error = ERR_get_error();
if (error != 0) {
const char* error_str = ERR_error_string(error, NULL);
qCWarning(entities) << "RSA error:" << error_str;
}
}
} }
void Wallet::account() { void Wallet::account() {

View file

@ -20,7 +20,7 @@ JointRayPick::JointRayPick(const std::string& jointName, const glm::vec3& posOff
{ {
} }
const PickRay JointRayPick::getMathematicalPick() const { PickRay JointRayPick::getMathematicalPick() const {
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar(); auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
int jointIndex = myAvatar->getJointIndex(QString::fromStdString(_jointName)); int jointIndex = myAvatar->getJointIndex(QString::fromStdString(_jointName));
bool useAvatarHead = _jointName == "Avatar"; bool useAvatarHead = _jointName == "Avatar";

View file

@ -18,7 +18,10 @@ class JointRayPick : public RayPick {
public: public:
JointRayPick(const std::string& jointName, const glm::vec3& posOffset, const glm::vec3& dirOffset, const PickFilter& filter, const float maxDistance = 0.0f, const bool enabled = false); JointRayPick(const std::string& jointName, const glm::vec3& posOffset, const glm::vec3& dirOffset, const PickFilter& filter, const float maxDistance = 0.0f, const bool enabled = false);
const PickRay getMathematicalPick() const override; PickRay getMathematicalPick() const override;
bool isLeftHand() const override { return (_jointName == "_CONTROLLER_LEFTHAND") || (_jointName == "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND"); }
bool isRightHand() const override { return (_jointName == "_CONTROLLER_RIGHTHAND") || (_jointName == "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND"); }
private: private:
std::string _jointName; std::string _jointName;

View file

@ -16,6 +16,7 @@
#include <DependencyManager.h> #include <DependencyManager.h>
#include <pointers/PickManager.h> #include <pointers/PickManager.h>
#include "PickScriptingInterface.h" #include "PickScriptingInterface.h"
#include "RayPick.h"
LaserPointer::LaserPointer(const QVariant& rayProps, const RenderStateMap& renderStates, const DefaultRenderStateMap& defaultRenderStates, bool hover, LaserPointer::LaserPointer(const QVariant& rayProps, const RenderStateMap& renderStates, const DefaultRenderStateMap& defaultRenderStates, bool hover,
const PointerTriggers& triggers, bool faceAvatar, bool centerEndY, bool lockEnd, bool distanceScaleEnd, bool enabled) : const PointerTriggers& triggers, bool faceAvatar, bool centerEndY, bool lockEnd, bool distanceScaleEnd, bool enabled) :
@ -177,17 +178,20 @@ void LaserPointer::disableRenderState(const RenderState& renderState) {
} }
} }
void LaserPointer::updateVisuals(const QVariantMap& prevRayPickResult) { void LaserPointer::updateVisuals(const PickResultPointer& pickResult) {
IntersectionType type = IntersectionType(prevRayPickResult["type"].toInt()); auto rayPickResult = std::static_pointer_cast<const RayPickResult>(pickResult);
PickRay pickRay = PickRay(prevRayPickResult["searchRay"].toMap());
QUuid uid = prevRayPickResult["objectID"].toUuid(); IntersectionType type = rayPickResult ? rayPickResult->type : IntersectionType::NONE;
if (_enabled && !_currentRenderState.empty() && _renderStates.find(_currentRenderState) != _renderStates.end() && if (_enabled && !_currentRenderState.empty() && _renderStates.find(_currentRenderState) != _renderStates.end() &&
(type != IntersectionType::NONE || _laserLength > 0.0f || !_objectLockEnd.first.isNull())) { (type != IntersectionType::NONE || _laserLength > 0.0f || !_objectLockEnd.first.isNull())) {
float distance = _laserLength > 0.0f ? _laserLength : prevRayPickResult["distance"].toFloat(); PickRay pickRay{ rayPickResult->pickVariant };
QUuid uid = rayPickResult->objectID;
float distance = _laserLength > 0.0f ? _laserLength : rayPickResult->distance;
updateRenderState(_renderStates[_currentRenderState], type, distance, uid, pickRay, false); updateRenderState(_renderStates[_currentRenderState], type, distance, uid, pickRay, false);
disableRenderState(_defaultRenderStates[_currentRenderState].second); disableRenderState(_defaultRenderStates[_currentRenderState].second);
} else if (_enabled && !_currentRenderState.empty() && _defaultRenderStates.find(_currentRenderState) != _defaultRenderStates.end()) { } else if (_enabled && !_currentRenderState.empty() && _defaultRenderStates.find(_currentRenderState) != _defaultRenderStates.end()) {
disableRenderState(_renderStates[_currentRenderState]); disableRenderState(_renderStates[_currentRenderState]);
PickRay pickRay = rayPickResult ? PickRay(rayPickResult->pickVariant) : PickRay();
updateRenderState(_defaultRenderStates[_currentRenderState].second, IntersectionType::NONE, _defaultRenderStates[_currentRenderState].first, QUuid(), pickRay, true); updateRenderState(_defaultRenderStates[_currentRenderState].second, IntersectionType::NONE, _defaultRenderStates[_currentRenderState].first, QUuid(), pickRay, true);
} else if (!_currentRenderState.empty()) { } else if (!_currentRenderState.empty()) {
disableRenderState(_renderStates[_currentRenderState]); disableRenderState(_renderStates[_currentRenderState]);
@ -195,8 +199,12 @@ void LaserPointer::updateVisuals(const QVariantMap& prevRayPickResult) {
} }
} }
Pointer::PickedObject LaserPointer::getHoveredObject(const QVariantMap& pickResult) { Pointer::PickedObject LaserPointer::getHoveredObject(const PickResultPointer& pickResult) {
return Pointer::PickedObject(pickResult["objectID"].toUuid(), IntersectionType(pickResult["type"].toUInt())); auto rayPickResult = std::static_pointer_cast<const RayPickResult>(pickResult);
if (!rayPickResult) {
return PickedObject();
}
return PickedObject(rayPickResult->objectID, rayPickResult->type);
} }
Pointer::Buttons LaserPointer::getPressedButtons() { Pointer::Buttons LaserPointer::getPressedButtons() {
@ -281,15 +289,21 @@ RenderState LaserPointer::buildRenderState(const QVariantMap& propMap) {
return RenderState(startID, pathID, endID); return RenderState(startID, pathID, endID);
} }
PointerEvent LaserPointer::buildPointerEvent(const PickedObject& target, const QVariantMap& pickResult) const { PointerEvent LaserPointer::buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult) const {
glm::vec3 intersection = vec3FromVariant(pickResult["intersection"]); QUuid pickedID;
glm::vec3 surfaceNormal = vec3FromVariant(pickResult["surfaceNormal"]); glm::vec3 intersection, surfaceNormal, direction, origin;
QVariantMap searchRay = pickResult["searchRay"].toMap(); if (target.type != NONE) {
glm::vec3 direction = vec3FromVariant(searchRay["direction"]); auto rayPickResult = std::static_pointer_cast<RayPickResult>(pickResult);
QUuid pickedID = pickResult["objectID"].toUuid(); intersection = rayPickResult->intersection;
surfaceNormal = rayPickResult->surfaceNormal;
const QVariantMap& searchRay = rayPickResult->pickVariant;
direction = vec3FromVariant(searchRay["direction"]);
origin = vec3FromVariant(searchRay["origin"]);
pickedID = rayPickResult->objectID;;
}
glm::vec2 pos2D; glm::vec2 pos2D;
if (pickedID != target.objectID) { if (pickedID != target.objectID) {
glm::vec3 origin = vec3FromVariant(searchRay["origin"]);
if (target.type == ENTITY) { if (target.type == ENTITY) {
intersection = intersectRayWithEntityXYPlane(target.objectID, origin, direction); intersection = intersectRayWithEntityXYPlane(target.objectID, origin, direction);
} else if (target.type == OVERLAY) { } else if (target.type == OVERLAY) {
@ -303,7 +317,7 @@ PointerEvent LaserPointer::buildPointerEvent(const PickedObject& target, const Q
} else if (target.type == HUD) { } else if (target.type == HUD) {
pos2D = DependencyManager::get<PickManager>()->calculatePos2DFromHUD(intersection); pos2D = DependencyManager::get<PickManager>()->calculatePos2DFromHUD(intersection);
} }
return PointerEvent(PointerEvent::Move, 0, pos2D, intersection, surfaceNormal, direction, PointerEvent::NoButtons); return PointerEvent(pos2D, intersection, surfaceNormal, direction);
} }
glm::vec3 LaserPointer::intersectRayWithXYPlane(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& point, const glm::quat rotation, const glm::vec3& registration) const { glm::vec3 LaserPointer::intersectRayWithXYPlane(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& point, const glm::quat rotation, const glm::vec3& registration) const {
@ -356,4 +370,4 @@ glm::vec2 LaserPointer::projectOntoOverlayXYPlane(const QUuid& overlayID, const
glm::vec2 LaserPointer::projectOntoEntityXYPlane(const QUuid& entityID, const glm::vec3& worldPos) const { glm::vec2 LaserPointer::projectOntoEntityXYPlane(const QUuid& entityID, const glm::vec3& worldPos) const {
auto props = DependencyManager::get<EntityScriptingInterface>()->getEntityProperties(entityID); auto props = DependencyManager::get<EntityScriptingInterface>()->getEntityProperties(entityID);
return projectOntoXYPlane(worldPos, props.getPosition(), props.getRotation(), props.getDimensions(), props.getRegistrationPoint()); return projectOntoXYPlane(worldPos, props.getPosition(), props.getRotation(), props.getDimensions(), props.getRegistrationPoint());
} }

View file

@ -65,15 +65,15 @@ public:
void setLength(float length) override; void setLength(float length) override;
void setLockEndUUID(const QUuid& objectID, bool isOverlay) override; void setLockEndUUID(const QUuid& objectID, bool isOverlay) override;
void updateVisuals(const QVariantMap& prevRayPickResult) override; void updateVisuals(const PickResultPointer& prevRayPickResult) override;
PickedObject getHoveredObject(const QVariantMap& pickResult) override;
Pointer::Buttons getPressedButtons() override;
static RenderState buildRenderState(const QVariantMap& propMap); static RenderState buildRenderState(const QVariantMap& propMap);
protected: protected:
PointerEvent buildPointerEvent(const PickedObject& target, const QVariantMap& pickResult) const override; PointerEvent buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult) const override;
PickedObject getHoveredObject(const PickResultPointer& pickResult) override;
Pointer::Buttons getPressedButtons() override;
bool shouldHover() override { return _currentRenderState != ""; } bool shouldHover() override { return _currentRenderState != ""; }
bool shouldTrigger() override { return _currentRenderState != ""; } bool shouldTrigger() override { return _currentRenderState != ""; }

View file

@ -17,6 +17,7 @@
void LaserPointerScriptingInterface::setIgnoreItems(unsigned int uid, const QScriptValue& ignoreItems) const { void LaserPointerScriptingInterface::setIgnoreItems(unsigned int uid, const QScriptValue& ignoreItems) const {
DependencyManager::get<PointerManager>()->setIgnoreItems(uid, qVectorQUuidFromScriptValue(ignoreItems)); DependencyManager::get<PointerManager>()->setIgnoreItems(uid, qVectorQUuidFromScriptValue(ignoreItems));
} }
void LaserPointerScriptingInterface::setIncludeItems(unsigned int uid, const QScriptValue& includeItems) const { void LaserPointerScriptingInterface::setIncludeItems(unsigned int uid, const QScriptValue& includeItems) const {
DependencyManager::get<PointerManager>()->setIncludeItems(uid, qVectorQUuidFromScriptValue(includeItems)); DependencyManager::get<PointerManager>()->setIncludeItems(uid, qVectorQUuidFromScriptValue(includeItems));
} }
@ -27,4 +28,8 @@ unsigned int LaserPointerScriptingInterface::createLaserPointer(const QVariant&
void LaserPointerScriptingInterface::editRenderState(unsigned int uid, const QString& renderState, const QVariant& properties) const { void LaserPointerScriptingInterface::editRenderState(unsigned int uid, const QString& renderState, const QVariant& properties) const {
DependencyManager::get<PointerScriptingInterface>()->editRenderState(uid, renderState, properties); DependencyManager::get<PointerScriptingInterface>()->editRenderState(uid, renderState, properties);
} }
QVariantMap LaserPointerScriptingInterface::getPrevRayPickResult(unsigned int uid) const {
return DependencyManager::get<PointerScriptingInterface>()->getPrevPickResult(uid);
}

View file

@ -20,14 +20,14 @@ class LaserPointerScriptingInterface : public QObject, public Dependency {
Q_OBJECT Q_OBJECT
SINGLETON_DEPENDENCY SINGLETON_DEPENDENCY
public slots: public:
Q_INVOKABLE unsigned int createLaserPointer(const QVariant& properties) const; Q_INVOKABLE unsigned int createLaserPointer(const QVariant& properties) const;
Q_INVOKABLE void enableLaserPointer(unsigned int uid) const { DependencyManager::get<PointerManager>()->enablePointer(uid); } Q_INVOKABLE void enableLaserPointer(unsigned int uid) const { DependencyManager::get<PointerManager>()->enablePointer(uid); }
Q_INVOKABLE void disableLaserPointer(unsigned int uid) const { DependencyManager::get<PointerManager>()->disablePointer(uid); } Q_INVOKABLE void disableLaserPointer(unsigned int uid) const { DependencyManager::get<PointerManager>()->disablePointer(uid); }
Q_INVOKABLE void removeLaserPointer(unsigned int uid) const { DependencyManager::get<PointerManager>()->removePointer(uid); } Q_INVOKABLE void removeLaserPointer(unsigned int uid) const { DependencyManager::get<PointerManager>()->removePointer(uid); }
Q_INVOKABLE void editRenderState(unsigned int uid, const QString& renderState, const QVariant& properties) const; Q_INVOKABLE void editRenderState(unsigned int uid, const QString& renderState, const QVariant& properties) const;
Q_INVOKABLE void setRenderState(unsigned int uid, const QString& renderState) const { DependencyManager::get<PointerManager>()->setRenderState(uid, renderState.toStdString()); } Q_INVOKABLE void setRenderState(unsigned int uid, const QString& renderState) const { DependencyManager::get<PointerManager>()->setRenderState(uid, renderState.toStdString()); }
Q_INVOKABLE QVariantMap getPrevRayPickResult(unsigned int uid) const { return DependencyManager::get<PointerManager>()->getPrevPickResult(uid); } Q_INVOKABLE QVariantMap getPrevRayPickResult(unsigned int uid) const;
Q_INVOKABLE void setPrecisionPicking(unsigned int uid, bool precisionPicking) const { DependencyManager::get<PointerManager>()->setPrecisionPicking(uid, precisionPicking); } Q_INVOKABLE void setPrecisionPicking(unsigned int uid, bool precisionPicking) const { DependencyManager::get<PointerManager>()->setPrecisionPicking(uid, precisionPicking); }
Q_INVOKABLE void setLaserLength(unsigned int uid, float laserLength) const { DependencyManager::get<PointerManager>()->setLength(uid, laserLength); } Q_INVOKABLE void setLaserLength(unsigned int uid, float laserLength) const { DependencyManager::get<PointerManager>()->setLength(uid, laserLength); }
@ -36,6 +36,10 @@ public slots:
Q_INVOKABLE void setLockEndUUID(unsigned int uid, const QUuid& objectID, bool isOverlay) const { DependencyManager::get<PointerManager>()->setLockEndUUID(uid, objectID, isOverlay); } Q_INVOKABLE void setLockEndUUID(unsigned int uid, const QUuid& objectID, bool isOverlay) const { DependencyManager::get<PointerManager>()->setLockEndUUID(uid, objectID, isOverlay); }
Q_INVOKABLE bool isLeftHand(unsigned int uid) { return DependencyManager::get<PointerManager>()->isLeftHand(uid); }
Q_INVOKABLE bool isRightHand(unsigned int uid) { return DependencyManager::get<PointerManager>()->isRightHand(uid); }
Q_INVOKABLE bool isMouse(unsigned int uid) { return DependencyManager::get<PointerManager>()->isMouse(uid); }
}; };
#endif // hifi_LaserPointerScriptingInterface_h #endif // hifi_LaserPointerScriptingInterface_h

View file

@ -18,7 +18,7 @@ MouseRayPick::MouseRayPick(const PickFilter& filter, const float maxDistance, co
{ {
} }
const PickRay MouseRayPick::getMathematicalPick() const { PickRay MouseRayPick::getMathematicalPick() const {
QVariant position = qApp->getApplicationCompositor().getReticleInterface()->getPosition(); QVariant position = qApp->getApplicationCompositor().getReticleInterface()->getPosition();
if (position.isValid()) { if (position.isValid()) {
QVariantMap posMap = position.toMap(); QVariantMap posMap = position.toMap();

View file

@ -18,7 +18,9 @@ class MouseRayPick : public RayPick {
public: public:
MouseRayPick(const PickFilter& filter, const float maxDistance = 0.0f, const bool enabled = false); MouseRayPick(const PickFilter& filter, const float maxDistance = 0.0f, const bool enabled = false);
const PickRay getMathematicalPick() const override; PickRay getMathematicalPick() const override;
bool isMouse() const override { return true; }
}; };
#endif // hifi_MouseRayPick_h #endif // hifi_MouseRayPick_h

View file

@ -25,7 +25,7 @@ unsigned int PickScriptingInterface::createPick(const PickQuery::PickType type,
case PickQuery::PickType::Ray: case PickQuery::PickType::Ray:
return createRayPick(properties); return createRayPick(properties);
default: default:
return 0; return PickManager::INVALID_PICK_ID;
} }
} }
@ -78,7 +78,7 @@ unsigned int PickScriptingInterface::createRayPick(const QVariant& properties) {
return DependencyManager::get<PickManager>()->addPick(PickQuery::Ray, std::make_shared<StaticRayPick>(position, direction, filter, maxDistance, enabled)); return DependencyManager::get<PickManager>()->addPick(PickQuery::Ray, std::make_shared<StaticRayPick>(position, direction, filter, maxDistance, enabled));
} }
return 0; return PickManager::INVALID_PICK_ID;
} }
void PickScriptingInterface::enablePick(unsigned int uid) { void PickScriptingInterface::enablePick(unsigned int uid) {
@ -94,10 +94,15 @@ void PickScriptingInterface::removePick(unsigned int uid) {
} }
QVariantMap PickScriptingInterface::getPrevPickResult(unsigned int uid) { QVariantMap PickScriptingInterface::getPrevPickResult(unsigned int uid) {
return DependencyManager::get<PickManager>()->getPrevPickResult(uid); QVariantMap result;
auto pickResult = DependencyManager::get<PickManager>()->getPrevPickResult(uid);
if (pickResult) {
result = pickResult->toVariantMap();
}
return result;
} }
void PickScriptingInterface::setPrecisionPicking(unsigned int uid, const bool precisionPicking) { void PickScriptingInterface::setPrecisionPicking(unsigned int uid, bool precisionPicking) {
DependencyManager::get<PickManager>()->setPrecisionPicking(uid, precisionPicking); DependencyManager::get<PickManager>()->setPrecisionPicking(uid, precisionPicking);
} }
@ -109,6 +114,18 @@ void PickScriptingInterface::setIncludeItems(unsigned int uid, const QScriptValu
DependencyManager::get<PickManager>()->setIncludeItems(uid, qVectorQUuidFromScriptValue(includeItems)); DependencyManager::get<PickManager>()->setIncludeItems(uid, qVectorQUuidFromScriptValue(includeItems));
} }
bool PickScriptingInterface::isLeftHand(unsigned int uid) {
return DependencyManager::get<PickManager>()->isLeftHand(uid);
}
bool PickScriptingInterface::isRightHand(unsigned int uid) {
return DependencyManager::get<PickManager>()->isRightHand(uid);
}
bool PickScriptingInterface::isMouse(unsigned int uid) {
return DependencyManager::get<PickManager>()->isMouse(uid);
}
QScriptValue pickTypesToScriptValue(QScriptEngine* engine, const PickQuery::PickType& pickType) { QScriptValue pickTypesToScriptValue(QScriptEngine* engine, const PickQuery::PickType& pickType) {
return pickType; return pickType;
} }

View file

@ -37,17 +37,21 @@ public:
void registerMetaTypes(QScriptEngine* engine); void registerMetaTypes(QScriptEngine* engine);
public slots:
Q_INVOKABLE unsigned int createPick(const PickQuery::PickType type, const QVariant& properties); Q_INVOKABLE unsigned int createPick(const PickQuery::PickType type, const QVariant& properties);
Q_INVOKABLE void enablePick(unsigned int uid); Q_INVOKABLE void enablePick(unsigned int uid);
Q_INVOKABLE void disablePick(unsigned int uid); Q_INVOKABLE void disablePick(unsigned int uid);
Q_INVOKABLE void removePick(unsigned int uid); Q_INVOKABLE void removePick(unsigned int uid);
Q_INVOKABLE QVariantMap getPrevPickResult(unsigned int uid); Q_INVOKABLE QVariantMap getPrevPickResult(unsigned int uid);
Q_INVOKABLE void setPrecisionPicking(unsigned int uid, const bool precisionPicking); Q_INVOKABLE void setPrecisionPicking(unsigned int uid, bool precisionPicking);
Q_INVOKABLE void setIgnoreItems(unsigned int uid, const QScriptValue& ignoreEntities); Q_INVOKABLE void setIgnoreItems(unsigned int uid, const QScriptValue& ignoreEntities);
Q_INVOKABLE void setIncludeItems(unsigned int uid, const QScriptValue& includeEntities); Q_INVOKABLE void setIncludeItems(unsigned int uid, const QScriptValue& includeEntities);
Q_INVOKABLE bool isLeftHand(unsigned int uid);
Q_INVOKABLE bool isRightHand(unsigned int uid);
Q_INVOKABLE bool isMouse(unsigned int uid);
public slots:
static constexpr unsigned int PICK_NOTHING() { return 0; } static constexpr unsigned int PICK_NOTHING() { return 0; }
static constexpr unsigned int PICK_ENTITIES() { return PickFilter::getBitMask(PickFilter::FlagBit::PICK_ENTITIES); } static constexpr unsigned int PICK_ENTITIES() { return PickFilter::getBitMask(PickFilter::FlagBit::PICK_ENTITIES); }
static constexpr unsigned int PICK_OVERLAYS() { return PickFilter::getBitMask(PickFilter::FlagBit::PICK_OVERLAYS); } static constexpr unsigned int PICK_OVERLAYS() { return PickFilter::getBitMask(PickFilter::FlagBit::PICK_OVERLAYS); }

View file

@ -10,26 +10,54 @@
#include <QtCore/QVariant> #include <QtCore/QVariant>
#include <GLMHelpers.h> #include <GLMHelpers.h>
#include <shared/QtHelpers.h>
#include "Application.h" #include "Application.h"
#include "LaserPointer.h" #include "LaserPointer.h"
#include "StylusPointer.h"
void PointerScriptingInterface::setIgnoreItems(unsigned int uid, const QScriptValue& ignoreItems) const { void PointerScriptingInterface::setIgnoreItems(unsigned int uid, const QScriptValue& ignoreItems) const {
DependencyManager::get<PointerManager>()->setIgnoreItems(uid, qVectorQUuidFromScriptValue(ignoreItems)); DependencyManager::get<PointerManager>()->setIgnoreItems(uid, qVectorQUuidFromScriptValue(ignoreItems));
} }
void PointerScriptingInterface::setIncludeItems(unsigned int uid, const QScriptValue& includeItems) const { void PointerScriptingInterface::setIncludeItems(unsigned int uid, const QScriptValue& includeItems) const {
DependencyManager::get<PointerManager>()->setIncludeItems(uid, qVectorQUuidFromScriptValue(includeItems)); DependencyManager::get<PointerManager>()->setIncludeItems(uid, qVectorQUuidFromScriptValue(includeItems));
} }
unsigned int PointerScriptingInterface::createPointer(const PickQuery::PickType& type, const QVariant& properties) const { unsigned int PointerScriptingInterface::createPointer(const PickQuery::PickType& type, const QVariant& properties) {
// Interaction with managers should always happen on the main thread
if (QThread::currentThread() != qApp->thread()) {
unsigned int result;
BLOCKING_INVOKE_METHOD(this, "createPointer", Q_RETURN_ARG(unsigned int, result), Q_ARG(PickQuery::PickType, type), Q_ARG(QVariant, properties));
return result;
}
switch (type) { switch (type) {
case PickQuery::PickType::Ray: case PickQuery::PickType::Ray:
return createLaserPointer(properties); return createLaserPointer(properties);
case PickQuery::PickType::Stylus:
return createStylus(properties);
default: default:
return 0; return PointerEvent::INVALID_POINTER_ID;
} }
} }
unsigned int PointerScriptingInterface::createStylus(const QVariant& properties) const {
bilateral::Side side = bilateral::Side::Invalid;
{
QVariant handVar = properties.toMap()["hand"];
if (handVar.isValid()) {
side = bilateral::side(handVar.toInt());
}
}
if (bilateral::Side::Invalid == side) {
return PointerEvent::INVALID_POINTER_ID;
}
return DependencyManager::get<PointerManager>()->addPointer(std::make_shared<StylusPointer>(side));
}
unsigned int PointerScriptingInterface::createLaserPointer(const QVariant& properties) const { unsigned int PointerScriptingInterface::createLaserPointer(const QVariant& properties) const {
QVariantMap propertyMap = properties.toMap(); QVariantMap propertyMap = properties.toMap();
@ -133,4 +161,13 @@ void PointerScriptingInterface::editRenderState(unsigned int uid, const QString&
} }
DependencyManager::get<PointerManager>()->editRenderState(uid, renderState.toStdString(), startProps, pathProps, endProps); DependencyManager::get<PointerManager>()->editRenderState(uid, renderState.toStdString(), startProps, pathProps, endProps);
}
QVariantMap PointerScriptingInterface::getPrevPickResult(unsigned int uid) const {
QVariantMap result;
auto pickResult = DependencyManager::get<PointerManager>()->getPrevPickResult(uid);
if (pickResult) {
result = pickResult->toVariantMap();
}
return result;
} }

View file

@ -20,22 +20,26 @@ class PointerScriptingInterface : public QObject, public Dependency {
public: public:
unsigned int createLaserPointer(const QVariant& properties) const; unsigned int createLaserPointer(const QVariant& properties) const;
unsigned int createStylus(const QVariant& properties) const;
public slots: Q_INVOKABLE unsigned int createPointer(const PickQuery::PickType& type, const QVariant& properties);
Q_INVOKABLE unsigned int createPointer(const PickQuery::PickType& type, const QVariant& properties) const;
Q_INVOKABLE void enablePointer(unsigned int uid) const { DependencyManager::get<PointerManager>()->enablePointer(uid); } Q_INVOKABLE void enablePointer(unsigned int uid) const { DependencyManager::get<PointerManager>()->enablePointer(uid); }
Q_INVOKABLE void disablePointer(unsigned int uid) const { DependencyManager::get<PointerManager>()->disablePointer(uid); } Q_INVOKABLE void disablePointer(unsigned int uid) const { DependencyManager::get<PointerManager>()->disablePointer(uid); }
Q_INVOKABLE void removePointer(unsigned int uid) const { DependencyManager::get<PointerManager>()->removePointer(uid); } Q_INVOKABLE void removePointer(unsigned int uid) const { DependencyManager::get<PointerManager>()->removePointer(uid); }
Q_INVOKABLE void editRenderState(unsigned int uid, const QString& renderState, const QVariant& properties) const; Q_INVOKABLE void editRenderState(unsigned int uid, const QString& renderState, const QVariant& properties) const;
Q_INVOKABLE void setRenderState(unsigned int uid, const QString& renderState) const { DependencyManager::get<PointerManager>()->setRenderState(uid, renderState.toStdString()); } Q_INVOKABLE void setRenderState(unsigned int uid, const QString& renderState) const { DependencyManager::get<PointerManager>()->setRenderState(uid, renderState.toStdString()); }
Q_INVOKABLE QVariantMap getPrevPickResult(unsigned int uid) const { return DependencyManager::get<PointerManager>()->getPrevPickResult(uid); } Q_INVOKABLE QVariantMap getPrevPickResult(unsigned int uid) const;
Q_INVOKABLE void setPrecisionPicking(unsigned int uid, bool precisionPicking) const { DependencyManager::get<PointerManager>()->setPrecisionPicking(uid, precisionPicking); } Q_INVOKABLE void setPrecisionPicking(unsigned int uid, bool precisionPicking) const { DependencyManager::get<PointerManager>()->setPrecisionPicking(uid, precisionPicking); }
Q_INVOKABLE void setLaserLength(unsigned int uid, float laserLength) const { DependencyManager::get<PointerManager>()->setLength(uid, laserLength); } Q_INVOKABLE void setLaserLength(unsigned int uid, float laserLength) const { DependencyManager::get<PointerManager>()->setLength(uid, laserLength); }
Q_INVOKABLE void setIgnoreItems(unsigned int uid, const QScriptValue& ignoreEntities) const; Q_INVOKABLE void setIgnoreItems(unsigned int uid, const QScriptValue& ignoreEntities) const;
Q_INVOKABLE void setIncludeItems(unsigned int uid, const QScriptValue& includeEntities) const; Q_INVOKABLE void setIncludeItems(unsigned int uid, const QScriptValue& includeEntities) const;
Q_INVOKABLE void setLockEndUid(unsigned int uid, const QUuid& objectID, bool isOverlay) const { DependencyManager::get<PointerManager>()->setLockEndUUID(uid, objectID, isOverlay); } Q_INVOKABLE void setLockEndUUID(unsigned int uid, const QUuid& objectID, bool isOverlay) const { DependencyManager::get<PointerManager>()->setLockEndUUID(uid, objectID, isOverlay); }
Q_INVOKABLE bool isLeftHand(unsigned int uid) { return DependencyManager::get<PointerManager>()->isLeftHand(uid); }
Q_INVOKABLE bool isRightHand(unsigned int uid) { return DependencyManager::get<PointerManager>()->isRightHand(uid); }
Q_INVOKABLE bool isMouse(unsigned int uid) { return DependencyManager::get<PointerManager>()->isMouse(uid); }
signals: signals:
void triggerBegin(const QUuid& id, const PointerEvent& pointerEvent); void triggerBegin(const QUuid& id, const PointerEvent& pointerEvent);

View file

@ -53,7 +53,7 @@ public:
bool doesIntersect() const override { return intersects; } bool doesIntersect() const override { return intersects; }
bool checkOrFilterAgainstMaxDistance(float maxDistance) override { return distance < maxDistance; } bool checkOrFilterAgainstMaxDistance(float maxDistance) override { return distance < maxDistance; }
PickResultPointer compareAndProcessNewResult(const PickResultPointer newRes) override { PickResultPointer compareAndProcessNewResult(const PickResultPointer& newRes) override {
auto newRayRes = std::static_pointer_cast<RayPickResult>(newRes); auto newRayRes = std::static_pointer_cast<RayPickResult>(newRes);
if (newRayRes->distance < distance) { if (newRayRes->distance < distance) {
return std::make_shared<RayPickResult>(*newRayRes); return std::make_shared<RayPickResult>(*newRayRes);

View file

@ -20,34 +20,51 @@
#include "JointRayPick.h" #include "JointRayPick.h"
#include "MouseRayPick.h" #include "MouseRayPick.h"
uint32_t RayPickScriptingInterface::createRayPick(const QVariant& properties) { unsigned int RayPickScriptingInterface::createRayPick(const QVariant& properties) {
return DependencyManager::get<PickScriptingInterface>()->createRayPick(properties); return DependencyManager::get<PickScriptingInterface>()->createRayPick(properties);
} }
void RayPickScriptingInterface::enableRayPick(uint32_t uid) { void RayPickScriptingInterface::enableRayPick(unsigned int uid) {
DependencyManager::get<PickManager>()->enablePick(uid); DependencyManager::get<PickManager>()->enablePick(uid);
} }
void RayPickScriptingInterface::disableRayPick(uint32_t uid) { void RayPickScriptingInterface::disableRayPick(unsigned int uid) {
DependencyManager::get<PickManager>()->disablePick(uid); DependencyManager::get<PickManager>()->disablePick(uid);
} }
void RayPickScriptingInterface::removeRayPick(uint32_t uid) { void RayPickScriptingInterface::removeRayPick(unsigned int uid) {
DependencyManager::get<PickManager>()->removePick(uid); DependencyManager::get<PickManager>()->removePick(uid);
} }
QVariantMap RayPickScriptingInterface::getPrevRayPickResult(uint32_t uid) { QVariantMap RayPickScriptingInterface::getPrevRayPickResult(unsigned int uid) {
return DependencyManager::get<PickManager>()->getPrevPickResult(uid); QVariantMap result;
auto pickResult = DependencyManager::get<PickManager>()->getPrevPickResult(uid);
if (pickResult) {
result = pickResult->toVariantMap();
}
return result;
} }
void RayPickScriptingInterface::setPrecisionPicking(uint32_t uid, const bool precisionPicking) { void RayPickScriptingInterface::setPrecisionPicking(unsigned int uid, bool precisionPicking) {
DependencyManager::get<PickManager>()->setPrecisionPicking(uid, precisionPicking); DependencyManager::get<PickManager>()->setPrecisionPicking(uid, precisionPicking);
} }
void RayPickScriptingInterface::setIgnoreItems(uint32_t uid, const QScriptValue& ignoreItems) { void RayPickScriptingInterface::setIgnoreItems(unsigned int uid, const QScriptValue& ignoreItems) {
DependencyManager::get<PickManager>()->setIgnoreItems(uid, qVectorQUuidFromScriptValue(ignoreItems)); DependencyManager::get<PickManager>()->setIgnoreItems(uid, qVectorQUuidFromScriptValue(ignoreItems));
} }
void RayPickScriptingInterface::setIncludeItems(uint32_t uid, const QScriptValue& includeItems) { void RayPickScriptingInterface::setIncludeItems(unsigned int uid, const QScriptValue& includeItems) {
DependencyManager::get<PickManager>()->setIncludeItems(uid, qVectorQUuidFromScriptValue(includeItems)); DependencyManager::get<PickManager>()->setIncludeItems(uid, qVectorQUuidFromScriptValue(includeItems));
} }
bool RayPickScriptingInterface::isLeftHand(unsigned int uid) {
return DependencyManager::get<PickManager>()->isLeftHand(uid);
}
bool RayPickScriptingInterface::isRightHand(unsigned int uid) {
return DependencyManager::get<PickManager>()->isRightHand(uid);
}
bool RayPickScriptingInterface::isMouse(unsigned int uid) {
return DependencyManager::get<PickManager>()->isMouse(uid);
}

View file

@ -36,17 +36,22 @@ class RayPickScriptingInterface : public QObject, public Dependency {
Q_PROPERTY(unsigned int INTERSECTED_HUD READ INTERSECTED_HUD CONSTANT) Q_PROPERTY(unsigned int INTERSECTED_HUD READ INTERSECTED_HUD CONSTANT)
SINGLETON_DEPENDENCY SINGLETON_DEPENDENCY
public slots: public:
Q_INVOKABLE unsigned int createRayPick(const QVariant& properties); Q_INVOKABLE unsigned int createRayPick(const QVariant& properties);
Q_INVOKABLE void enableRayPick(unsigned int uid); Q_INVOKABLE void enableRayPick(unsigned int uid);
Q_INVOKABLE void disableRayPick(unsigned int uid); Q_INVOKABLE void disableRayPick(unsigned int uid);
Q_INVOKABLE void removeRayPick(unsigned int uid); Q_INVOKABLE void removeRayPick(unsigned int uid);
Q_INVOKABLE QVariantMap getPrevRayPickResult(unsigned int uid); Q_INVOKABLE QVariantMap getPrevRayPickResult(unsigned int uid);
Q_INVOKABLE void setPrecisionPicking(unsigned int uid, const bool precisionPicking); Q_INVOKABLE void setPrecisionPicking(unsigned int uid, bool precisionPicking);
Q_INVOKABLE void setIgnoreItems(unsigned int uid, const QScriptValue& ignoreEntities); Q_INVOKABLE void setIgnoreItems(unsigned int uid, const QScriptValue& ignoreEntities);
Q_INVOKABLE void setIncludeItems(unsigned int uid, const QScriptValue& includeEntities); Q_INVOKABLE void setIncludeItems(unsigned int uid, const QScriptValue& includeEntities);
Q_INVOKABLE bool isLeftHand(unsigned int uid);
Q_INVOKABLE bool isRightHand(unsigned int uid);
Q_INVOKABLE bool isMouse(unsigned int uid);
public slots:
static unsigned int PICK_NOTHING() { return PickScriptingInterface::PICK_NOTHING(); } static unsigned int PICK_NOTHING() { return PickScriptingInterface::PICK_NOTHING(); }
static unsigned int PICK_ENTITIES() { return PickScriptingInterface::PICK_ENTITIES(); } static unsigned int PICK_ENTITIES() { return PickScriptingInterface::PICK_ENTITIES(); }
static unsigned int PICK_OVERLAYS() { return PickScriptingInterface::PICK_OVERLAYS(); } static unsigned int PICK_OVERLAYS() { return PickScriptingInterface::PICK_OVERLAYS(); }

View file

@ -13,6 +13,6 @@ StaticRayPick::StaticRayPick(const glm::vec3& position, const glm::vec3& directi
{ {
} }
const PickRay StaticRayPick::getMathematicalPick() const { PickRay StaticRayPick::getMathematicalPick() const {
return _pickRay; return _pickRay;
} }

View file

@ -15,7 +15,7 @@ class StaticRayPick : public RayPick {
public: public:
StaticRayPick(const glm::vec3& position, const glm::vec3& direction, const PickFilter& filter, const float maxDistance = 0.0f, const bool enabled = false); StaticRayPick(const glm::vec3& position, const glm::vec3& direction, const PickFilter& filter, const float maxDistance = 0.0f, const bool enabled = false);
const PickRay getMathematicalPick() const override; PickRay getMathematicalPick() const override;
private: private:
PickRay _pickRay; PickRay _pickRay;

View file

@ -0,0 +1,627 @@
//
// Created by Bradley Austin Davis on 2017/10/24
// Copyright 2013-2017 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 "StylusPointer.h"
#include <array>
#include <QtCore/QThread>
#include <DependencyManager.h>
#include <pointers/PickManager.h>
#include <GLMHelpers.h>
#include <Transform.h>
#include <shared/QtHelpers.h>
#include <controllers/StandardControls.h>
#include <controllers/UserInputMapper.h>
#include <RegisteredMetaTypes.h>
#include <MessagesClient.h>
#include <EntityItemID.h>
#include "Application.h"
#include "avatar/AvatarManager.h"
#include "avatar/MyAvatar.h"
#include "scripting/HMDScriptingInterface.h"
#include "ui/overlays/Web3DOverlay.h"
#include "ui/overlays/Sphere3DOverlay.h"
#include "avatar/AvatarManager.h"
#include "InterfaceLogging.h"
#include "PickScriptingInterface.h"
using namespace controller;
using namespace bilateral;
static Setting::Handle<double> USE_FINGER_AS_STYLUS("preferAvatarFingerOverStylus", false);
static const float WEB_STYLUS_LENGTH = 0.2f;
static const float WEB_TOUCH_Y_OFFSET = 0.105f; // how far forward (or back with a negative number) to slide stylus in hand
static const vec3 TIP_OFFSET{ 0.0f, WEB_STYLUS_LENGTH - WEB_TOUCH_Y_OFFSET, 0.0f };
static const float TABLET_MIN_HOVER_DISTANCE = 0.01f;
static const float TABLET_MAX_HOVER_DISTANCE = 0.1f;
static const float TABLET_MIN_TOUCH_DISTANCE = -0.05f;
static const float TABLET_MAX_TOUCH_DISTANCE = TABLET_MIN_HOVER_DISTANCE;
static const float EDGE_BORDER = 0.075f;
static const float HOVER_HYSTERESIS = 0.01f;
static const float NEAR_HYSTERESIS = 0.05f;
static const float TOUCH_HYSTERESIS = 0.002f;
// triggered when stylus presses a web overlay/entity
static const float HAPTIC_STYLUS_STRENGTH = 1.0f;
static const float HAPTIC_STYLUS_DURATION = 20.0f;
static const float POINTER_PRESS_TO_MOVE_DELAY = 0.33f; // seconds
static const float WEB_DISPLAY_STYLUS_DISTANCE = 0.5f;
static const float TOUCH_PRESS_TO_MOVE_DEADSPOT = 0.0481f;
static const float TOUCH_PRESS_TO_MOVE_DEADSPOT_SQUARED = TOUCH_PRESS_TO_MOVE_DEADSPOT * TOUCH_PRESS_TO_MOVE_DEADSPOT;
std::array<StylusPointer*, 2> STYLUSES;
static OverlayID getHomeButtonID() {
return DependencyManager::get<HMDScriptingInterface>()->getCurrentHomeButtonID();
}
static OverlayID getTabletScreenID() {
return DependencyManager::get<HMDScriptingInterface>()->getCurrentTabletScreenID();
}
struct SideData {
QString avatarJoint;
QString cameraJoint;
controller::StandardPoseChannel channel;
controller::Hand hand;
vec3 grabPointSphereOffset;
int getJointIndex(bool finger) {
const auto& jointName = finger ? avatarJoint : cameraJoint;
return DependencyManager::get<AvatarManager>()->getMyAvatar()->getJointIndex(jointName);
}
};
static const std::array<SideData, 2> SIDES{ { { "LeftHandIndex4",
"_CAMERA_RELATIVE_CONTROLLER_LEFTHAND",
StandardPoseChannel::LEFT_HAND,
Hand::LEFT,
{ -0.04f, 0.13f, 0.039f } },
{ "RightHandIndex4",
"_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND",
StandardPoseChannel::RIGHT_HAND,
Hand::RIGHT,
{ 0.04f, 0.13f, 0.039f } } } };
static StylusTip getFingerWorldLocation(Side side) {
const auto& sideData = SIDES[index(side)];
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
auto fingerJointIndex = myAvatar->getJointIndex(sideData.avatarJoint);
if (-1 == fingerJointIndex) {
return StylusTip();
}
auto fingerPosition = myAvatar->getAbsoluteJointTranslationInObjectFrame(fingerJointIndex);
auto fingerRotation = myAvatar->getAbsoluteJointRotationInObjectFrame(fingerJointIndex);
auto avatarOrientation = myAvatar->getOrientation();
auto avatarPosition = myAvatar->getPosition();
StylusTip result;
result.side = side;
result.orientation = avatarOrientation * fingerRotation;
result.position = avatarPosition + (avatarOrientation * fingerPosition);
return result;
}
// controllerWorldLocation is where the controller would be, in-world, with an added offset
static StylusTip getControllerWorldLocation(Side side, float sensorToWorldScale) {
static const std::array<Input, 2> INPUTS{ { UserInputMapper::makeStandardInput(SIDES[0].channel),
UserInputMapper::makeStandardInput(SIDES[1].channel) } };
const auto sideIndex = index(side);
const auto& input = INPUTS[sideIndex];
const auto pose = DependencyManager::get<UserInputMapper>()->getPose(input);
const auto& valid = pose.valid;
StylusTip result;
if (valid) {
result.side = side;
const auto& sideData = SIDES[sideIndex];
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
auto controllerJointIndex = myAvatar->getJointIndex(sideData.cameraJoint);
const auto avatarOrientation = myAvatar->getOrientation();
const auto avatarPosition = myAvatar->getPosition();
result.orientation = avatarOrientation * myAvatar->getAbsoluteJointRotationInObjectFrame(controllerJointIndex);
result.position =
avatarPosition + (avatarOrientation * myAvatar->getAbsoluteJointTranslationInObjectFrame(controllerJointIndex));
// add to the real position so the grab-point is out in front of the hand, a bit
result.position += result.orientation * (sideData.grabPointSphereOffset * sensorToWorldScale);
auto worldControllerPos = avatarPosition + avatarOrientation * pose.translation;
// compute tip velocity from hand controller motion, it is more accurate than computing it from previous positions.
auto worldControllerLinearVel = avatarOrientation * pose.velocity;
auto worldControllerAngularVel = avatarOrientation * pose.angularVelocity;
result.velocity =
worldControllerLinearVel + glm::cross(worldControllerAngularVel, result.position - worldControllerPos);
}
return result;
}
bool StylusPickResult::isNormalized() const {
return valid && (normalizedPosition == glm::clamp(normalizedPosition, vec3(0), vec3(1)));
}
bool StylusPickResult::isNearNormal(float min, float max, float hystersis) const {
return valid && (distance == glm::clamp(distance, min - hystersis, max + hystersis));
}
bool StylusPickResult::isNear2D(float border, float hystersis) const {
return valid && position2D == glm::clamp(position2D, vec2(0) - border - hystersis, vec2(dimensions) + border + hystersis);
}
bool StylusPickResult::isNear(float min, float max, float border, float hystersis) const {
// check to see if the projected stylusTip is within within the 2d border
return isNearNormal(min, max, hystersis) && isNear2D(border, hystersis);
}
StylusPickResult::operator bool() const {
return valid;
}
bool StylusPickResult::hasKeyboardFocus() const {
if (!overlayID.isNull()) {
return qApp->getOverlays().getKeyboardFocusOverlay() == overlayID;
}
#if 0
if (!entityID.isNull()) {
return qApp->getKeyboardFocusEntity() == entityID;
}
#endif
return false;
}
void StylusPickResult::setKeyboardFocus() const {
if (!overlayID.isNull()) {
qApp->getOverlays().setKeyboardFocusOverlay(overlayID);
qApp->setKeyboardFocusEntity(EntityItemID());
#if 0
} else if (!entityID.isNull()) {
qApp->getOverlays().setKeyboardFocusOverlay(OverlayID());
qApp->setKeyboardFocusEntity(entityID);
#endif
}
}
void StylusPickResult::sendHoverOverEvent() const {
if (!overlayID.isNull()) {
qApp->getOverlays().hoverOverOverlay(overlayID, PointerEvent{ PointerEvent::Move, deviceId(), position2D, position,
normal, -normal });
}
// FIXME support entity
}
void StylusPickResult::sendHoverEnterEvent() const {
if (!overlayID.isNull()) {
qApp->getOverlays().hoverEnterOverlay(overlayID, PointerEvent{ PointerEvent::Move, deviceId(), position2D, position,
normal, -normal });
}
// FIXME support entity
}
void StylusPickResult::sendTouchStartEvent() const {
if (!overlayID.isNull()) {
qApp->getOverlays().sendMousePressOnOverlay(overlayID, PointerEvent{ PointerEvent::Press, deviceId(), position2D, position,
normal, -normal, PointerEvent::PrimaryButton,
PointerEvent::PrimaryButton });
}
// FIXME support entity
}
void StylusPickResult::sendTouchEndEvent() const {
if (!overlayID.isNull()) {
qApp->getOverlays().sendMouseReleaseOnOverlay(overlayID,
PointerEvent{ PointerEvent::Release, deviceId(), position2D, position, normal,
-normal, PointerEvent::PrimaryButton });
}
// FIXME support entity
}
void StylusPickResult::sendTouchMoveEvent() const {
if (!overlayID.isNull()) {
qApp->getOverlays().sendMouseMoveOnOverlay(overlayID, PointerEvent{ PointerEvent::Move, deviceId(), position2D, position,
normal, -normal, PointerEvent::PrimaryButton,
PointerEvent::PrimaryButton });
}
// FIXME support entity
}
bool StylusPickResult::doesIntersect() const {
return true;
}
// for example: if we want the closest result, compare based on distance
// if we want all results, combine them
// must return a new pointer
std::shared_ptr<PickResult> StylusPickResult::compareAndProcessNewResult(const std::shared_ptr<PickResult>& newRes) {
auto newStylusResult = std::static_pointer_cast<StylusPickResult>(newRes);
if (newStylusResult && newStylusResult->distance < distance) {
return std::make_shared<StylusPickResult>(*newStylusResult);
} else {
return std::make_shared<StylusPickResult>(*this);
}
}
// returns true if this result contains any valid results with distance < maxDistance
// can also filter out results with distance >= maxDistance
bool StylusPickResult::checkOrFilterAgainstMaxDistance(float maxDistance) {
return distance < maxDistance;
}
uint32_t StylusPickResult::deviceId() const {
// 0 is reserved for hardware mouse
return index(tip.side) + 1;
}
StylusPick::StylusPick(Side side)
: Pick(PickFilter(PickScriptingInterface::PICK_OVERLAYS()), FLT_MAX, true)
, _side(side) {
}
StylusTip StylusPick::getMathematicalPick() const {
StylusTip result;
if (_useFingerInsteadOfStylus) {
result = getFingerWorldLocation(_side);
} else {
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
float sensorScaleFactor = myAvatar->getSensorToWorldScale();
result = getControllerWorldLocation(_side, sensorScaleFactor);
result.position += result.orientation * (TIP_OFFSET * sensorScaleFactor);
}
return result;
}
PickResultPointer StylusPick::getDefaultResult(const QVariantMap& pickVariant) const {
return std::make_shared<StylusPickResult>();
}
PickResultPointer StylusPick::getEntityIntersection(const StylusTip& pick) {
return PickResultPointer();
}
PickResultPointer StylusPick::getOverlayIntersection(const StylusTip& pick) {
if (!getFilter().doesPickOverlays()) {
return PickResultPointer();
}
std::vector<StylusPickResult> results;
for (const auto& target : getIncludeItems()) {
if (target.isNull()) {
continue;
}
auto overlay = qApp->getOverlays().getOverlay(target);
// Don't interact with non-3D or invalid overlays
if (!overlay || !overlay->is3D()) {
continue;
}
if (!overlay->getVisible() && !getFilter().doesPickInvisible()) {
continue;
}
auto overlayType = overlay->getType();
auto overlay3D = std::static_pointer_cast<Base3DOverlay>(overlay);
const auto overlayRotation = overlay3D->getRotation();
const auto overlayPosition = overlay3D->getPosition();
StylusPickResult result;
result.tip = pick;
result.overlayID = target;
result.normal = overlayRotation * Vectors::UNIT_Z;
result.distance = glm::dot(pick.position - overlayPosition, result.normal);
result.position = pick.position - (result.normal * result.distance);
if (overlayType == Web3DOverlay::TYPE) {
result.dimensions = vec3(std::static_pointer_cast<Web3DOverlay>(overlay3D)->getSize(), 0.01f);
} else if (overlayType == Sphere3DOverlay::TYPE) {
result.dimensions = std::static_pointer_cast<Sphere3DOverlay>(overlay3D)->getDimensions();
} else {
result.dimensions = vec3(0.01f);
}
auto tipRelativePosition = result.position - overlayPosition;
auto localPos = glm::inverse(overlayRotation) * tipRelativePosition;
auto normalizedPosition = localPos / result.dimensions;
result.normalizedPosition = normalizedPosition + 0.5f;
result.position2D = { result.normalizedPosition.x * result.dimensions.x,
(1.0f - result.normalizedPosition.y) * result.dimensions.y };
result.valid = true;
results.push_back(result);
}
StylusPickResult nearestTarget;
for (const auto& result : results) {
if (result && result.isNormalized() && result.distance < nearestTarget.distance) {
nearestTarget = result;
}
}
return std::make_shared<StylusPickResult>(nearestTarget);
}
PickResultPointer StylusPick::getAvatarIntersection(const StylusTip& pick) {
return PickResultPointer();
}
PickResultPointer StylusPick::getHUDIntersection(const StylusTip& pick) {
return PickResultPointer();
}
StylusPointer::StylusPointer(Side side)
: Pointer(DependencyManager::get<PickManager>()->addPick(PickQuery::Stylus, std::make_shared<StylusPick>(side)),
false,
true)
, _side(side)
, _sideData(SIDES[index(side)]) {
setIncludeItems({ { getHomeButtonID(), getTabletScreenID() } });
STYLUSES[index(_side)] = this;
}
StylusPointer::~StylusPointer() {
if (!_stylusOverlay.isNull()) {
qApp->getOverlays().deleteOverlay(_stylusOverlay);
}
STYLUSES[index(_side)] = nullptr;
}
StylusPointer* StylusPointer::getOtherStylus() {
return STYLUSES[((index(_side) + 1) % 2)];
}
void StylusPointer::enable() {
Parent::enable();
withWriteLock([&] { _renderingEnabled = true; });
}
void StylusPointer::disable() {
Parent::disable();
withWriteLock([&] { _renderingEnabled = false; });
}
void StylusPointer::updateStylusTarget() {
const float minNearDistance = TABLET_MIN_TOUCH_DISTANCE * _sensorScaleFactor;
const float maxNearDistance = WEB_DISPLAY_STYLUS_DISTANCE * _sensorScaleFactor;
const float edgeBorder = EDGE_BORDER * _sensorScaleFactor;
auto pickResult = DependencyManager::get<PickManager>()->getPrevPickResultTyped<StylusPickResult>(_pickUID);
if (pickResult) {
_state.target = *pickResult;
float hystersis = 0.0f;
// If we're already near the target, add hystersis to ensure we don't rapidly toggle between near and not near
// but only for the current near target
if (_previousState.nearTarget && pickResult->overlayID == _previousState.target.overlayID) {
hystersis = _nearHysteresis;
}
_state.nearTarget = pickResult->isNear(minNearDistance, maxNearDistance, edgeBorder, hystersis);
}
// Not near anything, short circuit the rest
if (!_state.nearTarget) {
relinquishTouchFocus();
hide();
return;
}
show();
auto minTouchDistance = TABLET_MIN_TOUCH_DISTANCE * _sensorScaleFactor;
auto maxTouchDistance = TABLET_MAX_TOUCH_DISTANCE * _sensorScaleFactor;
auto maxHoverDistance = TABLET_MAX_HOVER_DISTANCE * _sensorScaleFactor;
float hystersis = 0.0f;
if (_previousState.nearTarget && _previousState.target.overlayID == _previousState.target.overlayID) {
hystersis = _nearHysteresis;
}
// If we're in hover distance (calculated as the normal distance from the XY plane of the overlay)
if ((getOtherStylus() && getOtherStylus()->_state.touchingTarget) || !_state.target.isNearNormal(minTouchDistance, maxHoverDistance, hystersis)) {
relinquishTouchFocus();
return;
}
requestTouchFocus(_state.target);
if (!_state.target.hasKeyboardFocus()) {
_state.target.setKeyboardFocus();
}
if (hasTouchFocus(_state.target) && !_previousState.touchingTarget) {
_state.target.sendHoverOverEvent();
}
hystersis = 0.0f;
if (_previousState.touchingTarget && _previousState.target.overlayID == _state.target.overlayID) {
hystersis = _touchHysteresis;
}
// If we're in touch distance
if (_state.target.isNearNormal(minTouchDistance, maxTouchDistance, _touchHysteresis) && _state.target.isNormalized()) {
_state.touchingTarget = true;
}
}
void StylusPointer::update(unsigned int pointerID, float deltaTime) {
// This only needs to be a read lock because update won't change any of the properties that can be modified from scripts
withReadLock([&] {
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
// Store and reset the state
{
_previousState = _state;
_state = State();
}
#if 0
// Update finger as stylus setting
{
useFingerInsteadOfStylus = (USE_FINGER_AS_STYLUS.get() && myAvatar->getJointIndex(sideData.avatarJoint) != -1);
}
#endif
// Update scale factor
{
_sensorScaleFactor = myAvatar->getSensorToWorldScale();
_hoverHysteresis = HOVER_HYSTERESIS * _sensorScaleFactor;
_nearHysteresis = NEAR_HYSTERESIS * _sensorScaleFactor;
_touchHysteresis = TOUCH_HYSTERESIS * _sensorScaleFactor;
}
// Identify the current near or touching target
updateStylusTarget();
// If we stopped touching, or if the target overlay ID changed, send a touching exit to the previous touch target
if (_previousState.touchingTarget &&
(!_state.touchingTarget || _state.target.overlayID != _previousState.target.overlayID)) {
stylusTouchingExit();
}
// Handle new or continuing touch
if (_state.touchingTarget) {
// If we were previously not touching, or we were touching a different overlay, add a touch enter
if (!_previousState.touchingTarget || _previousState.target.overlayID != _state.target.overlayID) {
stylusTouchingEnter();
} else {
_touchingEnterTimer += deltaTime;
}
stylusTouching();
}
});
setIncludeItems({ { getHomeButtonID(), getTabletScreenID() } });
}
void StylusPointer::show() {
if (!_stylusOverlay.isNull()) {
return;
}
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
// FIXME perhaps instantiate a stylus and use show / hide instead of create / destroy
// however, the current design doesn't really allow for this because it assumes that
// hide / show are idempotent and low cost, but constantly querying the visibility
QVariantMap overlayProperties;
overlayProperties["name"] = "stylus";
overlayProperties["url"] = PathUtils::resourcesPath() + "/meshes/tablet-stylus-fat.fbx";
overlayProperties["loadPriority"] = 10.0f;
overlayProperties["dimensions"] = vec3toVariant(_sensorScaleFactor * vec3(0.01f, 0.01f, WEB_STYLUS_LENGTH));
overlayProperties["solid"] = true;
overlayProperties["visible"] = true;
overlayProperties["ignoreRayIntersection"] = true;
overlayProperties["drawInFront"] = false;
overlayProperties["parentID"] = AVATAR_SELF_ID;
overlayProperties["parentJointIndex"] = myAvatar->getJointIndex(_sideData.cameraJoint);
static const glm::quat X_ROT_NEG_90{ 0.70710678f, -0.70710678f, 0.0f, 0.0f };
auto modelOrientation = _state.target.tip.orientation * X_ROT_NEG_90;
auto modelPositionOffset = modelOrientation * (vec3(0.0f, 0.0f, -WEB_STYLUS_LENGTH / 2.0f) * _sensorScaleFactor);
overlayProperties["position"] = vec3toVariant(_state.target.tip.position + modelPositionOffset);
overlayProperties["rotation"] = quatToVariant(modelOrientation);
_stylusOverlay = qApp->getOverlays().addOverlay("model", overlayProperties);
}
void StylusPointer::hide() {
if (_stylusOverlay.isNull()) {
return;
}
qApp->getOverlays().deleteOverlay(_stylusOverlay);
_stylusOverlay = OverlayID();
}
#if 0
void pointFinger(bool value) {
static const QString HIFI_POINT_INDEX_MESSAGE_CHANNEL = "Hifi-Point-Index";
static const std::array<QString, 2> KEYS{ { "pointLeftIndex", "pointLeftIndex" } };
if (fingerPointing != value) {
QString message = QJsonDocument(QJsonObject{ { KEYS[index(side)], value } }).toJson();
DependencyManager::get<MessagesClient>()->sendMessage(HIFI_POINT_INDEX_MESSAGE_CHANNEL, message);
fingerPointing = value;
}
}
#endif
void StylusPointer::requestTouchFocus(const StylusPickResult& pickResult) {
if (!pickResult) {
return;
}
// send hover events to target if we can.
// record the entity or overlay we are hovering over.
if (!pickResult.overlayID.isNull() && pickResult.overlayID != _hoverOverlay &&
getOtherStylus() && pickResult.overlayID != getOtherStylus()->_hoverOverlay) {
_hoverOverlay = pickResult.overlayID;
pickResult.sendHoverEnterEvent();
}
}
bool StylusPointer::hasTouchFocus(const StylusPickResult& pickResult) {
return (!pickResult.overlayID.isNull() && pickResult.overlayID == _hoverOverlay);
}
void StylusPointer::relinquishTouchFocus() {
// send hover leave event.
if (!_hoverOverlay.isNull()) {
PointerEvent pointerEvent{ PointerEvent::Move, (uint32_t)(index(_side) + 1) };
auto& overlays = qApp->getOverlays();
overlays.sendMouseMoveOnOverlay(_hoverOverlay, pointerEvent);
overlays.sendHoverOverOverlay(_hoverOverlay, pointerEvent);
overlays.sendHoverLeaveOverlay(_hoverOverlay, pointerEvent);
_hoverOverlay = OverlayID();
}
};
void StylusPointer::stealTouchFocus() {
// send hover events to target
if (getOtherStylus() && _state.target.overlayID == getOtherStylus()->_hoverOverlay) {
getOtherStylus()->relinquishTouchFocus();
}
requestTouchFocus(_state.target);
}
void StylusPointer::stylusTouchingEnter() {
stealTouchFocus();
_state.target.sendTouchStartEvent();
DependencyManager::get<UserInputMapper>()->triggerHapticPulse(HAPTIC_STYLUS_STRENGTH, HAPTIC_STYLUS_DURATION,
_sideData.hand);
_touchingEnterTimer = 0;
_touchingEnterPosition = _state.target.position2D;
_deadspotExpired = false;
}
void StylusPointer::stylusTouchingExit() {
if (!_previousState.target) {
return;
}
// special case to handle home button.
if (_previousState.target.overlayID == getHomeButtonID()) {
DependencyManager::get<MessagesClient>()->sendLocalMessage("home", _previousState.target.overlayID.toString());
}
// send touch end event
_state.target.sendTouchEndEvent();
}
void StylusPointer::stylusTouching() {
qDebug() << "QQQ " << __FUNCTION__;
if (_state.target.overlayID.isNull()) {
return;
}
if (!_deadspotExpired) {
_deadspotExpired =
(_touchingEnterTimer > POINTER_PRESS_TO_MOVE_DELAY) ||
glm::distance2(_state.target.position2D, _touchingEnterPosition) > TOUCH_PRESS_TO_MOVE_DEADSPOT_SQUARED;
}
// Only send moves if the target moves more than the deadspot position or if we've timed out the deadspot
if (_deadspotExpired) {
_state.target.sendTouchMoveEvent();
}
}

View file

@ -0,0 +1,147 @@
//
// Created by Bradley Austin Davis on 2017/10/24
// Copyright 2013-2017 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_StylusPointer_h
#define hifi_StylusPointer_h
#include <QString>
#include <glm/glm.hpp>
#include <pointers/Pointer.h>
#include <pointers/Pick.h>
#include <shared/Bilateral.h>
#include <RegisteredMetaTypes.h>
#include <pointers/Pick.h>
#include "ui/overlays/Overlay.h"
class StylusPick : public Pick<StylusTip> {
using Side = bilateral::Side;
public:
StylusPick(Side side);
StylusTip getMathematicalPick() const override;
PickResultPointer getDefaultResult(const QVariantMap& pickVariant) const override;
PickResultPointer getEntityIntersection(const StylusTip& pick) override;
PickResultPointer getOverlayIntersection(const StylusTip& pick) override;
PickResultPointer getAvatarIntersection(const StylusTip& pick) override;
PickResultPointer getHUDIntersection(const StylusTip& pick) override;
private:
const Side _side;
const bool _useFingerInsteadOfStylus{ false };
};
struct SideData;
struct StylusPickResult : public PickResult {
using Side = bilateral::Side;
// FIXME make into a single ID
OverlayID overlayID;
// FIXME restore entity functionality
#if 0
EntityItemID entityID;
#endif
StylusTip tip;
float distance{ FLT_MAX };
vec3 position;
vec2 position2D;
vec3 normal;
vec3 normalizedPosition;
vec3 dimensions;
bool valid{ false };
virtual bool doesIntersect() const override;
virtual std::shared_ptr<PickResult> compareAndProcessNewResult(const std::shared_ptr<PickResult>& newRes) override;
virtual bool checkOrFilterAgainstMaxDistance(float maxDistance) override;
bool isNormalized() const;
bool isNearNormal(float min, float max, float hystersis) const;
bool isNear2D(float border, float hystersis) const;
bool isNear(float min, float max, float border, float hystersis) const;
operator bool() const;
bool hasKeyboardFocus() const;
void setKeyboardFocus() const;
void sendHoverOverEvent() const;
void sendHoverEnterEvent() const;
void sendTouchStartEvent() const;
void sendTouchEndEvent() const;
void sendTouchMoveEvent() const;
private:
uint32_t deviceId() const;
};
class StylusPointer : public Pointer {
using Parent = Pointer;
using Side = bilateral::Side;
using Ptr = std::shared_ptr<StylusPointer>;
public:
StylusPointer(Side side);
~StylusPointer();
void enable() override;
void disable() override;
void update(unsigned int pointerID, float deltaTime) override;
private:
virtual void setRenderState(const std::string& state) override {}
virtual void editRenderState(const std::string& state, const QVariant& startProps, const QVariant& pathProps, const QVariant& endProps) override {}
virtual PickedObject getHoveredObject(const PickResultPointer& pickResult) override { return PickedObject(); }
virtual Buttons getPressedButtons() override { return {}; }
bool shouldHover() override { return true; }
bool shouldTrigger() override { return true; }
virtual PointerEvent buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult) const override { return PointerEvent(); }
StylusPointer* getOtherStylus();
void updateStylusTarget();
void requestTouchFocus(const StylusPickResult& pickResult);
bool hasTouchFocus(const StylusPickResult& pickResult);
void relinquishTouchFocus();
void stealTouchFocus();
void stylusTouchingEnter();
void stylusTouchingExit();
void stylusTouching();
void show();
void hide();
struct State {
StylusPickResult target;
bool nearTarget{ false };
bool touchingTarget{ false };
};
State _state;
State _previousState;
float _nearHysteresis{ 0.0f };
float _touchHysteresis{ 0.0f };
float _hoverHysteresis{ 0.0f };
float _sensorScaleFactor{ 1.0f };
float _touchingEnterTimer{ 0 };
vec2 _touchingEnterPosition;
bool _deadspotExpired{ false };
bool _renderingEnabled;
OverlayID _stylusOverlay;
OverlayID _hoverOverlay;
const Side _side;
const SideData& _sideData;
};
#endif // hifi_StylusPointer_h

View file

@ -14,6 +14,12 @@
#include <EntityTreeRenderer.h> #include <EntityTreeRenderer.h>
#include <NetworkingConstants.h> #include <NetworkingConstants.h>
#include <NetworkAccessManager.h>
#include <QtNetwork/QNetworkRequest>
#include <QtNetwork/QNetworkReply>
#include <commerce/Ledger.h>
#include <pointers/PointerManager.h>
#ifndef MIN #ifndef MIN
#define MIN(a,b) ((a) < (b) ? (a) : (b)) #define MIN(a,b) ((a) < (b) ? (a) : (b))
@ -42,6 +48,8 @@ ContextOverlayInterface::ContextOverlayInterface() {
_entityPropertyFlags += PROP_DIMENSIONS; _entityPropertyFlags += PROP_DIMENSIONS;
_entityPropertyFlags += PROP_REGISTRATION_POINT; _entityPropertyFlags += PROP_REGISTRATION_POINT;
_entityPropertyFlags += PROP_CERTIFICATE_ID; _entityPropertyFlags += PROP_CERTIFICATE_ID;
_entityPropertyFlags += PROP_CLIENT_ONLY;
_entityPropertyFlags += PROP_OWNING_AVATAR_ID;
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>().data(); auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>().data();
connect(entityScriptingInterface, &EntityScriptingInterface::mousePressOnEntity, this, &ContextOverlayInterface::createOrDestroyContextOverlay); connect(entityScriptingInterface, &EntityScriptingInterface::mousePressOnEntity, this, &ContextOverlayInterface::createOrDestroyContextOverlay);
@ -66,10 +74,13 @@ ContextOverlayInterface::ContextOverlayInterface() {
connect(&qApp->getOverlays(), &Overlays::hoverLeaveOverlay, this, &ContextOverlayInterface::contextOverlays_hoverLeaveOverlay); connect(&qApp->getOverlays(), &Overlays::hoverLeaveOverlay, this, &ContextOverlayInterface::contextOverlays_hoverLeaveOverlay);
connect(_selectionScriptingInterface.data(), &SelectionScriptingInterface::selectedItemsListChanged, &_selectionToSceneHandler, &SelectionToSceneHandler::selectedItemsListChanged); connect(_selectionScriptingInterface.data(), &SelectionScriptingInterface::selectedItemsListChanged, &_selectionToSceneHandler, &SelectionToSceneHandler::selectedItemsListChanged);
auto nodeList = DependencyManager::get<NodeList>();
auto& packetReceiver = nodeList->getPacketReceiver();
packetReceiver.registerListener(PacketType::ChallengeOwnershipReply, this, "handleChallengeOwnershipReplyPacket");
_challengeOwnershipTimeoutTimer.setSingleShot(true);
} }
static const uint32_t MOUSE_HW_ID = 0;
static const uint32_t LEFT_HAND_HW_ID = 1;
static const xColor CONTEXT_OVERLAY_COLOR = { 255, 255, 255 }; static const xColor CONTEXT_OVERLAY_COLOR = { 255, 255, 255 };
static const float CONTEXT_OVERLAY_INSIDE_DISTANCE = 1.0f; // in meters static const float CONTEXT_OVERLAY_INSIDE_DISTANCE = 1.0f; // in meters
static const float CONTEXT_OVERLAY_SIZE = 0.09f; // in meters, same x and y dims static const float CONTEXT_OVERLAY_SIZE = 0.09f; // in meters, same x and y dims
@ -89,7 +100,7 @@ void ContextOverlayInterface::setEnabled(bool enabled) {
bool ContextOverlayInterface::createOrDestroyContextOverlay(const EntityItemID& entityItemID, const PointerEvent& event) { bool ContextOverlayInterface::createOrDestroyContextOverlay(const EntityItemID& entityItemID, const PointerEvent& event) {
if (_enabled && event.getButton() == PointerEvent::SecondaryButton) { if (_enabled && event.getButton() == PointerEvent::SecondaryButton) {
if (contextOverlayFilterPassed(entityItemID)) { if (contextOverlayFilterPassed(entityItemID)) {
if (event.getID() == MOUSE_HW_ID) { if (event.getID() == PointerManager::MOUSE_POINTER_ID || DependencyManager::get<PointerManager>()->isMouse(event.getID())) {
enableEntityHighlight(entityItemID); enableEntityHighlight(entityItemID);
} }
@ -140,7 +151,7 @@ bool ContextOverlayInterface::createOrDestroyContextOverlay(const EntityItemID&
glm::vec3 normal; glm::vec3 normal;
boundingBox.findRayIntersection(cameraPosition, direction, distance, face, normal); boundingBox.findRayIntersection(cameraPosition, direction, distance, face, normal);
float offsetAngle = -CONTEXT_OVERLAY_OFFSET_ANGLE; float offsetAngle = -CONTEXT_OVERLAY_OFFSET_ANGLE;
if (event.getID() == LEFT_HAND_HW_ID) { if (DependencyManager::get<PointerManager>()->isLeftHand(event.getID())) {
offsetAngle *= -1.0f; offsetAngle *= -1.0f;
} }
contextOverlayPosition = cameraPosition + contextOverlayPosition = cameraPosition +
@ -242,13 +253,15 @@ void ContextOverlayInterface::contextOverlays_hoverLeaveOverlay(const OverlayID&
} }
void ContextOverlayInterface::contextOverlays_hoverEnterEntity(const EntityItemID& entityID, const PointerEvent& event) { void ContextOverlayInterface::contextOverlays_hoverEnterEntity(const EntityItemID& entityID, const PointerEvent& event) {
if (contextOverlayFilterPassed(entityID) && _enabled && event.getID() != MOUSE_HW_ID) { bool isMouse = event.getID() == PointerManager::MOUSE_POINTER_ID || DependencyManager::get<PointerManager>()->isMouse(event.getID());
if (contextOverlayFilterPassed(entityID) && _enabled && !isMouse) {
enableEntityHighlight(entityID); enableEntityHighlight(entityID);
} }
} }
void ContextOverlayInterface::contextOverlays_hoverLeaveEntity(const EntityItemID& entityID, const PointerEvent& event) { void ContextOverlayInterface::contextOverlays_hoverLeaveEntity(const EntityItemID& entityID, const PointerEvent& event) {
if (_currentEntityWithContextOverlay != entityID && _enabled && event.getID() != MOUSE_HW_ID) { bool isMouse = event.getID() == PointerManager::MOUSE_POINTER_ID || DependencyManager::get<PointerManager>()->isMouse(event.getID());
if (_currentEntityWithContextOverlay != entityID && _enabled && !isMouse) {
disableEntityHighlight(entityID); disableEntityHighlight(entityID);
} }
} }
@ -260,6 +273,89 @@ void ContextOverlayInterface::openInspectionCertificate() {
auto tablet = dynamic_cast<TabletProxy*>(_tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system")); auto tablet = dynamic_cast<TabletProxy*>(_tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"));
tablet->loadQMLSource(INSPECTION_CERTIFICATE_QML_PATH); tablet->loadQMLSource(INSPECTION_CERTIFICATE_QML_PATH);
_hmdScriptingInterface->openTablet(); _hmdScriptingInterface->openTablet();
setLastInspectedEntity(_currentEntityWithContextOverlay);
EntityItemProperties entityProperties = _entityScriptingInterface->getEntityProperties(_lastInspectedEntity, _entityPropertyFlags);
auto nodeList = DependencyManager::get<NodeList>();
if (entityProperties.getClientOnly()) {
if (entityProperties.verifyStaticCertificateProperties()) {
SharedNodePointer entityServer = nodeList->soloNodeOfType(NodeType::EntityServer);
if (entityServer) {
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
QNetworkRequest networkRequest;
networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
QUrl requestURL = NetworkingConstants::METAVERSE_SERVER_URL;
requestURL.setPath("/api/v1/commerce/proof_of_purchase_status/transfer");
QJsonObject request;
request["certificate_id"] = entityProperties.getCertificateID();
networkRequest.setUrl(requestURL);
QNetworkReply* networkReply = NULL;
networkReply = networkAccessManager.put(networkRequest, QJsonDocument(request).toJson());
connect(networkReply, &QNetworkReply::finished, [=]() {
QJsonObject jsonObject = QJsonDocument::fromJson(networkReply->readAll()).object();
jsonObject = jsonObject["data"].toObject();
if (networkReply->error() == QNetworkReply::NoError) {
if (!jsonObject["invalid_reason"].toString().isEmpty()) {
qCDebug(entities) << "invalid_reason not empty";
} else if (jsonObject["transfer_status"].toArray().first().toString() == "failed") {
qCDebug(entities) << "'transfer_status' is 'failed'";
} else if (jsonObject["transfer_status"].toArray().first().toString() == "pending") {
qCDebug(entities) << "'transfer_status' is 'pending'";
} else {
QString ownerKey = jsonObject["transfer_recipient_key"].toString();
QByteArray certID = entityProperties.getCertificateID().toUtf8();
QByteArray encryptedText = DependencyManager::get<EntityTreeRenderer>()->getTree()->computeEncryptedNonce(certID, ownerKey);
QByteArray nodeToChallengeByteArray = entityProperties.getOwningAvatarID().toRfc4122();
int certIDByteArraySize = certID.length();
int encryptedTextByteArraySize = encryptedText.length();
int nodeToChallengeByteArraySize = nodeToChallengeByteArray.length();
auto challengeOwnershipPacket = NLPacket::create(PacketType::ChallengeOwnershipRequest,
certIDByteArraySize + encryptedTextByteArraySize + nodeToChallengeByteArraySize + 3 * sizeof(int),
true);
challengeOwnershipPacket->writePrimitive(certIDByteArraySize);
challengeOwnershipPacket->writePrimitive(encryptedTextByteArraySize);
challengeOwnershipPacket->writePrimitive(nodeToChallengeByteArraySize);
challengeOwnershipPacket->write(certID);
challengeOwnershipPacket->write(encryptedText);
challengeOwnershipPacket->write(nodeToChallengeByteArray);
nodeList->sendPacket(std::move(challengeOwnershipPacket), *entityServer);
// Kickoff a 10-second timeout timer that marks the cert if we don't get an ownership response in time
if (thread() != QThread::currentThread()) {
QMetaObject::invokeMethod(this, "startChallengeOwnershipTimer");
return;
} else {
startChallengeOwnershipTimer();
}
}
} else {
qCDebug(entities) << "Call to" << networkReply->url() << "failed with error" << networkReply->error() <<
"More info:" << networkReply->readAll();
}
networkReply->deleteLater();
});
} else {
qCWarning(context_overlay) << "Couldn't get Entity Server!";
}
} else {
auto ledger = DependencyManager::get<Ledger>();
_challengeOwnershipTimeoutTimer.stop();
emit ledger->updateCertificateStatus(entityProperties.getCertificateID(), (uint)(ledger->CERTIFICATE_STATUS_STATIC_VERIFICATION_FAILED));
qCDebug(context_overlay) << "Entity" << _lastInspectedEntity << "failed static certificate verification!";
}
}
} }
} }
@ -293,3 +389,39 @@ void ContextOverlayInterface::deletingEntity(const EntityItemID& entityID) {
destroyContextOverlay(_currentEntityWithContextOverlay, PointerEvent()); destroyContextOverlay(_currentEntityWithContextOverlay, PointerEvent());
} }
} }
void ContextOverlayInterface::startChallengeOwnershipTimer() {
auto ledger = DependencyManager::get<Ledger>();
EntityItemProperties entityProperties = _entityScriptingInterface->getEntityProperties(_lastInspectedEntity, _entityPropertyFlags);
connect(&_challengeOwnershipTimeoutTimer, &QTimer::timeout, this, [=]() {
qCDebug(entities) << "Ownership challenge timed out for" << _lastInspectedEntity;
emit ledger->updateCertificateStatus(entityProperties.getCertificateID(), (uint)(ledger->CERTIFICATE_STATUS_VERIFICATION_TIMEOUT));
});
_challengeOwnershipTimeoutTimer.start(5000);
}
void ContextOverlayInterface::handleChallengeOwnershipReplyPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode) {
auto ledger = DependencyManager::get<Ledger>();
_challengeOwnershipTimeoutTimer.stop();
int certIDByteArraySize;
int decryptedTextByteArraySize;
packet->readPrimitive(&certIDByteArraySize);
packet->readPrimitive(&decryptedTextByteArraySize);
QString certID(packet->read(certIDByteArraySize));
QString decryptedText(packet->read(decryptedTextByteArraySize));
EntityItemID id;
bool verificationSuccess = DependencyManager::get<EntityTreeRenderer>()->getTree()->verifyDecryptedNonce(certID, decryptedText, id);
if (verificationSuccess) {
emit ledger->updateCertificateStatus(certID, (uint)(ledger->CERTIFICATE_STATUS_VERIFICATION_SUCCESS));
} else {
emit ledger->updateCertificateStatus(certID, (uint)(ledger->CERTIFICATE_STATUS_OWNER_VERIFICATION_FAILED));
}
}

View file

@ -51,6 +51,7 @@ public:
Q_INVOKABLE QUuid getCurrentEntityWithContextOverlay() { return _currentEntityWithContextOverlay; } Q_INVOKABLE QUuid getCurrentEntityWithContextOverlay() { return _currentEntityWithContextOverlay; }
void setCurrentEntityWithContextOverlay(const QUuid& entityID) { _currentEntityWithContextOverlay = entityID; } void setCurrentEntityWithContextOverlay(const QUuid& entityID) { _currentEntityWithContextOverlay = entityID; }
void setLastInspectedEntity(const QUuid& entityID) { _challengeOwnershipTimeoutTimer.stop(); _lastInspectedEntity = entityID; }
void setEnabled(bool enabled); void setEnabled(bool enabled);
bool getEnabled() { return _enabled; } bool getEnabled() { return _enabled; }
bool getIsInMarketplaceInspectionMode() { return _isInMarketplaceInspectionMode; } bool getIsInMarketplaceInspectionMode() { return _isInMarketplaceInspectionMode; }
@ -70,10 +71,14 @@ public slots:
void contextOverlays_hoverLeaveEntity(const EntityItemID& entityID, const PointerEvent& event); void contextOverlays_hoverLeaveEntity(const EntityItemID& entityID, const PointerEvent& event);
bool contextOverlayFilterPassed(const EntityItemID& entityItemID); bool contextOverlayFilterPassed(const EntityItemID& entityItemID);
private slots:
void handleChallengeOwnershipReplyPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode);
private: private:
bool _verboseLogging { true }; bool _verboseLogging { true };
bool _enabled { true }; bool _enabled { true };
QUuid _currentEntityWithContextOverlay{}; EntityItemID _currentEntityWithContextOverlay{};
EntityItemID _lastInspectedEntity{};
QString _entityMarketplaceID; QString _entityMarketplaceID;
bool _contextOverlayJustClicked { false }; bool _contextOverlayJustClicked { false };
@ -87,6 +92,9 @@ private:
void deletingEntity(const EntityItemID& entityItemID); void deletingEntity(const EntityItemID& entityItemID);
SelectionToSceneHandler _selectionToSceneHandler; SelectionToSceneHandler _selectionToSceneHandler;
Q_INVOKABLE void startChallengeOwnershipTimer();
QTimer _challengeOwnershipTimeoutTimer;
}; };
#endif // hifi_ContextOverlayInterface_h #endif // hifi_ContextOverlayInterface_h

View file

@ -784,8 +784,6 @@ float Overlays::height() {
return offscreenUi->getWindow()->size().height(); return offscreenUi->getWindow()->size().height();
} }
static const uint32_t MOUSE_POINTER_ID = 0;
static glm::vec2 projectOntoOverlayXYPlane(glm::vec3 position, glm::quat rotation, glm::vec2 dimensions, const PickRay& pickRay, static glm::vec2 projectOntoOverlayXYPlane(glm::vec3 position, glm::quat rotation, glm::vec2 dimensions, const PickRay& pickRay,
const RayToOverlayIntersectionResult& rayPickResult) { const RayToOverlayIntersectionResult& rayPickResult) {
@ -846,7 +844,7 @@ PointerEvent Overlays::calculateOverlayPointerEvent(OverlayID overlayID, PickRay
glm::vec2 pos2D = projectOntoOverlayXYPlane(position, rotation, dimensions, ray, rayPickResult); glm::vec2 pos2D = projectOntoOverlayXYPlane(position, rotation, dimensions, ray, rayPickResult);
PointerEvent pointerEvent(eventType, MOUSE_POINTER_ID, pos2D, rayPickResult.intersection, rayPickResult.surfaceNormal, PointerEvent pointerEvent(eventType, PointerManager::MOUSE_POINTER_ID, pos2D, rayPickResult.intersection, rayPickResult.surfaceNormal,
ray.direction, toPointerButton(*event), toPointerButtons(*event), event->modifiers()); ray.direction, toPointerButton(*event), toPointerButtons(*event), event->modifiers());
return pointerEvent; return pointerEvent;

View file

@ -56,6 +56,8 @@
#include "ui/Snapshot.h" #include "ui/Snapshot.h"
#include "SoundCache.h" #include "SoundCache.h"
#include "raypick/PointerScriptingInterface.h"
static const float DPI = 30.47f; static const float DPI = 30.47f;
static const float INCHES_TO_METERS = 1.0f / 39.3701f; static const float INCHES_TO_METERS = 1.0f / 39.3701f;
static const float METERS_TO_INCHES = 39.3701f; static const float METERS_TO_INCHES = 39.3701f;
@ -160,14 +162,6 @@ void Web3DOverlay::buildWebSurface() {
QObject::connect(_webSurface.data(), &OffscreenQmlSurface::webEventReceived, this, &Web3DOverlay::webEventReceived); QObject::connect(_webSurface.data(), &OffscreenQmlSurface::webEventReceived, this, &Web3DOverlay::webEventReceived);
} }
void Web3DOverlay::hoverLeaveOverlay(const PointerEvent& event) {
if ((_pressed || (!_activeTouchPoints.empty() && _touchBeginAccepted))) {
PointerEvent endEvent(PointerEvent::Release, event.getID(), event.getPos2D(), event.getPos3D(), event.getNormal(), event.getDirection(),
event.getButton(), event.getButtons(), event.getKeyboardModifiers());
handlePointerEvent(endEvent);
}
}
void Web3DOverlay::update(float deltatime) { void Web3DOverlay::update(float deltatime) {
if (_webSurface) { if (_webSurface) {
// update globalPosition // update globalPosition
@ -224,6 +218,7 @@ void Web3DOverlay::setupQmlSurface() {
_webSurface->getSurfaceContext()->setContextProperty("Settings", SettingsScriptingInterface::getInstance()); _webSurface->getSurfaceContext()->setContextProperty("Settings", SettingsScriptingInterface::getInstance());
_webSurface->getSurfaceContext()->setContextProperty("Render", AbstractViewStateInterface::instance()->getRenderEngine()->getConfiguration().get()); _webSurface->getSurfaceContext()->setContextProperty("Render", AbstractViewStateInterface::instance()->getRenderEngine()->getConfiguration().get());
_webSurface->getSurfaceContext()->setContextProperty("Controller", DependencyManager::get<controller::ScriptingInterface>().data()); _webSurface->getSurfaceContext()->setContextProperty("Controller", DependencyManager::get<controller::ScriptingInterface>().data());
_webSurface->getSurfaceContext()->setContextProperty("Pointers", DependencyManager::get<PointerScriptingInterface>().data());
_webSurface->getSurfaceContext()->setContextProperty("Web3DOverlay", this); _webSurface->getSurfaceContext()->setContextProperty("Web3DOverlay", this);
_webSurface->getSurfaceContext()->setContextProperty("pathToFonts", "../../"); _webSurface->getSurfaceContext()->setContextProperty("pathToFonts", "../../");
@ -253,18 +248,12 @@ void Web3DOverlay::onResizeWebSurface() {
_webSurface->resize(QSize(_resolution.x, _resolution.y)); _webSurface->resize(QSize(_resolution.x, _resolution.y));
} }
const int INVALID_DEVICE_ID = -1; unsigned int Web3DOverlay::deviceIdByTouchPoint(qreal x, qreal y) {
if (_webSurface) {
Q_INVOKABLE int Web3DOverlay::deviceIdByTouchPoint(qreal x, qreal y) { return _webSurface->deviceIdByTouchPoint(x, y);
auto mapped = _webSurface->getRootItem()->mapFromGlobal(QPoint(x, y)); } else {
return PointerEvent::INVALID_POINTER_ID;
for (auto pair : _activeTouchPoints) {
if (mapped.x() == (int)pair.second.pos().x() && mapped.y() == (int)pair.second.pos().y()) {
return pair.first;
}
} }
return INVALID_DEVICE_ID;
} }
void Web3DOverlay::render(RenderArgs* args) { void Web3DOverlay::render(RenderArgs* args) {
@ -348,6 +337,19 @@ void Web3DOverlay::setProxyWindow(QWindow* proxyWindow) {
_webSurface->setProxyWindow(proxyWindow); _webSurface->setProxyWindow(proxyWindow);
} }
void Web3DOverlay::hoverLeaveOverlay(const PointerEvent& event) {
if (_inputMode == Mouse) {
PointerEvent endEvent(PointerEvent::Release, event.getID(), event.getPos2D(), event.getPos3D(), event.getNormal(), event.getDirection(),
event.getButton(), event.getButtons(), event.getKeyboardModifiers());
handlePointerEvent(endEvent);
// QML onReleased is only triggered if a click has happened first. We need to send this "fake" mouse move event to properly trigger an onExited.
PointerEvent endMoveEvent(PointerEvent::Move, event.getID());
handlePointerEvent(endMoveEvent);
} else if (_webSurface) {
_webSurface->hoverEndEvent(event, _touchDevice);
}
}
void Web3DOverlay::handlePointerEvent(const PointerEvent& event) { void Web3DOverlay::handlePointerEvent(const PointerEvent& event) {
if (event.getType() == PointerEvent::Press) { if (event.getType() == PointerEvent::Press) {
_pressed = true; _pressed = true;
@ -363,93 +365,11 @@ void Web3DOverlay::handlePointerEvent(const PointerEvent& event) {
} }
void Web3DOverlay::handlePointerEventAsTouch(const PointerEvent& event) { void Web3DOverlay::handlePointerEventAsTouch(const PointerEvent& event) {
if (!_webSurface) { if (_webSurface) {
return; PointerEvent webEvent = event;
webEvent.setPos2D(event.getPos2D() * (METERS_TO_INCHES * _dpi));
_webSurface->handlePointerEvent(webEvent, _touchDevice);
} }
glm::vec2 windowPos = event.getPos2D() * (METERS_TO_INCHES * _dpi);
QPointF windowPoint(windowPos.x, windowPos.y);
Qt::TouchPointState state = Qt::TouchPointStationary;
if (event.getType() == PointerEvent::Press && event.getButton() == PointerEvent::PrimaryButton) {
state = Qt::TouchPointPressed;
} else if (event.getType() == PointerEvent::Release) {
state = Qt::TouchPointReleased;
} else if (_activeTouchPoints.count(event.getID()) && windowPoint != _activeTouchPoints[event.getID()].pos()) {
state = Qt::TouchPointMoved;
}
QEvent::Type touchType = QEvent::TouchUpdate;
if (_activeTouchPoints.empty()) {
// If the first active touch point is being created, send a begin
touchType = QEvent::TouchBegin;
} if (state == Qt::TouchPointReleased && _activeTouchPoints.size() == 1 && _activeTouchPoints.count(event.getID())) {
// If the last active touch point is being released, send an end
touchType = QEvent::TouchEnd;
}
{
QTouchEvent::TouchPoint point;
point.setId(event.getID());
point.setState(state);
point.setPos(windowPoint);
point.setScreenPos(windowPoint);
_activeTouchPoints[event.getID()] = point;
}
QTouchEvent touchEvent(touchType, &_touchDevice, event.getKeyboardModifiers());
{
QList<QTouchEvent::TouchPoint> touchPoints;
Qt::TouchPointStates touchPointStates;
for (const auto& entry : _activeTouchPoints) {
touchPointStates |= entry.second.state();
touchPoints.push_back(entry.second);
}
touchEvent.setWindow(_webSurface->getWindow());
touchEvent.setDevice(&_touchDevice);
touchEvent.setTarget(_webSurface->getRootItem());
touchEvent.setTouchPoints(touchPoints);
touchEvent.setTouchPointStates(touchPointStates);
}
// Send mouse events to the Web surface so that HTML dialog elements work with mouse press and hover.
//
// In Qt 5.9 mouse events must be sent before touch events to make sure some QtQuick components will
// receive mouse events
Qt::MouseButton button = Qt::NoButton;
Qt::MouseButtons buttons = Qt::NoButton;
if (event.getButton() == PointerEvent::PrimaryButton) {
button = Qt::LeftButton;
}
if (event.getButtons() & PointerEvent::PrimaryButton) {
buttons |= Qt::LeftButton;
}
#if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0)
if (event.getType() == PointerEvent::Move) {
QMouseEvent mouseEvent(QEvent::MouseMove, windowPoint, windowPoint, windowPoint, button, buttons, Qt::NoModifier);
QCoreApplication::sendEvent(_webSurface->getWindow(), &mouseEvent);
}
#endif
if (touchType == QEvent::TouchBegin) {
_touchBeginAccepted = QCoreApplication::sendEvent(_webSurface->getWindow(), &touchEvent);
} else if (_touchBeginAccepted) {
QCoreApplication::sendEvent(_webSurface->getWindow(), &touchEvent);
}
// If this was a release event, remove the point from the active touch points
if (state == Qt::TouchPointReleased) {
_activeTouchPoints.erase(event.getID());
}
#if QT_VERSION < QT_VERSION_CHECK(5, 9, 0)
if (event.getType() == PointerEvent::Move) {
QMouseEvent mouseEvent(QEvent::MouseMove, windowPoint, windowPoint, windowPoint, button, buttons, Qt::NoModifier);
QCoreApplication::sendEvent(_webSurface->getWindow(), &mouseEvent);
}
#endif
} }
void Web3DOverlay::handlePointerEventAsMouse(const PointerEvent& event) { void Web3DOverlay::handlePointerEventAsMouse(const PointerEvent& event) {
@ -485,7 +405,7 @@ void Web3DOverlay::handlePointerEventAsMouse(const PointerEvent& event) {
return; return;
} }
QMouseEvent mouseEvent(type, windowPoint, windowPoint, windowPoint, button, buttons, Qt::NoModifier); QMouseEvent mouseEvent(type, windowPoint, windowPoint, windowPoint, button, buttons, event.getKeyboardModifiers());
QCoreApplication::sendEvent(_webSurface->getWindow(), &mouseEvent); QCoreApplication::sendEvent(_webSurface->getWindow(), &mouseEvent);
} }

View file

@ -67,7 +67,7 @@ public:
void destroyWebSurface(); void destroyWebSurface();
void onResizeWebSurface(); void onResizeWebSurface();
Q_INVOKABLE int deviceIdByTouchPoint(qreal x, qreal y); Q_INVOKABLE unsigned int deviceIdByTouchPoint(qreal x, qreal y);
public slots: public slots:
void emitScriptEvent(const QVariant& scriptMessage); void emitScriptEvent(const QVariant& scriptMessage);
@ -98,8 +98,6 @@ private:
bool _showKeyboardFocusHighlight{ true }; bool _showKeyboardFocusHighlight{ true };
bool _pressed{ false }; bool _pressed{ false };
bool _touchBeginAccepted { false };
std::map<uint32_t, QTouchEvent::TouchPoint> _activeTouchPoints;
QTouchDevice _touchDevice; QTouchDevice _touchDevice;
uint8_t _desiredMaxFPS { 10 }; uint8_t _desiredMaxFPS { 10 };

View file

@ -571,8 +571,6 @@ static PointerEvent::Button toPointerButton(const QMouseEvent& event) {
} }
} }
static const uint32_t MOUSE_POINTER_ID = 0;
void EntityTreeRenderer::mousePressEvent(QMouseEvent* event) { void EntityTreeRenderer::mousePressEvent(QMouseEvent* event) {
// If we don't have a tree, or we're in the process of shutting down, then don't // If we don't have a tree, or we're in the process of shutting down, then don't
// process these events. // process these events.
@ -593,7 +591,7 @@ void EntityTreeRenderer::mousePressEvent(QMouseEvent* event) {
} }
glm::vec2 pos2D = projectOntoEntityXYPlane(rayPickResult.entity, ray, rayPickResult); glm::vec2 pos2D = projectOntoEntityXYPlane(rayPickResult.entity, ray, rayPickResult);
PointerEvent pointerEvent(PointerEvent::Press, MOUSE_POINTER_ID, PointerEvent pointerEvent(PointerEvent::Press, PointerManager::MOUSE_POINTER_ID,
pos2D, rayPickResult.intersection, pos2D, rayPickResult.intersection,
rayPickResult.surfaceNormal, ray.direction, rayPickResult.surfaceNormal, ray.direction,
toPointerButton(*event), toPointerButtons(*event), toPointerButton(*event), toPointerButtons(*event),
@ -625,7 +623,7 @@ void EntityTreeRenderer::mouseDoublePressEvent(QMouseEvent* event) {
RayToEntityIntersectionResult rayPickResult = _getPrevRayPickResultOperator(_mouseRayPickID); RayToEntityIntersectionResult rayPickResult = _getPrevRayPickResultOperator(_mouseRayPickID);
if (rayPickResult.intersects && rayPickResult.entity) { if (rayPickResult.intersects && rayPickResult.entity) {
glm::vec2 pos2D = projectOntoEntityXYPlane(rayPickResult.entity, ray, rayPickResult); glm::vec2 pos2D = projectOntoEntityXYPlane(rayPickResult.entity, ray, rayPickResult);
PointerEvent pointerEvent(PointerEvent::Press, MOUSE_POINTER_ID, PointerEvent pointerEvent(PointerEvent::Press, PointerManager::MOUSE_POINTER_ID,
pos2D, rayPickResult.intersection, pos2D, rayPickResult.intersection,
rayPickResult.surfaceNormal, ray.direction, rayPickResult.surfaceNormal, ray.direction,
toPointerButton(*event), toPointerButtons(*event), Qt::NoModifier); toPointerButton(*event), toPointerButtons(*event), Qt::NoModifier);
@ -657,7 +655,7 @@ void EntityTreeRenderer::mouseReleaseEvent(QMouseEvent* event) {
//qCDebug(entitiesrenderer) << "mouseReleaseEvent over entity:" << rayPickResult.entityID; //qCDebug(entitiesrenderer) << "mouseReleaseEvent over entity:" << rayPickResult.entityID;
glm::vec2 pos2D = projectOntoEntityXYPlane(rayPickResult.entity, ray, rayPickResult); glm::vec2 pos2D = projectOntoEntityXYPlane(rayPickResult.entity, ray, rayPickResult);
PointerEvent pointerEvent(PointerEvent::Release, MOUSE_POINTER_ID, PointerEvent pointerEvent(PointerEvent::Release, PointerManager::MOUSE_POINTER_ID,
pos2D, rayPickResult.intersection, pos2D, rayPickResult.intersection,
rayPickResult.surfaceNormal, ray.direction, rayPickResult.surfaceNormal, ray.direction,
toPointerButton(*event), toPointerButtons(*event), toPointerButton(*event), toPointerButtons(*event),
@ -673,7 +671,7 @@ void EntityTreeRenderer::mouseReleaseEvent(QMouseEvent* event) {
// we're releasing the button, then this is considered a clickReleaseOn event // we're releasing the button, then this is considered a clickReleaseOn event
if (!_currentClickingOnEntityID.isInvalidID()) { if (!_currentClickingOnEntityID.isInvalidID()) {
glm::vec2 pos2D = projectOntoEntityXYPlane(rayPickResult.entity, ray, rayPickResult); glm::vec2 pos2D = projectOntoEntityXYPlane(rayPickResult.entity, ray, rayPickResult);
PointerEvent pointerEvent(PointerEvent::Release, MOUSE_POINTER_ID, PointerEvent pointerEvent(PointerEvent::Release, PointerManager::MOUSE_POINTER_ID,
pos2D, rayPickResult.intersection, pos2D, rayPickResult.intersection,
rayPickResult.surfaceNormal, ray.direction, rayPickResult.surfaceNormal, ray.direction,
toPointerButton(*event), toPointerButtons(*event), toPointerButton(*event), toPointerButtons(*event),
@ -699,7 +697,7 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event) {
RayToEntityIntersectionResult rayPickResult = _getPrevRayPickResultOperator(_mouseRayPickID); RayToEntityIntersectionResult rayPickResult = _getPrevRayPickResultOperator(_mouseRayPickID);
if (rayPickResult.intersects && rayPickResult.entity) { if (rayPickResult.intersects && rayPickResult.entity) {
glm::vec2 pos2D = projectOntoEntityXYPlane(rayPickResult.entity, ray, rayPickResult); glm::vec2 pos2D = projectOntoEntityXYPlane(rayPickResult.entity, ray, rayPickResult);
PointerEvent pointerEvent(PointerEvent::Move, MOUSE_POINTER_ID, PointerEvent pointerEvent(PointerEvent::Move, PointerManager::MOUSE_POINTER_ID,
pos2D, rayPickResult.intersection, pos2D, rayPickResult.intersection,
rayPickResult.surfaceNormal, ray.direction, rayPickResult.surfaceNormal, ray.direction,
toPointerButton(*event), toPointerButtons(*event), toPointerButton(*event), toPointerButtons(*event),
@ -713,7 +711,7 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event) {
// then we need to send the hover leave. // then we need to send the hover leave.
if (!_currentHoverOverEntityID.isInvalidID() && rayPickResult.entityID != _currentHoverOverEntityID) { if (!_currentHoverOverEntityID.isInvalidID() && rayPickResult.entityID != _currentHoverOverEntityID) {
glm::vec2 pos2D = projectOntoEntityXYPlane(rayPickResult.entity, ray, rayPickResult); glm::vec2 pos2D = projectOntoEntityXYPlane(rayPickResult.entity, ray, rayPickResult);
PointerEvent pointerEvent(PointerEvent::Move, MOUSE_POINTER_ID, PointerEvent pointerEvent(PointerEvent::Move, PointerManager::MOUSE_POINTER_ID,
pos2D, rayPickResult.intersection, pos2D, rayPickResult.intersection,
rayPickResult.surfaceNormal, ray.direction, rayPickResult.surfaceNormal, ray.direction,
toPointerButton(*event), toPointerButtons(*event), toPointerButton(*event), toPointerButtons(*event),
@ -744,7 +742,7 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event) {
// send the hover leave for our previous entity // send the hover leave for our previous entity
if (!_currentHoverOverEntityID.isInvalidID()) { if (!_currentHoverOverEntityID.isInvalidID()) {
glm::vec2 pos2D = projectOntoEntityXYPlane(rayPickResult.entity, ray, rayPickResult); glm::vec2 pos2D = projectOntoEntityXYPlane(rayPickResult.entity, ray, rayPickResult);
PointerEvent pointerEvent(PointerEvent::Move, MOUSE_POINTER_ID, PointerEvent pointerEvent(PointerEvent::Move, PointerManager::MOUSE_POINTER_ID,
pos2D, rayPickResult.intersection, pos2D, rayPickResult.intersection,
rayPickResult.surfaceNormal, ray.direction, rayPickResult.surfaceNormal, ray.direction,
toPointerButton(*event), toPointerButtons(*event), toPointerButton(*event), toPointerButtons(*event),

View file

@ -215,6 +215,7 @@ void RenderableModelEntityItem::updateModelBounds() {
model->setScaleToFit(true, getDimensions()); model->setScaleToFit(true, getDimensions());
model->setSnapModelToRegistrationPoint(true, getRegistrationPoint()); model->setSnapModelToRegistrationPoint(true, getRegistrationPoint());
updateRenderItems = true; updateRenderItems = true;
model->scaleToFit();
} }
bool success; bool success;

View file

@ -315,109 +315,18 @@ void WebEntityRenderer::loadSourceURL() {
} }
void WebEntityRenderer::hoverLeaveEntity(const PointerEvent& event) { void WebEntityRenderer::hoverLeaveEntity(const PointerEvent& event) {
if (!_lastLocked && _webSurface && _pressed) { if (!_lastLocked && _webSurface) {
// If the user mouses off the entity while the button is down, simulate a touch end. _webSurface->hoverEndEvent(event, _touchDevice);
PointerEvent endEvent(PointerEvent::Release, event.getID(), event.getPos2D(), event.getPos3D(), event.getNormal(), event.getDirection(),
event.getButton(), event.getButtons(), event.getKeyboardModifiers());
handlePointerEvent(endEvent);
} }
} }
void WebEntityRenderer::handlePointerEvent(const PointerEvent& event) { void WebEntityRenderer::handlePointerEvent(const PointerEvent& event) {
// Ignore mouse interaction if we're locked // Ignore mouse interaction if we're locked
if (_lastLocked || !_webSurface) { if (!_lastLocked && _webSurface) {
return; PointerEvent webEvent = event;
webEvent.setPos2D(event.getPos2D() * (METERS_TO_INCHES * _lastDPI));
_webSurface->handlePointerEvent(webEvent, _touchDevice);
} }
if (event.getType() == PointerEvent::Press) {
_pressed = true;
} else if (event.getType() == PointerEvent::Release) {
_pressed = false;
}
glm::vec2 windowPos = event.getPos2D() * (METERS_TO_INCHES * _lastDPI);
QPointF windowPoint(windowPos.x, windowPos.y);
Qt::TouchPointState state = Qt::TouchPointStationary;
if (event.getType() == PointerEvent::Press && event.getButton() == PointerEvent::PrimaryButton) {
state = Qt::TouchPointPressed;
} else if (event.getType() == PointerEvent::Release) {
state = Qt::TouchPointReleased;
} else if (_activeTouchPoints.count(event.getID()) && windowPoint != _activeTouchPoints[event.getID()].pos()) {
state = Qt::TouchPointMoved;
}
QEvent::Type touchType = QEvent::TouchUpdate;
if (_activeTouchPoints.empty()) {
// If the first active touch point is being created, send a begin
touchType = QEvent::TouchBegin;
} if (state == Qt::TouchPointReleased && _activeTouchPoints.size() == 1 && _activeTouchPoints.count(event.getID())) {
// If the last active touch point is being released, send an end
touchType = QEvent::TouchEnd;
}
{
QTouchEvent::TouchPoint point;
point.setId(event.getID());
point.setState(state);
point.setPos(windowPoint);
point.setScreenPos(windowPoint);
_activeTouchPoints[event.getID()] = point;
}
QTouchEvent touchEvent(touchType, &_touchDevice, event.getKeyboardModifiers());
{
QList<QTouchEvent::TouchPoint> touchPoints;
Qt::TouchPointStates touchPointStates;
for (const auto& entry : _activeTouchPoints) {
touchPointStates |= entry.second.state();
touchPoints.push_back(entry.second);
}
touchEvent.setWindow(_webSurface->getWindow());
touchEvent.setDevice(&_touchDevice);
touchEvent.setTarget(_webSurface->getRootItem());
touchEvent.setTouchPoints(touchPoints);
touchEvent.setTouchPointStates(touchPointStates);
}
// Send mouse events to the Web surface so that HTML dialog elements work with mouse press and hover.
//
// In Qt 5.9 mouse events must be sent before touch events to make sure some QtQuick components will
// receive mouse events
Qt::MouseButton button = Qt::NoButton;
Qt::MouseButtons buttons = Qt::NoButton;
if (event.getButton() == PointerEvent::PrimaryButton) {
button = Qt::LeftButton;
}
if (event.getButtons() & PointerEvent::PrimaryButton) {
buttons |= Qt::LeftButton;
}
#if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0)
if (event.getType() == PointerEvent::Move) {
QMouseEvent mouseEvent(QEvent::MouseMove, windowPoint, windowPoint, windowPoint, button, buttons, Qt::NoModifier);
QCoreApplication::sendEvent(_webSurface->getWindow(), &mouseEvent);
}
#endif
if (touchType == QEvent::TouchBegin) {
_touchBeginAccepted = QCoreApplication::sendEvent(_webSurface->getWindow(), &touchEvent);
} else if (_touchBeginAccepted) {
QCoreApplication::sendEvent(_webSurface->getWindow(), &touchEvent);
}
// If this was a release event, remove the point from the active touch points
if (state == Qt::TouchPointReleased) {
_activeTouchPoints.erase(event.getID());
}
#if QT_VERSION < QT_VERSION_CHECK(5, 9, 0)
if (event.getType() == PointerEvent::Move) {
QMouseEvent mouseEvent(QEvent::MouseMove, windowPoint, windowPoint, windowPoint, button, buttons, Qt::NoModifier);
QCoreApplication::sendEvent(_webSurface->getWindow(), &mouseEvent);
}
#endif
} }
void WebEntityRenderer::setProxyWindow(QWindow* proxyWindow) { void WebEntityRenderer::setProxyWindow(QWindow* proxyWindow) {

View file

@ -12,8 +12,6 @@
#include <WebEntityItem.h> #include <WebEntityItem.h>
#include "RenderableEntityItem.h" #include "RenderableEntityItem.h"
#include <QTouchEvent>
class OffscreenQmlSurface; class OffscreenQmlSurface;
class PointerEvent; class PointerEvent;
@ -66,10 +64,6 @@ private:
QTimer _timer; QTimer _timer;
uint64_t _lastRenderTime { 0 }; uint64_t _lastRenderTime { 0 };
Transform _renderTransform; Transform _renderTransform;
bool _pressed{ false };
bool _touchBeginAccepted{ false };
std::map<uint32_t, QTouchEvent::TouchPoint> _activeTouchPoints;
}; };
} } // namespace } } // namespace

View file

@ -93,27 +93,41 @@ void EntityEditPacketSender::queueEditEntityMessage(PacketType type,
QByteArray bufferOut(NLPacket::maxPayloadSize(type), 0); QByteArray bufferOut(NLPacket::maxPayloadSize(type), 0);
bool success; OctreeElement::AppendState encodeResult = OctreeElement::PARTIAL; // start the loop assuming there's more to send
auto nodeList = DependencyManager::get<NodeList>(); auto nodeList = DependencyManager::get<NodeList>();
EntityPropertyFlags didntFitProperties;
EntityItemProperties propertiesCopy = properties;
if (properties.parentIDChanged() && properties.getParentID() == AVATAR_SELF_ID) { if (properties.parentIDChanged() && properties.getParentID() == AVATAR_SELF_ID) {
EntityItemProperties propertiesCopy = properties;
const QUuid myNodeID = nodeList->getSessionUUID(); const QUuid myNodeID = nodeList->getSessionUUID();
propertiesCopy.setParentID(myNodeID); propertiesCopy.setParentID(myNodeID);
success = EntityItemProperties::encodeEntityEditPacket(type, entityItemID, propertiesCopy, bufferOut);
} else {
success = EntityItemProperties::encodeEntityEditPacket(type, entityItemID, properties, bufferOut);
} }
if (success) { EntityPropertyFlags requestedProperties = propertiesCopy.getChangedProperties();
#ifdef WANT_DEBUG
qCDebug(entities) << "calling queueOctreeEditMessage()..."; while (encodeResult == OctreeElement::PARTIAL) {
qCDebug(entities) << " id:" << entityItemID; encodeResult = EntityItemProperties::encodeEntityEditPacket(type, entityItemID, propertiesCopy, bufferOut, requestedProperties, didntFitProperties);
qCDebug(entities) << " properties:" << properties;
#endif if (encodeResult != OctreeElement::NONE) {
queueOctreeEditMessage(type, bufferOut); #ifdef WANT_DEBUG
if (type == PacketType::EntityAdd && !properties.getCertificateID().isEmpty()) { qCDebug(entities) << "calling queueOctreeEditMessage()...";
emit addingEntityWithCertificate(properties.getCertificateID(), DependencyManager::get<AddressManager>()->getPlaceName()); qCDebug(entities) << " id:" << entityItemID;
qCDebug(entities) << " properties:" << properties;
#endif
queueOctreeEditMessage(type, bufferOut);
if (type == PacketType::EntityAdd && !properties.getCertificateID().isEmpty()) {
emit addingEntityWithCertificate(properties.getCertificateID(), DependencyManager::get<AddressManager>()->getPlaceName());
}
} }
// if we still have properties to send, switch the message type to edit, and request only the packets that didn't fit
if (encodeResult != OctreeElement::COMPLETED) {
type = PacketType::EntityEdit;
requestedProperties = didntFitProperties;
}
bufferOut.resize(NLPacket::maxPayloadSize(type)); // resize our output buffer for the next packet
} }
} }

View file

@ -14,10 +14,6 @@
#include <QtCore/QObject> #include <QtCore/QObject>
#include <QtEndian> #include <QtEndian>
#include <QJsonDocument> #include <QJsonDocument>
#include <openssl/err.h>
#include <openssl/rsa.h>
#include <openssl/pem.h>
#include <openssl/x509.h>
#include <NetworkingConstants.h> #include <NetworkingConstants.h>
#include <NetworkAccessManager.h> #include <NetworkAccessManager.h>
#include <QtNetwork/QNetworkReply> #include <QtNetwork/QNetworkReply>
@ -87,7 +83,7 @@ EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& param
requestedProperties += PROP_ANGULAR_VELOCITY; requestedProperties += PROP_ANGULAR_VELOCITY;
requestedProperties += PROP_ACCELERATION; requestedProperties += PROP_ACCELERATION;
requestedProperties += PROP_DIMENSIONS; // NOTE: PROP_RADIUS obsolete requestedProperties += PROP_DIMENSIONS;
requestedProperties += PROP_DENSITY; requestedProperties += PROP_DENSITY;
requestedProperties += PROP_GRAVITY; requestedProperties += PROP_GRAVITY;
requestedProperties += PROP_DAMPING; requestedProperties += PROP_DAMPING;
@ -245,7 +241,7 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet
APPEND_ENTITY_PROPERTY(PROP_ANGULAR_VELOCITY, getLocalAngularVelocity()); APPEND_ENTITY_PROPERTY(PROP_ANGULAR_VELOCITY, getLocalAngularVelocity());
APPEND_ENTITY_PROPERTY(PROP_ACCELERATION, getAcceleration()); APPEND_ENTITY_PROPERTY(PROP_ACCELERATION, getAcceleration());
APPEND_ENTITY_PROPERTY(PROP_DIMENSIONS, getDimensions()); // NOTE: PROP_RADIUS obsolete APPEND_ENTITY_PROPERTY(PROP_DIMENSIONS, getDimensions());
APPEND_ENTITY_PROPERTY(PROP_DENSITY, getDensity()); APPEND_ENTITY_PROPERTY(PROP_DENSITY, getDensity());
APPEND_ENTITY_PROPERTY(PROP_GRAVITY, getGravity()); APPEND_ENTITY_PROPERTY(PROP_GRAVITY, getGravity());
APPEND_ENTITY_PROPERTY(PROP_DAMPING, getDamping()); APPEND_ENTITY_PROPERTY(PROP_DAMPING, getDamping());
@ -1575,116 +1571,6 @@ float EntityItem::getRadius() const {
return 0.5f * glm::length(getDimensions()); return 0.5f * glm::length(getDimensions());
} }
// Checking Certifiable Properties
#define ADD_STRING_PROPERTY(n, N) if (!propertySet.get##N().isEmpty()) json[#n] = propertySet.get##N()
#define ADD_ENUM_PROPERTY(n, N) json[#n] = propertySet.get##N##AsString()
#define ADD_INT_PROPERTY(n, N) if (propertySet.get##N() != 0) json[#n] = (propertySet.get##N() == (quint32) -1) ? -1.0 : ((double) propertySet.get##N())
QByteArray EntityItem::getStaticCertificateJSON() const {
// Produce a compact json of every non-default static certificate property, with the property names in alphabetical order.
// The static certificate properties include all an only those properties that cannot be changed without altering the identity
// of the entity as reviewed during the certification submission.
QJsonObject json;
EntityItemProperties propertySet = getProperties(); // Note: neither EntityItem nor EntityitemProperties "properties" are QObject "properties"!
// It is important that this be reproducible in the same order each time. Since we also generate these on the server, we do it alphabetically
// to help maintainence in two different code bases.
if (!propertySet.getAnimation().getURL().isEmpty()) {
json["animationURL"] = propertySet.getAnimation().getURL();
}
ADD_STRING_PROPERTY(collisionSoundURL, CollisionSoundURL);
ADD_STRING_PROPERTY(compoundShapeURL, CompoundShapeURL);
ADD_INT_PROPERTY(editionNumber, EditionNumber);
ADD_INT_PROPERTY(instanceNumber, EntityInstanceNumber);
ADD_STRING_PROPERTY(itemArtist, ItemArtist);
ADD_STRING_PROPERTY(itemCategories, ItemCategories);
ADD_STRING_PROPERTY(itemDescription, ItemDescription);
ADD_STRING_PROPERTY(itemLicenseUrl, ItemLicense);
ADD_STRING_PROPERTY(itemName, ItemName);
ADD_INT_PROPERTY(limitedRun, LimitedRun);
ADD_STRING_PROPERTY(marketplaceID, MarketplaceID);
ADD_STRING_PROPERTY(modelURL, ModelURL);
ADD_STRING_PROPERTY(script, Script);
ADD_ENUM_PROPERTY(shapeType, ShapeType);
json["type"] = EntityTypes::getEntityTypeName(propertySet.getType());
return QJsonDocument(json).toJson(QJsonDocument::Compact);
}
QByteArray EntityItem::getStaticCertificateHash() const {
return QCryptographicHash::hash(getStaticCertificateJSON(), QCryptographicHash::Sha256);
}
bool EntityItem::verifyStaticCertificateProperties() {
// True IIF a non-empty certificateID matches the static certificate json.
// I.e., if we can verify that the certificateID was produced by High Fidelity signing the static certificate hash.
if (getCertificateID().isEmpty()) {
return false;
}
const QByteArray marketplacePublicKeyByteArray = EntityItem::_marketplacePublicKey.toUtf8();
const unsigned char* marketplacePublicKey = reinterpret_cast<const unsigned char*>(marketplacePublicKeyByteArray.constData());
int marketplacePublicKeyLength = marketplacePublicKeyByteArray.length();
BIO *bio = BIO_new_mem_buf((void*)marketplacePublicKey, marketplacePublicKeyLength);
EVP_PKEY* evp_key = PEM_read_bio_PUBKEY(bio, NULL, NULL, NULL);
if (evp_key) {
RSA* rsa = EVP_PKEY_get1_RSA(evp_key);
if (rsa) {
const QByteArray digestByteArray = getStaticCertificateHash();
const unsigned char* digest = reinterpret_cast<const unsigned char*>(digestByteArray.constData());
int digestLength = digestByteArray.length();
const QByteArray signatureByteArray = QByteArray::fromBase64(getCertificateID().toUtf8());
const unsigned char* signature = reinterpret_cast<const unsigned char*>(signatureByteArray.constData());
int signatureLength = signatureByteArray.length();
ERR_clear_error();
bool answer = RSA_verify(NID_sha256,
digest,
digestLength,
signature,
signatureLength,
rsa);
long error = ERR_get_error();
if (error != 0) {
const char* error_str = ERR_error_string(error, NULL);
qCWarning(entities) << "ERROR while verifying static certificate properties! RSA error:" << error_str
<< "\nStatic Cert JSON:" << getStaticCertificateJSON()
<< "\nKey:" << EntityItem::_marketplacePublicKey << "\nKey Length:" << marketplacePublicKeyLength
<< "\nDigest:" << digest << "\nDigest Length:" << digestLength
<< "\nSignature:" << signature << "\nSignature Length:" << signatureLength;
}
RSA_free(rsa);
if (bio) {
BIO_free(bio);
}
if (evp_key) {
EVP_PKEY_free(evp_key);
}
return answer;
} else {
if (bio) {
BIO_free(bio);
}
if (evp_key) {
EVP_PKEY_free(evp_key);
}
long error = ERR_get_error();
const char* error_str = ERR_error_string(error, NULL);
qCWarning(entities) << "Failed to verify static certificate properties! RSA error:" << error_str;
return false;
}
} else {
if (bio) {
BIO_free(bio);
}
long error = ERR_get_error();
const char* error_str = ERR_error_string(error, NULL);
qCWarning(entities) << "Failed to verify static certificate properties! RSA error:" << error_str;
return false;
}
}
void EntityItem::adjustShapeInfoByRegistration(ShapeInfo& info) const { void EntityItem::adjustShapeInfoByRegistration(ShapeInfo& info) const {
if (_registrationPoint != ENTITY_ITEM_DEFAULT_REGISTRATION_POINT) { if (_registrationPoint != ENTITY_ITEM_DEFAULT_REGISTRATION_POINT) {
glm::mat4 scale = glm::scale(getDimensions()); glm::mat4 scale = glm::scale(getDimensions());

View file

@ -328,9 +328,6 @@ public:
void setEntityInstanceNumber(const quint32&); void setEntityInstanceNumber(const quint32&);
QString getCertificateID() const; QString getCertificateID() const;
void setCertificateID(const QString& value); void setCertificateID(const QString& value);
QByteArray getStaticCertificateJSON() const;
QByteArray getStaticCertificateHash() const;
bool verifyStaticCertificateProperties();
// TODO: get rid of users of getRadius()... // TODO: get rid of users of getRadius()...
float getRadius() const; float getRadius() const;

View file

@ -14,6 +14,15 @@
#include <QObject> #include <QObject>
#include <QtCore/QJsonDocument> #include <QtCore/QJsonDocument>
#include <openssl/err.h>
#include <openssl/rsa.h>
#include <openssl/pem.h>
#include <openssl/x509.h>
#include <NetworkingConstants.h>
#include <NetworkAccessManager.h>
#include <QtNetwork/QNetworkReply>
#include <QtNetwork/QNetworkRequest>
#include <ByteCountCoding.h> #include <ByteCountCoding.h>
#include <GLMHelpers.h> #include <GLMHelpers.h>
#include <RegisteredMetaTypes.h> #include <RegisteredMetaTypes.h>
@ -1212,8 +1221,9 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue
// //
// TODO: Implement support for script and visible properties. // TODO: Implement support for script and visible properties.
// //
bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItemID id, const EntityItemProperties& properties, OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItemID id, const EntityItemProperties& properties,
QByteArray& buffer) { QByteArray& buffer, EntityPropertyFlags requestedProperties, EntityPropertyFlags& didntFitProperties) {
OctreePacketData ourDataPacket(false, buffer.size()); // create a packetData object to add out packet details too. OctreePacketData ourDataPacket(false, buffer.size()); // create a packetData object to add out packet details too.
OctreePacketData* packetData = &ourDataPacket; // we want a pointer to this so we can use our APPEND_ENTITY_PROPERTY macro OctreePacketData* packetData = &ourDataPacket; // we want a pointer to this so we can use our APPEND_ENTITY_PROPERTY macro
@ -1255,17 +1265,8 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem
QByteArray encodedUpdateDelta = updateDeltaCoder; QByteArray encodedUpdateDelta = updateDeltaCoder;
EntityPropertyFlags propertyFlags(PROP_LAST_ITEM); EntityPropertyFlags propertyFlags(PROP_LAST_ITEM);
EntityPropertyFlags requestedProperties = properties.getChangedProperties();
EntityPropertyFlags propertiesDidntFit = requestedProperties; EntityPropertyFlags propertiesDidntFit = requestedProperties;
// TODO: we need to handle the multi-pass form of this, similar to how we handle entity data
//
// If we are being called for a subsequent pass at appendEntityData() that failed to completely encode this item,
// then our modelTreeElementExtraEncodeData should include data about which properties we need to append.
//if (modelTreeElementExtraEncodeData && modelTreeElementExtraEncodeData->includedItems.contains(getEntityItemID())) {
// requestedProperties = modelTreeElementExtraEncodeData->includedItems.value(getEntityItemID());
//}
LevelDetails entityLevel = packetData->startLevel(); LevelDetails entityLevel = packetData->startLevel();
// Last Edited quint64 always first, before any other details, which allows us easy access to adjusting this // Last Edited quint64 always first, before any other details, which allows us easy access to adjusting this
@ -1293,7 +1294,7 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem
int propertyCount = 0; int propertyCount = 0;
bool headerFits = successIDFits && successTypeFits && successLastEditedFits bool headerFits = successIDFits && successTypeFits && successLastEditedFits
&& successLastUpdatedFits && successPropertyFlagsFits; && successLastUpdatedFits && successPropertyFlagsFits;
int startOfEntityItemData = packetData->getUncompressedByteOffset(); int startOfEntityItemData = packetData->getUncompressedByteOffset();
@ -1307,7 +1308,7 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem
APPEND_ENTITY_PROPERTY(PROP_SIMULATION_OWNER, properties._simulationOwner.toByteArray()); APPEND_ENTITY_PROPERTY(PROP_SIMULATION_OWNER, properties._simulationOwner.toByteArray());
APPEND_ENTITY_PROPERTY(PROP_POSITION, properties.getPosition()); APPEND_ENTITY_PROPERTY(PROP_POSITION, properties.getPosition());
APPEND_ENTITY_PROPERTY(PROP_DIMENSIONS, properties.getDimensions()); // NOTE: PROP_RADIUS obsolete APPEND_ENTITY_PROPERTY(PROP_DIMENSIONS, properties.getDimensions());
APPEND_ENTITY_PROPERTY(PROP_ROTATION, properties.getRotation()); APPEND_ENTITY_PROPERTY(PROP_ROTATION, properties.getRotation());
APPEND_ENTITY_PROPERTY(PROP_DENSITY, properties.getDensity()); APPEND_ENTITY_PROPERTY(PROP_DENSITY, properties.getDensity());
APPEND_ENTITY_PROPERTY(PROP_VELOCITY, properties.getVelocity()); APPEND_ENTITY_PROPERTY(PROP_VELOCITY, properties.getVelocity());
@ -1463,6 +1464,7 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem
properties.getType() == EntityTypes::Sphere) { properties.getType() == EntityTypes::Sphere) {
APPEND_ENTITY_PROPERTY(PROP_SHAPE, properties.getShape()); APPEND_ENTITY_PROPERTY(PROP_SHAPE, properties.getShape());
} }
APPEND_ENTITY_PROPERTY(PROP_NAME, properties.getName()); APPEND_ENTITY_PROPERTY(PROP_NAME, properties.getName());
APPEND_ENTITY_PROPERTY(PROP_COLLISION_SOUND_URL, properties.getCollisionSoundURL()); APPEND_ENTITY_PROPERTY(PROP_COLLISION_SOUND_URL, properties.getCollisionSoundURL());
APPEND_ENTITY_PROPERTY(PROP_ACTION_DATA, properties.getActionData()); APPEND_ENTITY_PROPERTY(PROP_ACTION_DATA, properties.getActionData());
@ -1513,12 +1515,7 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem
// If any part of the model items didn't fit, then the element is considered partial // If any part of the model items didn't fit, then the element is considered partial
if (appendState != OctreeElement::COMPLETED) { if (appendState != OctreeElement::COMPLETED) {
// TODO: handle mechanism for handling partial fitting data! didntFitProperties = propertiesDidntFit;
// add this item into our list for the next appendElementData() pass
//modelTreeElementExtraEncodeData->includedItems.insert(getEntityItemID(), propertiesDidntFit);
// for now, if it's not complete, it's not successful
success = false;
} }
} }
@ -1534,11 +1531,15 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem
} else { } else {
qCDebug(entities) << "ERROR - encoded edit message doesn't fit in output buffer."; qCDebug(entities) << "ERROR - encoded edit message doesn't fit in output buffer.";
success = false; success = false;
appendState = OctreeElement::NONE; // if we got here, then we didn't include the item
// maybe we should assert!!!
} }
} else { } else {
packetData->discardSubTree(); packetData->discardSubTree();
} }
return success;
return appendState;
} }
QByteArray EntityItemProperties::getPackedNormals() const { QByteArray EntityItemProperties::getPackedNormals() const {
@ -1664,7 +1665,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SIMULATION_OWNER, QByteArray, setSimulationOwner); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SIMULATION_OWNER, QByteArray, setSimulationOwner);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_POSITION, glm::vec3, setPosition); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_POSITION, glm::vec3, setPosition);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_DIMENSIONS, glm::vec3, setDimensions); // NOTE: PROP_RADIUS obsolete READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_DIMENSIONS, glm::vec3, setDimensions);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ROTATION, glm::quat, setRotation); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ROTATION, glm::quat, setRotation);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_DENSITY, float, setDensity); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_DENSITY, float, setDensity);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_VELOCITY, glm::vec3, setVelocity); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_VELOCITY, glm::vec3, setVelocity);
@ -2471,3 +2472,110 @@ bool EntityItemProperties::parentRelatedPropertyChanged() const {
bool EntityItemProperties::queryAACubeRelatedPropertyChanged() const { bool EntityItemProperties::queryAACubeRelatedPropertyChanged() const {
return parentRelatedPropertyChanged() || dimensionsChanged(); return parentRelatedPropertyChanged() || dimensionsChanged();
} }
// Checking Certifiable Properties
#define ADD_STRING_PROPERTY(n, N) if (!get##N().isEmpty()) json[#n] = get##N()
#define ADD_ENUM_PROPERTY(n, N) json[#n] = get##N##AsString()
#define ADD_INT_PROPERTY(n, N) if (get##N() != 0) json[#n] = (get##N() == (quint32) -1) ? -1.0 : ((double) get##N())
QByteArray EntityItemProperties::getStaticCertificateJSON() const {
// Produce a compact json of every non-default static certificate property, with the property names in alphabetical order.
// The static certificate properties include all an only those properties that cannot be changed without altering the identity
// of the entity as reviewed during the certification submission.
QJsonObject json;
if (!getAnimation().getURL().isEmpty()) {
json["animationURL"] = getAnimation().getURL();
}
ADD_STRING_PROPERTY(collisionSoundURL, CollisionSoundURL);
ADD_STRING_PROPERTY(compoundShapeURL, CompoundShapeURL);
ADD_INT_PROPERTY(editionNumber, EditionNumber);
ADD_INT_PROPERTY(instanceNumber, EntityInstanceNumber);
ADD_STRING_PROPERTY(itemArtist, ItemArtist);
ADD_STRING_PROPERTY(itemCategories, ItemCategories);
ADD_STRING_PROPERTY(itemDescription, ItemDescription);
ADD_STRING_PROPERTY(itemLicenseUrl, ItemLicense);
ADD_STRING_PROPERTY(itemName, ItemName);
ADD_INT_PROPERTY(limitedRun, LimitedRun);
ADD_STRING_PROPERTY(marketplaceID, MarketplaceID);
ADD_STRING_PROPERTY(modelURL, ModelURL);
ADD_STRING_PROPERTY(script, Script);
ADD_ENUM_PROPERTY(shapeType, ShapeType);
json["type"] = EntityTypes::getEntityTypeName(getType());
return QJsonDocument(json).toJson(QJsonDocument::Compact);
}
QByteArray EntityItemProperties::getStaticCertificateHash() const {
return QCryptographicHash::hash(getStaticCertificateJSON(), QCryptographicHash::Sha256);
}
bool EntityItemProperties::verifyStaticCertificateProperties() {
// True IIF a non-empty certificateID matches the static certificate json.
// I.e., if we can verify that the certificateID was produced by High Fidelity signing the static certificate hash.
if (getCertificateID().isEmpty()) {
return false;
}
const QByteArray marketplacePublicKeyByteArray = EntityItem::_marketplacePublicKey.toUtf8();
const unsigned char* marketplacePublicKey = reinterpret_cast<const unsigned char*>(marketplacePublicKeyByteArray.constData());
int marketplacePublicKeyLength = marketplacePublicKeyByteArray.length();
BIO *bio = BIO_new_mem_buf((void*)marketplacePublicKey, marketplacePublicKeyLength);
EVP_PKEY* evp_key = PEM_read_bio_PUBKEY(bio, NULL, NULL, NULL);
if (evp_key) {
RSA* rsa = EVP_PKEY_get1_RSA(evp_key);
if (rsa) {
const QByteArray digestByteArray = getStaticCertificateHash();
const unsigned char* digest = reinterpret_cast<const unsigned char*>(digestByteArray.constData());
int digestLength = digestByteArray.length();
const QByteArray signatureByteArray = QByteArray::fromBase64(getCertificateID().toUtf8());
const unsigned char* signature = reinterpret_cast<const unsigned char*>(signatureByteArray.constData());
int signatureLength = signatureByteArray.length();
ERR_clear_error();
bool answer = RSA_verify(NID_sha256,
digest,
digestLength,
signature,
signatureLength,
rsa);
long error = ERR_get_error();
if (error != 0) {
const char* error_str = ERR_error_string(error, NULL);
qCWarning(entities) << "ERROR while verifying static certificate properties! RSA error:" << error_str
<< "\nStatic Cert JSON:" << getStaticCertificateJSON()
<< "\nKey:" << EntityItem::_marketplacePublicKey << "\nKey Length:" << marketplacePublicKeyLength
<< "\nDigest:" << digest << "\nDigest Length:" << digestLength
<< "\nSignature:" << signature << "\nSignature Length:" << signatureLength;
}
RSA_free(rsa);
if (bio) {
BIO_free(bio);
}
if (evp_key) {
EVP_PKEY_free(evp_key);
}
return answer;
} else {
if (bio) {
BIO_free(bio);
}
if (evp_key) {
EVP_PKEY_free(evp_key);
}
long error = ERR_get_error();
const char* error_str = ERR_error_string(error, NULL);
qCWarning(entities) << "Failed to verify static certificate properties! RSA error:" << error_str;
return false;
}
} else {
if (bio) {
BIO_free(bio);
}
long error = ERR_get_error();
const char* error_str = ERR_error_string(error, NULL);
qCWarning(entities) << "Failed to verify static certificate properties! RSA error:" << error_str;
return false;
}
}

View file

@ -262,8 +262,8 @@ public:
float getLocalRenderAlpha() const { return _localRenderAlpha; } float getLocalRenderAlpha() const { return _localRenderAlpha; }
void setLocalRenderAlpha(float value) { _localRenderAlpha = value; _localRenderAlphaChanged = true; } void setLocalRenderAlpha(float value) { _localRenderAlpha = value; _localRenderAlphaChanged = true; }
static bool encodeEntityEditPacket(PacketType command, EntityItemID id, const EntityItemProperties& properties, static OctreeElement::AppendState encodeEntityEditPacket(PacketType command, EntityItemID id, const EntityItemProperties& properties,
QByteArray& buffer); QByteArray& buffer, EntityPropertyFlags requestedProperties, EntityPropertyFlags& didntFitProperties);
static bool encodeEraseEntityMessage(const EntityItemID& entityItemID, QByteArray& buffer); static bool encodeEraseEntityMessage(const EntityItemID& entityItemID, QByteArray& buffer);
@ -336,6 +336,10 @@ public:
QByteArray getPackedStrokeColors() const; QByteArray getPackedStrokeColors() const;
QByteArray packStrokeColors(const QVector<glm::vec3>& strokeColors) const; QByteArray packStrokeColors(const QVector<glm::vec3>& strokeColors) const;
QByteArray getStaticCertificateJSON() const;
QByteArray getStaticCertificateHash() const;
bool verifyStaticCertificateProperties();
protected: protected:
QString getCollisionMaskAsString() const; QString getCollisionMaskAsString() const;
void setCollisionMaskFromString(const QString& maskString); void setCollisionMaskFromString(const QString& maskString);

View file

@ -21,8 +21,7 @@ enum EntityPropertyList {
// these properties are supported by the EntityItem base class // these properties are supported by the EntityItem base class
PROP_VISIBLE, PROP_VISIBLE,
PROP_POSITION, PROP_POSITION,
PROP_RADIUS, // NOTE: PROP_RADIUS is obsolete and only included in old format streams PROP_DIMENSIONS,
PROP_DIMENSIONS = PROP_RADIUS,
PROP_ROTATION, PROP_ROTATION,
PROP_DENSITY, PROP_DENSITY,
PROP_VELOCITY, PROP_VELOCITY,
@ -47,13 +46,13 @@ enum EntityPropertyList {
PROP_ANGULAR_VELOCITY, PROP_ANGULAR_VELOCITY,
PROP_ANGULAR_DAMPING, PROP_ANGULAR_DAMPING,
PROP_COLLISIONLESS, PROP_COLLISIONLESS,
PROP_DYNAMIC, PROP_DYNAMIC, // 24
// property used by Light entity // property used by Light entity
PROP_IS_SPOTLIGHT, PROP_IS_SPOTLIGHT,
PROP_DIFFUSE_COLOR, PROP_DIFFUSE_COLOR,
PROP_AMBIENT_COLOR_UNUSED, PROP_AMBIENT_COLOR_UNUSED, // FIXME - No longer used, can remove and bump protocol
PROP_SPECULAR_COLOR_UNUSED, PROP_SPECULAR_COLOR_UNUSED, // FIXME - No longer used, can remove and bump protocol
PROP_INTENSITY, // Previously PROP_CONSTANT_ATTENUATION PROP_INTENSITY, // Previously PROP_CONSTANT_ATTENUATION
PROP_LINEAR_ATTENUATION_UNUSED, PROP_LINEAR_ATTENUATION_UNUSED,
PROP_QUADRATIC_ATTENUATION_UNUSED, PROP_QUADRATIC_ATTENUATION_UNUSED,
@ -61,30 +60,30 @@ enum EntityPropertyList {
PROP_CUTOFF, PROP_CUTOFF,
// available to all entities // available to all entities
PROP_LOCKED, PROP_LOCKED, // 34
PROP_TEXTURES, // used by Model entities PROP_TEXTURES, // used by Model entities
PROP_ANIMATION_SETTINGS, // used by Model entities PROP_ANIMATION_SETTINGS_UNUSED, // FIXME - No longer used, can remove and bump protocol
PROP_USER_DATA, // all entities PROP_USER_DATA, // all entities -- 37
PROP_SHAPE_TYPE, // used by Model + zones entities PROP_SHAPE_TYPE, // used by Model + zones entities
// used by ParticleEffect entities // used by ParticleEffect entities
PROP_MAX_PARTICLES, PROP_MAX_PARTICLES, // 39
PROP_LIFESPAN, PROP_LIFESPAN, // 40 -- used by all entities
PROP_EMIT_RATE, PROP_EMIT_RATE,
PROP_EMIT_SPEED, PROP_EMIT_SPEED,
PROP_EMIT_STRENGTH, PROP_EMIT_STRENGTH,
PROP_EMIT_ACCELERATION, PROP_EMIT_ACCELERATION, // FIXME - doesn't seem to get set in mark all changed????
PROP_PARTICLE_RADIUS, PROP_PARTICLE_RADIUS, // 45!!
PROP_COMPOUND_SHAPE_URL, // used by Model + zones entities PROP_COMPOUND_SHAPE_URL, // used by Model + zones entities
PROP_MARKETPLACE_ID, // all entities PROP_MARKETPLACE_ID, // all entities
PROP_ACCELERATION, // all entities PROP_ACCELERATION, // all entities
PROP_SIMULATION_OWNER, // formerly known as PROP_SIMULATOR_ID PROP_SIMULATION_OWNER, // formerly known as PROP_SIMULATOR_ID
PROP_NAME, // all entities PROP_NAME, // all entities -- 50
PROP_COLLISION_SOUND_URL, PROP_COLLISION_SOUND_URL,
PROP_RESTITUTION, PROP_RESTITUTION,
PROP_FRICTION, PROP_FRICTION, // 53
PROP_VOXEL_VOLUME_SIZE, PROP_VOXEL_VOLUME_SIZE,
PROP_VOXEL_DATA, PROP_VOXEL_DATA,
@ -96,7 +95,7 @@ enum EntityPropertyList {
// used by hyperlinks // used by hyperlinks
PROP_HREF, PROP_HREF,
PROP_DESCRIPTION, PROP_DESCRIPTION, // 61
PROP_FACE_CAMERA, PROP_FACE_CAMERA,
PROP_SCRIPT_TIMESTAMP, PROP_SCRIPT_TIMESTAMP,

View file

@ -1827,7 +1827,7 @@ bool EntityScriptingInterface::verifyStaticCertificateProperties(const QUuid& en
_entityTree->withReadLock([&] { _entityTree->withReadLock([&] {
EntityItemPointer entity = _entityTree->findEntityByEntityItemID(EntityItemID(entityID)); EntityItemPointer entity = _entityTree->findEntityByEntityItemID(EntityItemID(entityID));
if (entity) { if (entity) {
result = entity->verifyStaticCertificateProperties(); result = entity->getProperties().verifyStaticCertificateProperties();
} }
}); });
} }

View file

@ -1195,7 +1195,6 @@ QByteArray EntityTree::computeEncryptedNonce(const QString& certID, const QStrin
QWriteLocker locker(&_certNonceMapLock); QWriteLocker locker(&_certNonceMapLock);
_certNonceMap.insert(certID, nonce); _certNonceMap.insert(certID, nonce);
qCDebug(entities) << "Challenging ownership of Cert ID" << certID << "by encrypting and sending nonce" << nonce << "to owner.";
return encryptedText; return encryptedText;
} else { } else {
@ -1206,9 +1205,7 @@ QByteArray EntityTree::computeEncryptedNonce(const QString& certID, const QStrin
} }
} }
bool EntityTree::verifyDecryptedNonce(const QString& certID, const QString& decryptedNonce) { bool EntityTree::verifyDecryptedNonce(const QString& certID, const QString& decryptedNonce, EntityItemID& id) {
EntityItemID id;
{ {
QReadLocker certIdMapLocker(&_entityCertificateIDMapLock); QReadLocker certIdMapLocker(&_entityCertificateIDMapLock);
id = _entityCertificateIDMap.value(certID); id = _entityCertificateIDMap.value(certID);
@ -1221,19 +1218,116 @@ bool EntityTree::verifyDecryptedNonce(const QString& certID, const QString& decr
} }
bool verificationSuccess = (actualNonce == decryptedNonce); bool verificationSuccess = (actualNonce == decryptedNonce);
if (!verificationSuccess) {
if (!id.isNull()) { if (verificationSuccess) {
qCDebug(entities) << "Ownership challenge for Cert ID" << certID << "failed; deleting entity" << id qCDebug(entities) << "Ownership challenge for Cert ID" << certID << "succeeded.";
<< "\nActual nonce:" << actualNonce << "\nDecrypted nonce:" << decryptedNonce;
deleteEntity(id, true);
}
} else { } else {
qCDebug(entities) << "Ownership challenge for Cert ID" << certID << "succeeded; keeping entity" << id; qCDebug(entities) << "Ownership challenge for Cert ID" << certID << "failed."
<< "\nActual nonce:" << actualNonce << "\nDecrypted nonce:" << decryptedNonce;
} }
return verificationSuccess; return verificationSuccess;
} }
void EntityTree::processChallengeOwnershipRequestPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) {
int certIDByteArraySize;
int encryptedTextByteArraySize;
int nodeToChallengeByteArraySize;
message.readPrimitive(&certIDByteArraySize);
message.readPrimitive(&encryptedTextByteArraySize);
message.readPrimitive(&nodeToChallengeByteArraySize);
QByteArray certID(message.read(certIDByteArraySize));
QByteArray encryptedText(message.read(encryptedTextByteArraySize));
QByteArray nodeToChallenge(message.read(nodeToChallengeByteArraySize));
sendChallengeOwnershipRequestPacket(certID, encryptedText, nodeToChallenge, sourceNode);
}
void EntityTree::processChallengeOwnershipReplyPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) {
auto nodeList = DependencyManager::get<NodeList>();
int certIDByteArraySize;
int decryptedTextByteArraySize;
int challengingNodeUUIDByteArraySize;
message.readPrimitive(&certIDByteArraySize);
message.readPrimitive(&decryptedTextByteArraySize);
message.readPrimitive(&challengingNodeUUIDByteArraySize);
QByteArray certID(message.read(certIDByteArraySize));
QByteArray decryptedText(message.read(decryptedTextByteArraySize));
QUuid challengingNode = QUuid::fromRfc4122(message.read(challengingNodeUUIDByteArraySize));
auto challengeOwnershipReplyPacket = NLPacket::create(PacketType::ChallengeOwnershipReply,
certIDByteArraySize + decryptedText.length() + 2 * sizeof(int),
true);
challengeOwnershipReplyPacket->writePrimitive(certIDByteArraySize);
challengeOwnershipReplyPacket->writePrimitive(decryptedText.length());
challengeOwnershipReplyPacket->write(certID);
challengeOwnershipReplyPacket->write(decryptedText);
nodeList->sendPacket(std::move(challengeOwnershipReplyPacket), *(nodeList->nodeWithUUID(challengingNode)));
}
void EntityTree::sendChallengeOwnershipPacket(const QString& certID, const QString& ownerKey, const EntityItemID& entityItemID, const SharedNodePointer& senderNode) {
// 1. Encrypt a nonce with the owner's public key
auto nodeList = DependencyManager::get<NodeList>();
QByteArray encryptedText = computeEncryptedNonce(certID, ownerKey);
if (encryptedText == "") {
qCDebug(entities) << "CRITICAL ERROR: Couldn't compute encrypted nonce. Deleting entity...";
deleteEntity(entityItemID, true);
} else {
qCDebug(entities) << "Challenging ownership of Cert ID" << certID;
// 2. Send the encrypted text to the rezzing avatar's node
QByteArray certIDByteArray = certID.toUtf8();
int certIDByteArraySize = certIDByteArray.size();
auto challengeOwnershipPacket = NLPacket::create(PacketType::ChallengeOwnership,
certIDByteArraySize + encryptedText.length() + 2 * sizeof(int),
true);
challengeOwnershipPacket->writePrimitive(certIDByteArraySize);
challengeOwnershipPacket->writePrimitive(encryptedText.length());
challengeOwnershipPacket->write(certIDByteArray);
challengeOwnershipPacket->write(encryptedText);
nodeList->sendPacket(std::move(challengeOwnershipPacket), *senderNode);
// 3. Kickoff a 10-second timeout timer that deletes the entity if we don't get an ownership response in time
if (thread() != QThread::currentThread()) {
QMetaObject::invokeMethod(this, "startChallengeOwnershipTimer", Q_ARG(const EntityItemID&, entityItemID));
return;
} else {
startChallengeOwnershipTimer(entityItemID);
}
}
}
void EntityTree::sendChallengeOwnershipRequestPacket(const QByteArray& certID, const QByteArray& encryptedText, const QByteArray& nodeToChallenge, const SharedNodePointer& senderNode) {
auto nodeList = DependencyManager::get<NodeList>();
// In this case, Client A is challenging Client B. Client A is inspecting a certified entity that it wants
// to make sure belongs to Avatar B.
QByteArray senderNodeUUID = senderNode->getUUID().toRfc4122();
int certIDByteArraySize = certID.length();
int encryptedTextByteArraySize = encryptedText.length();
int senderNodeUUIDSize = senderNodeUUID.length();
auto challengeOwnershipPacket = NLPacket::create(PacketType::ChallengeOwnershipRequest,
certIDByteArraySize + encryptedTextByteArraySize + senderNodeUUIDSize + 3 * sizeof(int),
true);
challengeOwnershipPacket->writePrimitive(certIDByteArraySize);
challengeOwnershipPacket->writePrimitive(encryptedTextByteArraySize);
challengeOwnershipPacket->writePrimitive(senderNodeUUIDSize);
challengeOwnershipPacket->write(certID);
challengeOwnershipPacket->write(encryptedText);
challengeOwnershipPacket->write(senderNodeUUID);
nodeList->sendPacket(std::move(challengeOwnershipPacket), *(nodeList->nodeWithUUID(QUuid::fromRfc4122(nodeToChallenge))));
}
void EntityTree::validatePop(const QString& certID, const EntityItemID& entityItemID, const SharedNodePointer& senderNode, bool isRetryingValidation) { void EntityTree::validatePop(const QString& certID, const EntityItemID& entityItemID, const SharedNodePointer& senderNode, bool isRetryingValidation) {
// Start owner verification. // Start owner verification.
auto nodeList = DependencyManager::get<NodeList>(); auto nodeList = DependencyManager::get<NodeList>();
@ -1279,33 +1373,11 @@ void EntityTree::validatePop(const QString& certID, const EntityItemID& entityIt
} }
} else { } else {
// Second, challenge ownership of the PoP cert // Second, challenge ownership of the PoP cert
// 1. Encrypt a nonce with the owner's public key sendChallengeOwnershipPacket(certID,
QByteArray encryptedText = computeEncryptedNonce(certID, jsonObject["transfer_recipient_key"].toString()); jsonObject["transfer_recipient_key"].toString(),
entityItemID,
senderNode);
if (encryptedText == "") {
qCDebug(entities) << "CRITICAL ERROR: Couldn't compute encrypted nonce. Deleting entity...";
deleteEntity(entityItemID, true);
} else {
// 2. Send the encrypted text to the rezzing avatar's node
QByteArray certIDByteArray = certID.toUtf8();
int certIDByteArraySize = certIDByteArray.size();
auto challengeOwnershipPacket = NLPacket::create(PacketType::ChallengeOwnership,
certIDByteArraySize + encryptedText.length() + 2 * sizeof(int),
true);
challengeOwnershipPacket->writePrimitive(certIDByteArraySize);
challengeOwnershipPacket->writePrimitive(encryptedText.length());
challengeOwnershipPacket->write(certIDByteArray);
challengeOwnershipPacket->write(encryptedText);
nodeList->sendPacket(std::move(challengeOwnershipPacket), *senderNode);
// 3. Kickoff a 10-second timeout timer that deletes the entity if we don't get an ownership response in time
if (thread() != QThread::currentThread()) {
QMetaObject::invokeMethod(this, "startChallengeOwnershipTimer", Q_ARG(const EntityItemID&, entityItemID));
return;
} else {
startChallengeOwnershipTimer(entityItemID);
}
}
} }
} else { } else {
qCDebug(entities) << "Call to" << networkReply->url() << "failed with error" << networkReply->error() << "; deleting entity" << entityItemID qCDebug(entities) << "Call to" << networkReply->url() << "failed with error" << networkReply->error() << "; deleting entity" << entityItemID
@ -1329,7 +1401,12 @@ void EntityTree::processChallengeOwnershipPacket(ReceivedMessage& message, const
emit killChallengeOwnershipTimeoutTimer(certID); emit killChallengeOwnershipTimeoutTimer(certID);
verifyDecryptedNonce(certID, decryptedText); EntityItemID id;
if (!verifyDecryptedNonce(certID, decryptedText, id)) {
if (!id.isNull()) {
deleteEntity(id, true);
}
}
} }
int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned char* editData, int maxLength, int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned char* editData, int maxLength,
@ -1528,7 +1605,7 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c
_totalCreates++; _totalCreates++;
if (newEntity && isCertified && getIsServer()) { if (newEntity && isCertified && getIsServer()) {
if (!newEntity->verifyStaticCertificateProperties()) { if (!properties.verifyStaticCertificateProperties()) {
qCDebug(entities) << "User" << senderNode->getUUID() qCDebug(entities) << "User" << senderNode->getUUID()
<< "attempted to add a certified entity with ID" << entityItemID << "which failed" << "attempted to add a certified entity with ID" << entityItemID << "which failed"
<< "static certificate verification."; << "static certificate verification.";

View file

@ -93,6 +93,8 @@ public:
void fixupTerseEditLogging(EntityItemProperties& properties, QList<QString>& changedProperties); void fixupTerseEditLogging(EntityItemProperties& properties, QList<QString>& changedProperties);
virtual int processEditPacketData(ReceivedMessage& message, const unsigned char* editData, int maxLength, virtual int processEditPacketData(ReceivedMessage& message, const unsigned char* editData, int maxLength,
const SharedNodePointer& senderNode) override; const SharedNodePointer& senderNode) override;
virtual void processChallengeOwnershipRequestPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) override;
virtual void processChallengeOwnershipReplyPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) override;
virtual void processChallengeOwnershipPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) override; virtual void processChallengeOwnershipPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) override;
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
@ -273,6 +275,9 @@ public:
static const float DEFAULT_MAX_TMP_ENTITY_LIFETIME; static const float DEFAULT_MAX_TMP_ENTITY_LIFETIME;
QByteArray computeEncryptedNonce(const QString& certID, const QString ownerKey);
bool verifyDecryptedNonce(const QString& certID, const QString& decryptedNonce, EntityItemID& id);
signals: signals:
void deletingEntity(const EntityItemID& entityID); void deletingEntity(const EntityItemID& entityID);
void deletingEntityPointer(EntityItem* entityID); void deletingEntityPointer(EntityItem* entityID);
@ -375,8 +380,8 @@ protected:
Q_INVOKABLE void startPendingTransferStatusTimer(const QString& certID, const EntityItemID& entityItemID, const SharedNodePointer& senderNode); Q_INVOKABLE void startPendingTransferStatusTimer(const QString& certID, const EntityItemID& entityItemID, const SharedNodePointer& senderNode);
private: private:
QByteArray computeEncryptedNonce(const QString& certID, const QString ownerKey); void sendChallengeOwnershipPacket(const QString& certID, const QString& ownerKey, const EntityItemID& entityItemID, const SharedNodePointer& senderNode);
bool verifyDecryptedNonce(const QString& certID, const QString& decryptedNonce); void sendChallengeOwnershipRequestPacket(const QByteArray& certID, const QByteArray& encryptedText, const QByteArray& nodeToChallenge, const SharedNodePointer& senderNode);
void validatePop(const QString& certID, const EntityItemID& entityItemID, const SharedNodePointer& senderNode, bool isRetryingValidation); void validatePop(const QString& certID, const EntityItemID& entityItemID, const SharedNodePointer& senderNode, bool isRetryingValidation);
}; };

View file

@ -667,8 +667,11 @@ bool AddressManager::handleViewpoint(const QString& viewpointString, bool should
qCDebug(networking) << "Orientation parsed from lookup string is invalid. Will not use for location change."; qCDebug(networking) << "Orientation parsed from lookup string is invalid. Will not use for location change.";
} }
} }
emit locationChangeRequired(newPosition, orientationChanged, newOrientation, shouldFace); emit locationChangeRequired(newPosition, orientationChanged,
LookupTrigger::VisitUserFromPAL ? cancelOutRollAndPitch(newOrientation): newOrientation,
shouldFace
);
} else { } else {
qCDebug(networking) << "Could not jump to position from lookup string because it has an invalid value."; qCDebug(networking) << "Could not jump to position from lookup string because it has an invalid value.";
@ -732,13 +735,14 @@ bool AddressManager::setDomainInfo(const QString& hostname, quint16 port, Lookup
return hostChanged; return hostChanged;
} }
void AddressManager::goToUser(const QString& username) { void AddressManager::goToUser(const QString& username, bool shouldMatchOrientation) {
QString formattedUsername = QUrl::toPercentEncoding(username); QString formattedUsername = QUrl::toPercentEncoding(username);
// for history storage handling we remember how this lookup was trigged - for a username it's always user input // for history storage handling we remember how this lookup was triggered - for a username it's always user input
QVariantMap requestParams; QVariantMap requestParams;
requestParams.insert(LOOKUP_TRIGGER_KEY, static_cast<int>(LookupTrigger::UserInput)); requestParams.insert(LOOKUP_TRIGGER_KEY, static_cast<int>(
shouldMatchOrientation ? LookupTrigger::UserInput : LookupTrigger::VisitUserFromPAL
));
// this is a username - pull the captured name and lookup that user's location // this is a username - pull the captured name and lookup that user's location
DependencyManager::get<AccountManager>()->sendRequest(GET_USER_LOCATION.arg(formattedUsername), DependencyManager::get<AccountManager>()->sendRequest(GET_USER_LOCATION.arg(formattedUsername),
AccountManagerAuth::Optional, AccountManagerAuth::Optional,
@ -840,8 +844,8 @@ void AddressManager::addCurrentAddressToHistory(LookupTrigger trigger) {
// and do not but it into the back stack // and do not but it into the back stack
_forwardStack.push(currentAddress()); _forwardStack.push(currentAddress());
} else { } else {
if (trigger == LookupTrigger::UserInput) { if (trigger == LookupTrigger::UserInput || trigger == LookupTrigger::VisitUserFromPAL) {
// anyime the user has manually looked up an address we know we should clear the forward stack // anyime the user has actively triggered an address we know we should clear the forward stack
_forwardStack.clear(); _forwardStack.clear();
emit goForwardPossible(false); emit goForwardPossible(false);

View file

@ -54,7 +54,8 @@ public:
DomainPathResponse, DomainPathResponse,
Internal, Internal,
AttemptedRefresh, AttemptedRefresh,
Suggestions Suggestions,
VisitUserFromPAL
}; };
bool isConnected(); bool isConnected();
@ -93,7 +94,7 @@ public slots:
void goToLocalSandbox(QString path = "", LookupTrigger trigger = LookupTrigger::StartupFromSettings) { handleUrl(SANDBOX_HIFI_ADDRESS + path, trigger); } void goToLocalSandbox(QString path = "", LookupTrigger trigger = LookupTrigger::StartupFromSettings) { handleUrl(SANDBOX_HIFI_ADDRESS + path, trigger); }
void goToEntry(LookupTrigger trigger = LookupTrigger::StartupFromSettings) { handleUrl(DEFAULT_HIFI_ADDRESS, trigger); } void goToEntry(LookupTrigger trigger = LookupTrigger::StartupFromSettings) { handleUrl(DEFAULT_HIFI_ADDRESS, trigger); }
void goToUser(const QString& username); void goToUser(const QString& username, bool shouldMatchOrientation = true);
void refreshPreviousLookup(); void refreshPreviousLookup();

View file

@ -34,7 +34,7 @@ public:
HifiSockAddr(const sockaddr* sockaddr); HifiSockAddr(const sockaddr* sockaddr);
bool isNull() const { return _address.isNull() && _port == 0; } bool isNull() const { return _address.isNull() && _port == 0; }
void clear() { _address = QHostAddress::Null; _port = 0;} void clear() { _address.clear(); _port = 0;}
HifiSockAddr& operator=(const HifiSockAddr& rhsSockAddr); HifiSockAddr& operator=(const HifiSockAddr& rhsSockAddr);
void swap(HifiSockAddr& otherSockAddr); void swap(HifiSockAddr& otherSockAddr);

View file

@ -16,8 +16,8 @@
namespace NetworkingConstants { namespace NetworkingConstants {
// If you want to use STAGING instead of STABLE, // If you want to use STAGING instead of STABLE,
// don't forget to ALSO change the Domain Server Metaverse Server URL, which is at the top of: // don't forget to ALSO change the Domain Server Metaverse Server URL inside of:
// <hifi repo>\domain-server\resources\web\settings\js\settings.js // <hifi repo>\domain-server\resources\web\js\shared.js
const QUrl METAVERSE_SERVER_URL_STABLE("https://metaverse.highfidelity.com"); const QUrl METAVERSE_SERVER_URL_STABLE("https://metaverse.highfidelity.com");
const QUrl METAVERSE_SERVER_URL_STAGING("https://staging.highfidelity.com"); const QUrl METAVERSE_SERVER_URL_STAGING("https://staging.highfidelity.com");
const QUrl METAVERSE_SERVER_URL = METAVERSE_SERVER_URL_STABLE; const QUrl METAVERSE_SERVER_URL = METAVERSE_SERVER_URL_STABLE;

View file

@ -124,6 +124,8 @@ public:
OctreeFileReplacementFromUrl, OctreeFileReplacementFromUrl,
ChallengeOwnership, ChallengeOwnership,
EntityScriptCallMethod, EntityScriptCallMethod,
ChallengeOwnershipRequest,
ChallengeOwnershipReply,
NUM_PACKET_TYPE NUM_PACKET_TYPE
}; };

View file

@ -212,6 +212,8 @@ public:
virtual bool handlesEditPacketType(PacketType packetType) const { return false; } virtual bool handlesEditPacketType(PacketType packetType) const { return false; }
virtual int processEditPacketData(ReceivedMessage& message, const unsigned char* editData, int maxLength, virtual int processEditPacketData(ReceivedMessage& message, const unsigned char* editData, int maxLength,
const SharedNodePointer& sourceNode) { return 0; } const SharedNodePointer& sourceNode) { return 0; }
virtual void processChallengeOwnershipRequestPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) { return; }
virtual void processChallengeOwnershipReplyPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) { return; }
virtual void processChallengeOwnershipPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) { return; } virtual void processChallengeOwnershipPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) { return; }
virtual bool recurseChildrenWithData() const { return true; } virtual bool recurseChildrenWithData() const { return true; }

View file

@ -68,8 +68,8 @@ bool OctreePacketData::append(const unsigned char* data, int length) {
_dirty = true; _dirty = true;
} }
const bool wantDebug = false; #ifdef WANT_DEBUG
if (wantDebug && !success) { if (!success) {
qCDebug(octree) << "OctreePacketData::append(const unsigned char* data, int length) FAILING...."; qCDebug(octree) << "OctreePacketData::append(const unsigned char* data, int length) FAILING....";
qCDebug(octree) << " length=" << length; qCDebug(octree) << " length=" << length;
qCDebug(octree) << " _bytesAvailable=" << _bytesAvailable; qCDebug(octree) << " _bytesAvailable=" << _bytesAvailable;
@ -77,6 +77,7 @@ bool OctreePacketData::append(const unsigned char* data, int length) {
qCDebug(octree) << " _targetSize=" << _targetSize; qCDebug(octree) << " _targetSize=" << _targetSize;
qCDebug(octree) << " _bytesReserved=" << _bytesReserved; qCDebug(octree) << " _bytesReserved=" << _bytesReserved;
} }
#endif
return success; return success;
} }
@ -647,6 +648,13 @@ void OctreePacketData::debugContent() {
printf("\n"); printf("\n");
} }
void OctreePacketData::debugBytes() {
qCDebug(octree) << " _bytesAvailable=" << _bytesAvailable;
qCDebug(octree) << " _bytesInUse=" << _bytesInUse;
qCDebug(octree) << " _targetSize=" << _targetSize;
qCDebug(octree) << " _bytesReserved=" << _bytesReserved;
}
int OctreePacketData::unpackDataFromBytes(const unsigned char* dataBytes, QString& result) { int OctreePacketData::unpackDataFromBytes(const unsigned char* dataBytes, QString& result) {
uint16_t length; uint16_t length;
memcpy(&length, dataBytes, sizeof(length)); memcpy(&length, dataBytes, sizeof(length));

View file

@ -240,6 +240,7 @@ public:
/// displays contents for debugging /// displays contents for debugging
void debugContent(); void debugContent();
void debugBytes();
static quint64 getCompressContentTime() { return _compressContentTime; } /// total time spent compressing content static quint64 getCompressContentTime() { return _compressContentTime; } /// total time spent compressing content
static quint64 getCompressContentCalls() { return _compressContentCalls; } /// total calls to compress content static quint64 getCompressContentCalls() { return _compressContentCalls; } /// total calls to compress content

View file

@ -118,7 +118,7 @@ public:
// for example: if we want the closest result, compare based on distance // for example: if we want the closest result, compare based on distance
// if we want all results, combine them // if we want all results, combine them
// must return a new pointer // must return a new pointer
virtual std::shared_ptr<PickResult> compareAndProcessNewResult(const std::shared_ptr<PickResult> newRes) = 0; virtual std::shared_ptr<PickResult> compareAndProcessNewResult(const std::shared_ptr<PickResult>& newRes) = 0;
// returns true if this result contains any valid results with distance < maxDistance // returns true if this result contains any valid results with distance < maxDistance
// can also filter out results with distance >= maxDistance // can also filter out results with distance >= maxDistance
@ -182,6 +182,10 @@ public:
void setIgnoreItems(const QVector<QUuid>& items); void setIgnoreItems(const QVector<QUuid>& items);
void setIncludeItems(const QVector<QUuid>& items); void setIncludeItems(const QVector<QUuid>& items);
virtual bool isLeftHand() const { return false; }
virtual bool isRightHand() const { return false; }
virtual bool isMouse() const { return false; }
private: private:
PickFilter _filter; PickFilter _filter;
const float _maxDistance; const float _maxDistance;
@ -198,7 +202,7 @@ class Pick : public PickQuery {
public: public:
Pick(const PickFilter& filter, const float maxDistance, const bool enabled) : PickQuery(filter, maxDistance, enabled) {} Pick(const PickFilter& filter, const float maxDistance, const bool enabled) : PickQuery(filter, maxDistance, enabled) {}
virtual const T getMathematicalPick() const = 0; virtual T getMathematicalPick() const = 0;
virtual PickResultPointer getDefaultResult(const QVariantMap& pickVariant) const = 0; virtual PickResultPointer getDefaultResult(const QVariantMap& pickVariant) const = 0;
virtual PickResultPointer getEntityIntersection(const T& pick) = 0; virtual PickResultPointer getEntityIntersection(const T& pick) = 0;
virtual PickResultPointer getOverlayIntersection(const T& pick) = 0; virtual PickResultPointer getOverlayIntersection(const T& pick) = 0;
@ -206,4 +210,13 @@ public:
virtual PickResultPointer getHUDIntersection(const T& pick) = 0; virtual PickResultPointer getHUDIntersection(const T& pick) = 0;
}; };
namespace std {
template <>
struct hash<PickQuery::PickType> {
size_t operator()(const PickQuery::PickType& a) const {
return a;
}
};
}
#endif // hifi_Pick_h #endif // hifi_Pick_h

View file

@ -37,7 +37,7 @@ template<typename T>
class PickCacheOptimizer { class PickCacheOptimizer {
public: public:
void update(QHash<unsigned int, std::shared_ptr<PickQuery>>& picks, bool shouldPickHUD); void update(std::unordered_map<unsigned int, std::shared_ptr<PickQuery>>& picks, bool shouldPickHUD);
protected: protected:
typedef std::unordered_map<T, std::unordered_map<PickCacheKey, PickResultPointer>> PickCache; typedef std::unordered_map<T, std::unordered_map<PickCacheKey, PickResultPointer>> PickCache;
@ -67,27 +67,26 @@ void PickCacheOptimizer<T>::cacheResult(const bool intersects, const PickResultP
} }
template<typename T> template<typename T>
void PickCacheOptimizer<T>::update(QHash<unsigned int, std::shared_ptr<PickQuery>>& picks, bool shouldPickHUD) { void PickCacheOptimizer<T>::update(std::unordered_map<unsigned int, std::shared_ptr<PickQuery>>& picks, bool shouldPickHUD) {
PickCache results; PickCache results;
for (const auto& uid : picks.keys()) { for (const auto& pickPair : picks) {
std::shared_ptr<Pick<T>> pick = std::static_pointer_cast<Pick<T>>(picks[uid]); std::shared_ptr<Pick<T>> pick = std::static_pointer_cast<Pick<T>>(pickPair.second);
if (!pick->isEnabled() || pick->getFilter().doesPickNothing() || pick->getMaxDistance() < 0.0f) {
continue;
}
T mathematicalPick = pick->getMathematicalPick(); T mathematicalPick = pick->getMathematicalPick();
PickResultPointer res = pick->getDefaultResult(mathematicalPick.toVariantMap());
if (!mathematicalPick) { if (!pick->isEnabled() || pick->getFilter().doesPickNothing() || pick->getMaxDistance() < 0.0f || !mathematicalPick) {
pick->setPickResult(res);
continue; continue;
} }
PickResultPointer res = pick->getDefaultResult(mathematicalPick.toVariantMap());
if (pick->getFilter().doesPickEntities()) { if (pick->getFilter().doesPickEntities()) {
PickCacheKey entityKey = { pick->getFilter().getEntityFlags(), pick->getIncludeItems(), pick->getIgnoreItems() }; PickCacheKey entityKey = { pick->getFilter().getEntityFlags(), pick->getIncludeItems(), pick->getIgnoreItems() };
if (!checkAndCompareCachedResults(mathematicalPick, results, res, entityKey)) { if (!checkAndCompareCachedResults(mathematicalPick, results, res, entityKey)) {
PickResultPointer entityRes = pick->getEntityIntersection(mathematicalPick); PickResultPointer entityRes = pick->getEntityIntersection(mathematicalPick);
cacheResult(entityRes->doesIntersect(), entityRes, entityKey, res, mathematicalPick, results, pick); if (entityRes) {
cacheResult(entityRes->doesIntersect(), entityRes, entityKey, res, mathematicalPick, results, pick);
}
} }
} }
@ -95,7 +94,9 @@ void PickCacheOptimizer<T>::update(QHash<unsigned int, std::shared_ptr<PickQuery
PickCacheKey overlayKey = { pick->getFilter().getOverlayFlags(), pick->getIncludeItems(), pick->getIgnoreItems() }; PickCacheKey overlayKey = { pick->getFilter().getOverlayFlags(), pick->getIncludeItems(), pick->getIgnoreItems() };
if (!checkAndCompareCachedResults(mathematicalPick, results, res, overlayKey)) { if (!checkAndCompareCachedResults(mathematicalPick, results, res, overlayKey)) {
PickResultPointer overlayRes = pick->getOverlayIntersection(mathematicalPick); PickResultPointer overlayRes = pick->getOverlayIntersection(mathematicalPick);
cacheResult(overlayRes->doesIntersect(), overlayRes, overlayKey, res, mathematicalPick, results, pick); if (overlayRes) {
cacheResult(overlayRes->doesIntersect(), overlayRes, overlayKey, res, mathematicalPick, results, pick);
}
} }
} }
@ -103,7 +104,9 @@ void PickCacheOptimizer<T>::update(QHash<unsigned int, std::shared_ptr<PickQuery
PickCacheKey avatarKey = { pick->getFilter().getAvatarFlags(), pick->getIncludeItems(), pick->getIgnoreItems() }; PickCacheKey avatarKey = { pick->getFilter().getAvatarFlags(), pick->getIncludeItems(), pick->getIgnoreItems() };
if (!checkAndCompareCachedResults(mathematicalPick, results, res, avatarKey)) { if (!checkAndCompareCachedResults(mathematicalPick, results, res, avatarKey)) {
PickResultPointer avatarRes = pick->getAvatarIntersection(mathematicalPick); PickResultPointer avatarRes = pick->getAvatarIntersection(mathematicalPick);
cacheResult(avatarRes->doesIntersect(), avatarRes, avatarKey, res, mathematicalPick, results, pick); if (avatarRes) {
cacheResult(avatarRes->doesIntersect(), avatarRes, avatarKey, res, mathematicalPick, results, pick);
}
} }
} }
@ -112,7 +115,9 @@ void PickCacheOptimizer<T>::update(QHash<unsigned int, std::shared_ptr<PickQuery
PickCacheKey hudKey = { pick->getFilter().getHUDFlags(), QVector<QUuid>(), QVector<QUuid>() }; PickCacheKey hudKey = { pick->getFilter().getHUDFlags(), QVector<QUuid>(), QVector<QUuid>() };
if (!checkAndCompareCachedResults(mathematicalPick, results, res, hudKey)) { if (!checkAndCompareCachedResults(mathematicalPick, results, res, hudKey)) {
PickResultPointer hudRes = pick->getHUDIntersection(mathematicalPick); PickResultPointer hudRes = pick->getHUDIntersection(mathematicalPick);
cacheResult(true, hudRes, hudKey, res, mathematicalPick, results, pick); if (hudRes) {
cacheResult(true, hudRes, hudKey, res, mathematicalPick, results, pick);
}
} }
} }

View file

@ -13,7 +13,7 @@ PickManager::PickManager() {
} }
unsigned int PickManager::addPick(PickQuery::PickType type, const std::shared_ptr<PickQuery> pick) { unsigned int PickManager::addPick(PickQuery::PickType type, const std::shared_ptr<PickQuery> pick) {
unsigned int id = 0; unsigned int id = INVALID_PICK_ID;
withWriteLock([&] { withWriteLock([&] {
// Don't let the pick IDs overflow // Don't let the pick IDs overflow
if (_nextPickID < UINT32_MAX) { if (_nextPickID < UINT32_MAX) {
@ -29,7 +29,7 @@ std::shared_ptr<PickQuery> PickManager::findPick(unsigned int uid) const {
return resultWithReadLock<std::shared_ptr<PickQuery>>([&] { return resultWithReadLock<std::shared_ptr<PickQuery>>([&] {
auto type = _typeMap.find(uid); auto type = _typeMap.find(uid);
if (type != _typeMap.end()) { if (type != _typeMap.end()) {
return _picks[type.value()][uid]; return _picks.find(type->second)->second.find(uid)->second;
} }
return std::shared_ptr<PickQuery>(); return std::shared_ptr<PickQuery>();
}); });
@ -39,18 +39,18 @@ void PickManager::removePick(unsigned int uid) {
withWriteLock([&] { withWriteLock([&] {
auto type = _typeMap.find(uid); auto type = _typeMap.find(uid);
if (type != _typeMap.end()) { if (type != _typeMap.end()) {
_picks[type.value()].remove(uid); _picks[type->second].erase(uid);
_typeMap.remove(uid); _typeMap.erase(uid);
} }
}); });
} }
QVariantMap PickManager::getPrevPickResult(unsigned int uid) const { PickResultPointer PickManager::getPrevPickResult(unsigned int uid) const {
auto pick = findPick(uid); auto pick = findPick(uid);
if (pick && pick->getPrevPickResult()) { if (pick) {
return pick->getPrevPickResult()->toVariantMap(); return pick->getPrevPickResult();
} }
return QVariantMap(); return PickResultPointer();
} }
void PickManager::enablePick(unsigned int uid) const { void PickManager::enablePick(unsigned int uid) const {
@ -89,11 +89,36 @@ void PickManager::setIncludeItems(unsigned int uid, const QVector<QUuid>& includ
} }
void PickManager::update() { void PickManager::update() {
QHash<PickQuery::PickType, QHash<unsigned int, std::shared_ptr<PickQuery>>> cachedPicks; std::unordered_map<PickQuery::PickType, std::unordered_map<unsigned int, std::shared_ptr<PickQuery>>> cachedPicks;
withReadLock([&] { withReadLock([&] {
cachedPicks = _picks; cachedPicks = _picks;
}); });
bool shouldPickHUD = _shouldPickHUDOperator(); bool shouldPickHUD = _shouldPickHUDOperator();
_rayPickCacheOptimizer.update(cachedPicks[PickQuery::Ray], shouldPickHUD); _rayPickCacheOptimizer.update(cachedPicks[PickQuery::Ray], shouldPickHUD);
_stylusPickCacheOptimizer.update(cachedPicks[PickQuery::Stylus], false);
}
bool PickManager::isLeftHand(unsigned int uid) {
auto pick = findPick(uid);
if (pick) {
return pick->isLeftHand();
}
return false;
}
bool PickManager::isRightHand(unsigned int uid) {
auto pick = findPick(uid);
if (pick) {
return pick->isRightHand();
}
return false;
}
bool PickManager::isMouse(unsigned int uid) {
auto pick = findPick(uid);
if (pick) {
return pick->isMouse();
}
return false;
} }

View file

@ -27,28 +27,38 @@ public:
void enablePick(unsigned int uid) const; void enablePick(unsigned int uid) const;
void disablePick(unsigned int uid) const; void disablePick(unsigned int uid) const;
QVariantMap getPrevPickResult(unsigned int uid) const; PickResultPointer getPrevPickResult(unsigned int uid) const;
template <typename T>
std::shared_ptr<T> getPrevPickResultTyped(unsigned int uid) const {
return std::static_pointer_cast<T>(getPrevPickResult(uid));
}
void setPrecisionPicking(unsigned int uid, bool precisionPicking) const; void setPrecisionPicking(unsigned int uid, bool precisionPicking) const;
void setIgnoreItems(unsigned int uid, const QVector<QUuid>& ignore) const; void setIgnoreItems(unsigned int uid, const QVector<QUuid>& ignore) const;
void setIncludeItems(unsigned int uid, const QVector<QUuid>& include) const; void setIncludeItems(unsigned int uid, const QVector<QUuid>& include) const;
bool isLeftHand(unsigned int uid);
bool isRightHand(unsigned int uid);
bool isMouse(unsigned int uid);
void setShouldPickHUDOperator(std::function<bool()> shouldPickHUDOperator) { _shouldPickHUDOperator = shouldPickHUDOperator; } void setShouldPickHUDOperator(std::function<bool()> shouldPickHUDOperator) { _shouldPickHUDOperator = shouldPickHUDOperator; }
void setCalculatePos2DFromHUDOperator(std::function<glm::vec2(const glm::vec3&)> calculatePos2DFromHUDOperator) { _calculatePos2DFromHUDOperator = calculatePos2DFromHUDOperator; } void setCalculatePos2DFromHUDOperator(std::function<glm::vec2(const glm::vec3&)> calculatePos2DFromHUDOperator) { _calculatePos2DFromHUDOperator = calculatePos2DFromHUDOperator; }
glm::vec2 calculatePos2DFromHUD(const glm::vec3& intersection) { return _calculatePos2DFromHUDOperator(intersection); } glm::vec2 calculatePos2DFromHUD(const glm::vec3& intersection) { return _calculatePos2DFromHUDOperator(intersection); }
static const unsigned int INVALID_PICK_ID { 0 };
protected: protected:
std::function<bool()> _shouldPickHUDOperator; std::function<bool()> _shouldPickHUDOperator;
std::function<glm::vec2(const glm::vec3&)> _calculatePos2DFromHUDOperator; std::function<glm::vec2(const glm::vec3&)> _calculatePos2DFromHUDOperator;
std::shared_ptr<PickQuery> findPick(unsigned int uid) const; std::shared_ptr<PickQuery> findPick(unsigned int uid) const;
QHash<PickQuery::PickType, QHash<unsigned int, std::shared_ptr<PickQuery>>> _picks; std::unordered_map<PickQuery::PickType, std::unordered_map<unsigned int, std::shared_ptr<PickQuery>>> _picks;
QHash<unsigned int, PickQuery::PickType> _typeMap; std::unordered_map<unsigned int, PickQuery::PickType> _typeMap;
// 0 = invalid unsigned int _nextPickID { INVALID_PICK_ID + 1 };
const unsigned int FIRST_PICK_ID { 1 };
unsigned int _nextPickID { FIRST_PICK_ID };
PickCacheOptimizer<PickRay> _rayPickCacheOptimizer; PickCacheOptimizer<PickRay> _rayPickCacheOptimizer;
PickCacheOptimizer<StylusTip> _stylusPickCacheOptimizer;
}; };
#endif // hifi_PickManager_h #endif // hifi_PickManager_h

View file

@ -30,7 +30,7 @@ void Pointer::disable() {
DependencyManager::get<PickManager>()->disablePick(_pickUID); DependencyManager::get<PickManager>()->disablePick(_pickUID);
} }
const QVariantMap Pointer::getPrevPickResult() { PickResultPointer Pointer::getPrevPickResult() {
return DependencyManager::get<PickManager>()->getPrevPickResult(_pickUID); return DependencyManager::get<PickManager>()->getPrevPickResult(_pickUID);
} }
@ -46,27 +46,70 @@ void Pointer::setIncludeItems(const QVector<QUuid>& includeItems) const {
DependencyManager::get<PickManager>()->setIncludeItems(_pickUID, includeItems); DependencyManager::get<PickManager>()->setIncludeItems(_pickUID, includeItems);
} }
void Pointer::update(unsigned int pointerID) { bool Pointer::isLeftHand() const {
return DependencyManager::get<PickManager>()->isLeftHand(_pickUID);
}
bool Pointer::isRightHand() const {
return DependencyManager::get<PickManager>()->isRightHand(_pickUID);
}
bool Pointer::isMouse() const {
return DependencyManager::get<PickManager>()->isMouse(_pickUID);
}
void Pointer::update(unsigned int pointerID, float deltaTime) {
// This only needs to be a read lock because update won't change any of the properties that can be modified from scripts // This only needs to be a read lock because update won't change any of the properties that can be modified from scripts
withReadLock([&] { withReadLock([&] {
QVariantMap pickResult = getPrevPickResult(); auto pickResult = getPrevPickResult();
updateVisuals(pickResult); updateVisuals(pickResult);
generatePointerEvents(pointerID, pickResult); generatePointerEvents(pointerID, pickResult);
}); });
} }
void Pointer::generatePointerEvents(unsigned int pointerID, const QVariantMap& pickResult) { void Pointer::generatePointerEvents(unsigned int pointerID, const PickResultPointer& pickResult) {
// TODO: avatars? // TODO: avatars/HUD?
auto pointerManager = DependencyManager::get<PointerManager>(); auto pointerManager = DependencyManager::get<PointerManager>();
// NOTE: After this loop: _prevButtons = buttons that were removed
// If switching to disabled or should stop triggering, release all buttons
Buttons buttons;
Buttons newButtons;
Buttons sameButtons;
const std::string PRIMARY_BUTTON = "Primary";
bool primaryPressed = false;
if (_enabled && shouldTrigger()) {
buttons = getPressedButtons();
primaryPressed = buttons.find(PRIMARY_BUTTON) != buttons.end();
for (const std::string& button : buttons) {
if (_prevButtons.find(button) == _prevButtons.end()) {
newButtons.insert(button);
} else {
sameButtons.insert(button);
_prevButtons.erase(button);
}
}
}
// Hover events // Hover events
bool doHover = shouldHover();
Pointer::PickedObject hoveredObject = getHoveredObject(pickResult); Pointer::PickedObject hoveredObject = getHoveredObject(pickResult);
PointerEvent hoveredEvent = buildPointerEvent(hoveredObject, pickResult); PointerEvent hoveredEvent = buildPointerEvent(hoveredObject, pickResult);
hoveredEvent.setType(PointerEvent::Move); hoveredEvent.setType(PointerEvent::Move);
hoveredEvent.setID(pointerID); hoveredEvent.setID(pointerID);
// TODO: set buttons on hover events bool releaseOnHoverLeave = !primaryPressed || (!_enabled && _prevEnabled) || (!doHover && _prevDoHover);
hoveredEvent.setButton(PointerEvent::NoButtons); hoveredEvent.setReleaseOnHoverLeave(releaseOnHoverLeave);
if (_enabled && _hover && shouldHover()) {
// if shouldHover && !_prevDoHover, only send hoverBegin
if (_enabled && _hover && doHover && !_prevDoHover) {
if (hoveredObject.type == ENTITY) {
emit pointerManager->hoverBeginEntity(hoveredObject.objectID, hoveredEvent);
} else if (hoveredObject.type == OVERLAY) {
emit pointerManager->hoverBeginOverlay(hoveredObject.objectID, hoveredEvent);
} else if (hoveredObject.type == HUD) {
emit pointerManager->hoverBeginHUD(hoveredEvent);
}
} else if (_enabled && _hover && doHover) {
if (hoveredObject.type == OVERLAY) { if (hoveredObject.type == OVERLAY) {
if (_prevHoveredObject.type == OVERLAY) { if (_prevHoveredObject.type == OVERLAY) {
if (hoveredObject.objectID == _prevHoveredObject.objectID) { if (hoveredObject.objectID == _prevHoveredObject.objectID) {
@ -74,6 +117,7 @@ void Pointer::generatePointerEvents(unsigned int pointerID, const QVariantMap& p
} else { } else {
PointerEvent prevHoveredEvent = buildPointerEvent(_prevHoveredObject, pickResult); PointerEvent prevHoveredEvent = buildPointerEvent(_prevHoveredObject, pickResult);
prevHoveredEvent.setID(pointerID); prevHoveredEvent.setID(pointerID);
prevHoveredEvent.setReleaseOnHoverLeave(releaseOnHoverLeave);
emit pointerManager->hoverEndOverlay(_prevHoveredObject.objectID, prevHoveredEvent); emit pointerManager->hoverEndOverlay(_prevHoveredObject.objectID, prevHoveredEvent);
emit pointerManager->hoverBeginOverlay(hoveredObject.objectID, hoveredEvent); emit pointerManager->hoverBeginOverlay(hoveredObject.objectID, hoveredEvent);
} }
@ -82,7 +126,7 @@ void Pointer::generatePointerEvents(unsigned int pointerID, const QVariantMap& p
if (_prevHoveredObject.type == ENTITY) { if (_prevHoveredObject.type == ENTITY) {
emit pointerManager->hoverEndEntity(_prevHoveredObject.objectID, hoveredEvent); emit pointerManager->hoverEndEntity(_prevHoveredObject.objectID, hoveredEvent);
} else if (_prevHoveredObject.type == HUD) { } else if (_prevHoveredObject.type == HUD) {
emit pointerManager->hoverEndHUD(_prevHoveredObject.objectID, hoveredEvent); emit pointerManager->hoverEndHUD(hoveredEvent);
} }
} }
} }
@ -95,6 +139,7 @@ void Pointer::generatePointerEvents(unsigned int pointerID, const QVariantMap& p
} else { } else {
PointerEvent prevHoveredEvent = buildPointerEvent(_prevHoveredObject, pickResult); PointerEvent prevHoveredEvent = buildPointerEvent(_prevHoveredObject, pickResult);
prevHoveredEvent.setID(pointerID); prevHoveredEvent.setID(pointerID);
prevHoveredEvent.setReleaseOnHoverLeave(releaseOnHoverLeave);
emit pointerManager->hoverEndEntity(_prevHoveredObject.objectID, prevHoveredEvent); emit pointerManager->hoverEndEntity(_prevHoveredObject.objectID, prevHoveredEvent);
emit pointerManager->hoverBeginEntity(hoveredObject.objectID, hoveredEvent); emit pointerManager->hoverBeginEntity(hoveredObject.objectID, hoveredEvent);
} }
@ -103,7 +148,7 @@ void Pointer::generatePointerEvents(unsigned int pointerID, const QVariantMap& p
if (_prevHoveredObject.type == OVERLAY) { if (_prevHoveredObject.type == OVERLAY) {
emit pointerManager->hoverEndOverlay(_prevHoveredObject.objectID, hoveredEvent); emit pointerManager->hoverEndOverlay(_prevHoveredObject.objectID, hoveredEvent);
} else if (_prevHoveredObject.type == HUD) { } else if (_prevHoveredObject.type == HUD) {
emit pointerManager->hoverEndHUD(_prevHoveredObject.objectID, hoveredEvent); emit pointerManager->hoverEndHUD(hoveredEvent);
} }
} }
} }
@ -111,9 +156,9 @@ void Pointer::generatePointerEvents(unsigned int pointerID, const QVariantMap& p
if (hoveredObject.type == HUD) { if (hoveredObject.type == HUD) {
if (_prevHoveredObject.type == HUD) { if (_prevHoveredObject.type == HUD) {
// There's only one HUD // There's only one HUD
emit pointerManager->hoverContinueHUD(hoveredObject.objectID, hoveredEvent); emit pointerManager->hoverContinueHUD(hoveredEvent);
} else { } else {
emit pointerManager->hoverBeginHUD(hoveredObject.objectID, hoveredEvent); emit pointerManager->hoverBeginHUD(hoveredEvent);
if (_prevHoveredObject.type == ENTITY) { if (_prevHoveredObject.type == ENTITY) {
emit pointerManager->hoverEndEntity(_prevHoveredObject.objectID, hoveredEvent); emit pointerManager->hoverEndEntity(_prevHoveredObject.objectID, hoveredEvent);
} else if (_prevHoveredObject.type == OVERLAY) { } else if (_prevHoveredObject.type == OVERLAY) {
@ -123,20 +168,15 @@ void Pointer::generatePointerEvents(unsigned int pointerID, const QVariantMap& p
} }
} }
// Trigger events if (_hover) {
Buttons buttons; // send hoverEnd events if we disable the pointer, disable hovering, or actually stop hovering over an object
Buttons newButtons; if ((!_enabled && _prevEnabled) || (!doHover && _prevDoHover) || (hoveredObject.type == NONE && _prevHoveredObject.type != NONE)) {
Buttons sameButtons; if (_prevHoveredObject.type == ENTITY) {
// NOTE: After this loop: _prevButtons = buttons that were removed emit pointerManager->hoverEndEntity(_prevHoveredObject.objectID, hoveredEvent);
// If switching to disabled or should stop triggering, release all buttons } else if (_prevHoveredObject.type == OVERLAY) {
if (_enabled && shouldTrigger()) { emit pointerManager->hoverEndOverlay(_prevHoveredObject.objectID, hoveredEvent);
buttons = getPressedButtons(); } else if (_prevHoveredObject.type == HUD) {
for (const std::string& button : buttons) { emit pointerManager->hoverEndHUD(hoveredEvent);
if (_prevButtons.find(button) == _prevButtons.end()) {
newButtons.insert(button);
} else {
sameButtons.insert(button);
_prevButtons.erase(button);
} }
} }
} }
@ -152,25 +192,23 @@ void Pointer::generatePointerEvents(unsigned int pointerID, const QVariantMap& p
} else if (hoveredObject.type == OVERLAY) { } else if (hoveredObject.type == OVERLAY) {
emit pointerManager->triggerBeginOverlay(hoveredObject.objectID, hoveredEvent); emit pointerManager->triggerBeginOverlay(hoveredObject.objectID, hoveredEvent);
} else if (hoveredObject.type == HUD) { } else if (hoveredObject.type == HUD) {
emit pointerManager->triggerBeginHUD(hoveredObject.objectID, hoveredEvent); emit pointerManager->triggerBeginHUD(hoveredEvent);
} }
_triggeredObjects[button] = hoveredObject; _triggeredObjects[button] = hoveredObject;
} }
// We still want other trigger events for Focus buttons, but they only set focus on triggerBegin
hoveredEvent.setShouldFocus(false);
// Trigger continue // Trigger continue
for (const std::string& button : sameButtons) { for (const std::string& button : sameButtons) {
PointerEvent triggeredEvent = buildPointerEvent(_triggeredObjects[button], pickResult); PointerEvent triggeredEvent = buildPointerEvent(_triggeredObjects[button], pickResult);
triggeredEvent.setID(pointerID); triggeredEvent.setID(pointerID);
triggeredEvent.setType(PointerEvent::Move); triggeredEvent.setType(PointerEvent::Move);
hoveredEvent.setButton(chooseButton(button)); triggeredEvent.setButton(chooseButton(button));
if (_triggeredObjects[button].type == ENTITY) { if (_triggeredObjects[button].type == ENTITY) {
emit pointerManager->triggerContinueEntity(_triggeredObjects[button].objectID, triggeredEvent); emit pointerManager->triggerContinueEntity(_triggeredObjects[button].objectID, triggeredEvent);
} else if (_triggeredObjects[button].type == OVERLAY) { } else if (_triggeredObjects[button].type == OVERLAY) {
emit pointerManager->triggerContinueOverlay(_triggeredObjects[button].objectID, triggeredEvent); emit pointerManager->triggerContinueOverlay(_triggeredObjects[button].objectID, triggeredEvent);
} else if (_triggeredObjects[button].type == HUD) { } else if (_triggeredObjects[button].type == HUD) {
emit pointerManager->triggerContinueHUD(_triggeredObjects[button].objectID, triggeredEvent); emit pointerManager->triggerContinueHUD(triggeredEvent);
} }
} }
@ -179,19 +217,21 @@ void Pointer::generatePointerEvents(unsigned int pointerID, const QVariantMap& p
PointerEvent triggeredEvent = buildPointerEvent(_triggeredObjects[button], pickResult); PointerEvent triggeredEvent = buildPointerEvent(_triggeredObjects[button], pickResult);
triggeredEvent.setID(pointerID); triggeredEvent.setID(pointerID);
triggeredEvent.setType(PointerEvent::Release); triggeredEvent.setType(PointerEvent::Release);
hoveredEvent.setButton(chooseButton(button)); triggeredEvent.setButton(chooseButton(button));
if (_triggeredObjects[button].type == ENTITY) { if (_triggeredObjects[button].type == ENTITY) {
emit pointerManager->triggerEndEntity(_triggeredObjects[button].objectID, triggeredEvent); emit pointerManager->triggerEndEntity(_triggeredObjects[button].objectID, triggeredEvent);
} else if (_triggeredObjects[button].type == OVERLAY) { } else if (_triggeredObjects[button].type == OVERLAY) {
emit pointerManager->triggerEndOverlay(_triggeredObjects[button].objectID, triggeredEvent); emit pointerManager->triggerEndOverlay(_triggeredObjects[button].objectID, triggeredEvent);
} else if (_triggeredObjects[button].type == HUD) { } else if (_triggeredObjects[button].type == HUD) {
emit pointerManager->triggerEndHUD(_triggeredObjects[button].objectID, triggeredEvent); emit pointerManager->triggerEndHUD(triggeredEvent);
} }
_triggeredObjects.erase(button); _triggeredObjects.erase(button);
} }
_prevHoveredObject = hoveredObject; _prevHoveredObject = hoveredObject;
_prevButtons = buttons; _prevButtons = buttons;
_prevEnabled = _enabled;
_prevDoHover = doHover;
} }
PointerEvent::Button Pointer::chooseButton(const std::string& button) { PointerEvent::Button Pointer::chooseButton(const std::string& button) {
@ -207,4 +247,4 @@ PointerEvent::Button Pointer::chooseButton(const std::string& button) {
} else { } else {
return PointerEvent::NoButtons; return PointerEvent::NoButtons;
} }
} }

View file

@ -16,6 +16,7 @@
#include <QVariant> #include <QVariant>
#include <shared/ReadWriteLockable.h> #include <shared/ReadWriteLockable.h>
#include "Pick.h"
#include <controllers/impl/Endpoint.h> #include <controllers/impl/Endpoint.h>
#include "PointerEvent.h" #include "PointerEvent.h"
@ -44,7 +45,7 @@ public:
virtual void enable(); virtual void enable();
virtual void disable(); virtual void disable();
virtual const QVariantMap getPrevPickResult(); virtual PickResultPointer getPrevPickResult();
virtual void setRenderState(const std::string& state) = 0; virtual void setRenderState(const std::string& state) = 0;
virtual void editRenderState(const std::string& state, const QVariant& startProps, const QVariant& pathProps, const QVariant& endProps) = 0; virtual void editRenderState(const std::string& state, const QVariant& startProps, const QVariant& pathProps, const QVariant& endProps) = 0;
@ -53,17 +54,20 @@ public:
virtual void setIgnoreItems(const QVector<QUuid>& ignoreItems) const; virtual void setIgnoreItems(const QVector<QUuid>& ignoreItems) const;
virtual void setIncludeItems(const QVector<QUuid>& includeItems) const; virtual void setIncludeItems(const QVector<QUuid>& includeItems) const;
bool isLeftHand() const;
bool isRightHand() const;
bool isMouse() const;
// Pointers can choose to implement these // Pointers can choose to implement these
virtual void setLength(float length) {} virtual void setLength(float length) {}
virtual void setLockEndUUID(const QUuid& objectID, bool isOverlay) {} virtual void setLockEndUUID(const QUuid& objectID, bool isOverlay) {}
void update(unsigned int pointerID); virtual void update(unsigned int pointerID, float deltaTime);
virtual void updateVisuals(const QVariantMap& pickResult) = 0; virtual void updateVisuals(const PickResultPointer& pickResult) {}
void generatePointerEvents(unsigned int pointerID, const QVariantMap& pickResult); void generatePointerEvents(unsigned int pointerID, const PickResultPointer& pickResult);
struct PickedObject { struct PickedObject {
PickedObject() {} PickedObject(const QUuid& objectID = QUuid(), IntersectionType type = IntersectionType::NONE) : objectID(objectID), type(type) {}
PickedObject(const QUuid& objectID, IntersectionType type) : objectID(objectID), type(type) {}
QUuid objectID; QUuid objectID;
IntersectionType type; IntersectionType type;
@ -78,9 +82,9 @@ protected:
bool _enabled; bool _enabled;
bool _hover; bool _hover;
virtual PointerEvent buildPointerEvent(const PickedObject& target, const QVariantMap& pickResult) const = 0; virtual PointerEvent buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult) const = 0;
virtual PickedObject getHoveredObject(const QVariantMap& pickResult) = 0; virtual PickedObject getHoveredObject(const PickResultPointer& pickResult) = 0;
virtual Buttons getPressedButtons() = 0; virtual Buttons getPressedButtons() = 0;
virtual bool shouldHover() = 0; virtual bool shouldHover() = 0;
@ -89,6 +93,8 @@ protected:
private: private:
PickedObject _prevHoveredObject; PickedObject _prevHoveredObject;
Buttons _prevButtons; Buttons _prevButtons;
bool _prevEnabled { false };
bool _prevDoHover { false };
std::unordered_map<std::string, PickedObject> _triggeredObjects; std::unordered_map<std::string, PickedObject> _triggeredObjects;
PointerEvent::Button chooseButton(const std::string& button); PointerEvent::Button chooseButton(const std::string& button);

View file

@ -8,19 +8,21 @@
#include "PointerManager.h" #include "PointerManager.h"
#include "PickManager.h"
std::shared_ptr<Pointer> PointerManager::find(unsigned int uid) const { std::shared_ptr<Pointer> PointerManager::find(unsigned int uid) const {
return resultWithReadLock<std::shared_ptr<Pointer>>([&] { return resultWithReadLock<std::shared_ptr<Pointer>>([&] {
auto itr = _pointers.find(uid); auto itr = _pointers.find(uid);
if (itr != _pointers.end()) { if (itr != _pointers.end()) {
return *itr; return itr->second;
} }
return std::shared_ptr<Pointer>(); return std::shared_ptr<Pointer>();
}); });
} }
unsigned int PointerManager::addPointer(std::shared_ptr<Pointer> pointer) { unsigned int PointerManager::addPointer(std::shared_ptr<Pointer> pointer) {
unsigned int result = 0; unsigned int result = PointerEvent::INVALID_POINTER_ID;
if (pointer->getRayUID() > 0) { if (pointer->getRayUID() != PickManager::INVALID_PICK_ID) {
withWriteLock([&] { withWriteLock([&] {
// Don't let the pointer IDs overflow // Don't let the pointer IDs overflow
if (_nextPointerID < UINT32_MAX) { if (_nextPointerID < UINT32_MAX) {
@ -34,7 +36,7 @@ unsigned int PointerManager::addPointer(std::shared_ptr<Pointer> pointer) {
void PointerManager::removePointer(unsigned int uid) { void PointerManager::removePointer(unsigned int uid) {
withWriteLock([&] { withWriteLock([&] {
_pointers.remove(uid); _pointers.erase(uid);
}); });
} }
@ -66,21 +68,22 @@ void PointerManager::editRenderState(unsigned int uid, const std::string& state,
} }
} }
const QVariantMap PointerManager::getPrevPickResult(unsigned int uid) const { PickResultPointer PointerManager::getPrevPickResult(unsigned int uid) const {
PickResultPointer result;
auto pointer = find(uid); auto pointer = find(uid);
if (pointer) { if (pointer) {
return pointer->getPrevPickResult(); result = pointer->getPrevPickResult();
} }
return QVariantMap(); return result;
} }
void PointerManager::update() { void PointerManager::update(float deltaTime) {
auto cachedPointers = resultWithReadLock<QHash<unsigned int, std::shared_ptr<Pointer>>>([&] { auto cachedPointers = resultWithReadLock<std::unordered_map<unsigned int, std::shared_ptr<Pointer>>>([&] {
return _pointers; return _pointers;
}); });
for (const auto& uid : cachedPointers.keys()) { for (const auto& pointerPair : cachedPointers) {
cachedPointers[uid]->update(uid); pointerPair.second->update(pointerPair.first, deltaTime);
} }
} }
@ -118,3 +121,27 @@ void PointerManager::setLockEndUUID(unsigned int uid, const QUuid& objectID, boo
pointer->setLockEndUUID(objectID, isOverlay); pointer->setLockEndUUID(objectID, isOverlay);
} }
} }
bool PointerManager::isLeftHand(unsigned int uid) {
auto pointer = find(uid);
if (pointer) {
return pointer->isLeftHand();
}
return false;
}
bool PointerManager::isRightHand(unsigned int uid) {
auto pointer = find(uid);
if (pointer) {
return pointer->isRightHand();
}
return false;
}
bool PointerManager::isMouse(unsigned int uid) {
auto pointer = find(uid);
if (pointer) {
return pointer->isMouse();
}
return false;
}

View file

@ -29,7 +29,7 @@ public:
void disablePointer(unsigned int uid) const; void disablePointer(unsigned int uid) const;
void setRenderState(unsigned int uid, const std::string& renderState) const; void setRenderState(unsigned int uid, const std::string& renderState) const;
void editRenderState(unsigned int uid, const std::string& state, const QVariant& startProps, const QVariant& pathProps, const QVariant& endProps) const; void editRenderState(unsigned int uid, const std::string& state, const QVariant& startProps, const QVariant& pathProps, const QVariant& endProps) const;
const QVariantMap getPrevPickResult(unsigned int uid) const; PickResultPointer getPrevPickResult(unsigned int uid) const;
void setPrecisionPicking(unsigned int uid, bool precisionPicking) const; void setPrecisionPicking(unsigned int uid, bool precisionPicking) const;
void setIgnoreItems(unsigned int uid, const QVector<QUuid>& ignoreEntities) const; void setIgnoreItems(unsigned int uid, const QVector<QUuid>& ignoreEntities) const;
@ -38,13 +38,18 @@ public:
void setLength(unsigned int uid, float length) const; void setLength(unsigned int uid, float length) const;
void setLockEndUUID(unsigned int uid, const QUuid& objectID, bool isOverlay) const; void setLockEndUUID(unsigned int uid, const QUuid& objectID, bool isOverlay) const;
void update(); void update(float deltaTime);
bool isLeftHand(unsigned int uid);
bool isRightHand(unsigned int uid);
bool isMouse(unsigned int uid);
static const unsigned int MOUSE_POINTER_ID { PointerEvent::INVALID_POINTER_ID + 1 };
private: private:
std::shared_ptr<Pointer> find(unsigned int uid) const; std::shared_ptr<Pointer> find(unsigned int uid) const;
QHash<unsigned int, std::shared_ptr<Pointer>> _pointers; std::unordered_map<unsigned int, std::shared_ptr<Pointer>> _pointers;
// 0 = invalid, 1 = reserved for system mouse unsigned int _nextPointerID { MOUSE_POINTER_ID + 1 };
unsigned int _nextPointerID { 2 };
signals: signals:
void triggerBeginOverlay(const QUuid& id, const PointerEvent& pointerEvent); void triggerBeginOverlay(const QUuid& id, const PointerEvent& pointerEvent);
@ -61,12 +66,12 @@ signals:
void hoverContinueEntity(const QUuid& id, const PointerEvent& pointerEvent); void hoverContinueEntity(const QUuid& id, const PointerEvent& pointerEvent);
void hoverEndEntity(const QUuid& id, const PointerEvent& pointerEvent); void hoverEndEntity(const QUuid& id, const PointerEvent& pointerEvent);
void triggerBeginHUD(const QUuid& id, const PointerEvent& pointerEvent); void triggerBeginHUD(const PointerEvent& pointerEvent);
void triggerContinueHUD(const QUuid& id, const PointerEvent& pointerEvent); void triggerContinueHUD(const PointerEvent& pointerEvent);
void triggerEndHUD(const QUuid& id, const PointerEvent& pointerEvent); void triggerEndHUD(const PointerEvent& pointerEvent);
void hoverBeginHUD(const QUuid& id, const PointerEvent& pointerEvent); void hoverBeginHUD(const PointerEvent& pointerEvent);
void hoverContinueHUD(const QUuid& id, const PointerEvent& pointerEvent); void hoverContinueHUD(const PointerEvent& pointerEvent);
void hoverEndHUD(const QUuid& id, const PointerEvent& pointerEvent); void hoverEndHUD(const PointerEvent& pointerEvent);
}; };
#endif // hifi_pointers_PointerManager_h #endif // hifi_pointers_PointerManager_h

View file

@ -262,6 +262,8 @@ public:
Q_INVOKABLE MeshProxyList getMeshes() const; Q_INVOKABLE MeshProxyList getMeshes() const;
void scaleToFit();
public slots: public slots:
void loadURLFinished(bool success); void loadURLFinished(bool success);
@ -320,7 +322,6 @@ protected:
virtual void initJointStates(); virtual void initJointStates();
void setScaleInternal(const glm::vec3& scale); void setScaleInternal(const glm::vec3& scale);
void scaleToFit();
void snapToRegistrationPoint(); void snapToRegistrationPoint();
void computeMeshPartLocalBounds(); void computeMeshPartLocalBounds();

View file

@ -69,6 +69,7 @@ void ScriptCache::getScriptContents(const QString& scriptOrURL, contentAvailable
#ifdef THREAD_DEBUGGING #ifdef THREAD_DEBUGGING
qCDebug(scriptengine) << "ScriptCache::getScriptContents() on thread [" << QThread::currentThread() << "] expected thread [" << thread() << "]"; qCDebug(scriptengine) << "ScriptCache::getScriptContents() on thread [" << QThread::currentThread() << "] expected thread [" << thread() << "]";
#endif #endif
forceDownload = true;
QUrl unnormalizedURL(scriptOrURL); QUrl unnormalizedURL(scriptOrURL);
QUrl url = DependencyManager::get<ResourceManager>()->normalizeURL(unnormalizedURL); QUrl url = DependencyManager::get<ResourceManager>()->normalizeURL(unnormalizedURL);

View file

@ -91,32 +91,6 @@ QVariantMap HifiConfigVariantMap::mergeCLParametersWithJSONConfig(const QStringL
return mergedMap; return mergedMap;
} }
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";
int masterConfigIndex = argumentList.indexOf(MASTER_CONFIG_FILE_OPTION);
if (masterConfigIndex != -1) {
QString masterConfigFilepath = argumentList[masterConfigIndex + 1];
loadMapFromJSONFile(_masterConfig, masterConfigFilepath);
}
// load the user config - that method replace loadMasterAndUserConfig after the 1.7 migration
loadConfig(argumentList);
mergeMasterAndUserConfigs();
}
void HifiConfigVariantMap::loadConfig(const QStringList& argumentList) { void HifiConfigVariantMap::loadConfig(const QStringList& argumentList) {
// load the user config // load the user config
const QString USER_CONFIG_FILE_OPTION = "--user-config"; const QString USER_CONFIG_FILE_OPTION = "--user-config";
@ -172,14 +146,6 @@ void HifiConfigVariantMap::loadConfig(const QStringList& argumentList) {
loadMapFromJSONFile(_userConfig, _userConfigFilename); loadMapFromJSONFile(_userConfig, _userConfigFilename);
} }
void HifiConfigVariantMap::mergeMasterAndUserConfigs() {
// 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::loadMapFromJSONFile(QVariantMap& existingMap, const QString& filename) { void HifiConfigVariantMap::loadMapFromJSONFile(QVariantMap& existingMap, const QString& filename) {
QFile configFile(filename); QFile configFile(filename);

View file

@ -21,26 +21,19 @@ class HifiConfigVariantMap {
public: public:
static QVariantMap mergeCLParametersWithJSONConfig(const QStringList& argumentList); static QVariantMap mergeCLParametersWithJSONConfig(const QStringList& argumentList);
HifiConfigVariantMap();
void loadMasterAndUserConfig(const QStringList& argumentList);
void loadConfig(const QStringList& argumentList); void loadConfig(const QStringList& argumentList);
const QVariant value(const QString& key) const { return _userConfig.value(key); } const QVariant value(const QString& key) const { return _userConfig.value(key); }
QVariant* valueForKeyPath(const QString& keyPath, bool shouldCreateIfMissing = false) QVariant* valueForKeyPath(const QString& keyPath, bool shouldCreateIfMissing = false)
{ return ::valueForKeyPath(_userConfig, keyPath, shouldCreateIfMissing); } { return ::valueForKeyPath(_userConfig, keyPath, shouldCreateIfMissing); }
QVariantMap& getMergedConfig() { return _mergedConfig; }
QVariantMap& getConfig() { return _userConfig; } QVariantMap& getConfig() { return _userConfig; }
void mergeMasterAndUserConfigs();
const QString& getUserConfigFilename() const { return _userConfigFilename; } const QString& getUserConfigFilename() const { return _userConfigFilename; }
private: private:
QString _userConfigFilename; QString _userConfigFilename;
QVariantMap _masterConfig;
QVariantMap _userConfig; QVariantMap _userConfig;
QVariantMap _mergedConfig;
void loadMapFromJSONFile(QVariantMap& existingMap, const QString& filename); void loadMapFromJSONFile(QVariantMap& existingMap, const QString& filename);
void addMissingValuesToExistingMap(QVariantMap& existingMap, const QVariantMap& newMap); void addMissingValuesToExistingMap(QVariantMap& existingMap, const QVariantMap& newMap);

View file

@ -20,8 +20,28 @@ static bool areFlagsSet(uint32_t flags, uint32_t mask) {
return (flags & mask) != 0; return (flags & mask) != 0;
} }
PointerEvent::PointerEvent() { PointerEvent::PointerEvent(EventType type, uint32_t id) :
; _type(type),
_id(id)
{
}
PointerEvent::PointerEvent(EventType type, uint32_t id, const glm::vec2& pos2D, Button button, uint32_t buttons, Qt::KeyboardModifiers keyboardModifiers) :
_type(type),
_id(id),
_pos2D(pos2D),
_button(button),
_buttons(buttons),
_keyboardModifiers(keyboardModifiers)
{
}
PointerEvent::PointerEvent(const glm::vec2& pos2D, const glm::vec3& pos3D, const glm::vec3& normal, const glm::vec3& direction) :
_pos2D(pos2D),
_pos3D(pos3D),
_normal(normal),
_direction(direction)
{
} }
PointerEvent::PointerEvent(EventType type, uint32_t id, PointerEvent::PointerEvent(EventType type, uint32_t id,
@ -38,7 +58,6 @@ PointerEvent::PointerEvent(EventType type, uint32_t id,
_buttons(buttons), _buttons(buttons),
_keyboardModifiers(keyboardModifiers) _keyboardModifiers(keyboardModifiers)
{ {
;
} }
void PointerEvent::setButton(Button button) { void PointerEvent::setButton(Button button) {

View file

@ -35,11 +35,14 @@ public:
NumEventTypes NumEventTypes
}; };
PointerEvent(); PointerEvent() {}
PointerEvent(EventType type, uint32_t id);
PointerEvent(EventType type, uint32_t id, const glm::vec2& pos2D, Button button, uint32_t buttons, Qt::KeyboardModifiers keyboardModifiers);
PointerEvent(const glm::vec2& pos2D, const glm::vec3& pos3D, const glm::vec3& normal, const glm::vec3& direction);
PointerEvent(EventType type, uint32_t id, PointerEvent(EventType type, uint32_t id,
const glm::vec2& pos2D, const glm::vec3& pos3D, const glm::vec2& pos2D, const glm::vec3& pos3D,
const glm::vec3& normal, const glm::vec3& direction, const glm::vec3& normal, const glm::vec3& direction,
Button button, uint32_t buttons = NoButtons, Qt::KeyboardModifiers keyboardModifiers = Qt::KeyboardModifier::NoModifier); Button button = NoButtons, uint32_t buttons = NoButtons, Qt::KeyboardModifiers keyboardModifiers = Qt::NoModifier);
static QScriptValue toScriptValue(QScriptEngine* engine, const PointerEvent& event); static QScriptValue toScriptValue(QScriptEngine* engine, const PointerEvent& event);
static void fromScriptValue(const QScriptValue& object, PointerEvent& event); static void fromScriptValue(const QScriptValue& object, PointerEvent& event);
@ -56,25 +59,31 @@ public:
uint32_t getButtons() const { return _buttons; } uint32_t getButtons() const { return _buttons; }
Qt::KeyboardModifiers getKeyboardModifiers() const { return _keyboardModifiers; } Qt::KeyboardModifiers getKeyboardModifiers() const { return _keyboardModifiers; }
bool shouldFocus() const { return _shouldFocus; } bool shouldFocus() const { return _shouldFocus; }
bool sendReleaseOnHoverLeave() const { return _releaseOnHoverLeave; }
void setID(uint32_t id) { _id = id; } void setID(uint32_t id) { _id = id; }
void setType(EventType type) { _type = type; } void setType(EventType type) { _type = type; }
void setButton(Button button); void setButton(Button button);
void setShouldFocus(bool focus) { _shouldFocus = focus; } void setShouldFocus(bool focus) { _shouldFocus = focus; }
void setReleaseOnHoverLeave(bool releaseOnHoverLeave) { _releaseOnHoverLeave = releaseOnHoverLeave; }
void setPos2D(const glm::vec2& pos2D) { _pos2D = pos2D; }
static const unsigned int INVALID_POINTER_ID { 0 };
private: private:
EventType _type; EventType _type;
uint32_t _id; // used to identify the pointer. (left vs right hand, for example) uint32_t _id { INVALID_POINTER_ID }; // used to identify the pointer. (left vs right hand, for example)
glm::vec2 _pos2D; // (in meters) projected onto the xy plane of entities dimension box, (0, 0) is upper right hand corner glm::vec2 _pos2D { glm::vec2(NAN) }; // (in meters) projected onto the xy plane of entities dimension box, (0, 0) is upper right hand corner
glm::vec3 _pos3D; // surface location in world coordinates (in meters) glm::vec3 _pos3D { glm::vec3(NAN) }; // surface location in world coordinates (in meters)
glm::vec3 _normal; // surface normal glm::vec3 _normal { glm::vec3(NAN) }; // surface normal
glm::vec3 _direction; // incoming direction of pointer ray. glm::vec3 _direction { glm::vec3(NAN) }; // incoming direction of pointer ray.
Button _button { NoButtons }; // button associated with this event, (if type is Press, this will be the button that is pressed) Button _button { NoButtons }; // button associated with this event, (if type is Press, this will be the button that is pressed)
uint32_t _buttons { NoButtons }; // the current state of all the buttons. uint32_t _buttons { NoButtons }; // the current state of all the buttons.
Qt::KeyboardModifiers _keyboardModifiers; // set of keys held when event was generated Qt::KeyboardModifiers _keyboardModifiers { Qt::KeyboardModifier::NoModifier }; // set of keys held when event was generated
bool _shouldFocus { true }; bool _shouldFocus { true };
bool _releaseOnHoverLeave { true };
}; };
QDebug& operator<<(QDebug& dbg, const PointerEvent& p); QDebug& operator<<(QDebug& dbg, const PointerEvent& p);

View file

@ -21,6 +21,7 @@
#include "AACube.h" #include "AACube.h"
#include "SharedUtil.h" #include "SharedUtil.h"
#include "shared/Bilateral.h"
class QColor; class QColor;
class QUrl; class QUrl;
@ -152,17 +153,73 @@ public:
return pickRay; return pickRay;
} }
}; };
struct StylusTip : public MathPick {
bilateral::Side side{ bilateral::Side::Invalid };
glm::vec3 position;
glm::quat orientation;
glm::vec3 velocity;
virtual operator bool() const override { return side != bilateral::Side::Invalid; }
QVariantMap toVariantMap() const override {
QVariantMap pickRay;
pickRay["position"] = vec3toVariant(position);
pickRay["orientation"] = quatToVariant(orientation);
pickRay["velocity"] = vec3toVariant(velocity);
return pickRay;
}
};
namespace std { namespace std {
inline void hash_combine(std::size_t& seed) { }
template <typename T, typename... Rest>
inline void hash_combine(std::size_t& seed, const T& v, Rest... rest) {
std::hash<T> hasher;
seed ^= hasher(v) + 0x9e3779b9 + (seed<<6) + (seed>>2);
hash_combine(seed, rest...);
}
template <> template <>
struct hash<glm::vec3> { struct hash<bilateral::Side> {
size_t operator()(const glm::vec3& a) const { size_t operator()(const bilateral::Side& a) const {
return ((hash<float>()(a.x) ^ (hash<float>()(a.y) << 1)) >> 1) ^ (hash<float>()(a.z) << 1); return std::hash<int>()((int)a);
} }
}; };
template <>
template <>
struct hash<glm::vec3> {
size_t operator()(const glm::vec3& a) const {
size_t result = 0;
hash_combine(result, a.x, a.y, a.z);
return result;
}
};
template <>
struct hash<glm::quat> {
size_t operator()(const glm::quat& a) const {
size_t result = 0;
hash_combine(result, a.x, a.y, a.z, a.w);
return result;
}
};
template <>
struct hash<PickRay> { struct hash<PickRay> {
size_t operator()(const PickRay& a) const { size_t operator()(const PickRay& a) const {
return (hash<glm::vec3>()(a.origin) ^ (hash<glm::vec3>()(a.direction) << 1)); size_t result = 0;
hash_combine(result, a.origin, a.direction);
return result;
}
};
template <>
struct hash<StylusTip> {
size_t operator()(const StylusTip& a) const {
size_t result = 0;
hash_combine(result, a.side, a.position, a.orientation, a.velocity);
return result;
} }
}; };
} }

View file

@ -23,9 +23,15 @@
#include "SharedLogging.h" #include "SharedLogging.h"
const QSettings::Format JSON_FORMAT = QSettings::registerFormat("json", readJSONFile, writeJSONFile);
QSettings::SettingsMap jsonDocumentToVariantMap(const QJsonDocument& document); QSettings::SettingsMap jsonDocumentToVariantMap(const QJsonDocument& document);
QJsonDocument variantMapToJsonDocument(const QSettings::SettingsMap& map); QJsonDocument variantMapToJsonDocument(const QSettings::SettingsMap& map);
QString settingsFilename() {
return QSettings().fileName();
}
bool readJSONFile(QIODevice& device, QSettings::SettingsMap& map) { bool readJSONFile(QIODevice& device, QSettings::SettingsMap& map) {
QJsonParseError jsonParseError; QJsonParseError jsonParseError;

View file

@ -14,12 +14,13 @@
#include <QSettings> #include <QSettings>
extern const QSettings::Format JSON_FORMAT;
QString settingsFilename();
bool readJSONFile(QIODevice& device, QSettings::SettingsMap& map); bool readJSONFile(QIODevice& device, QSettings::SettingsMap& map);
bool writeJSONFile(QIODevice& device, const QSettings::SettingsMap& map); bool writeJSONFile(QIODevice& device, const QSettings::SettingsMap& map);
static const auto JSON_FORMAT = QSettings::registerFormat("json", readJSONFile, writeJSONFile);
void loadOldINIFile(QSettings& settings); void loadOldINIFile(QSettings& settings);
#endif // hifi_SettingHelpers_h #endif // hifi_SettingHelpers_h

View file

@ -11,7 +11,8 @@
namespace bilateral { namespace bilateral {
enum class Side { enum class Side {
Left = 0, Left = 0,
Right = 1 Right = 1,
Invalid = -1
}; };
using Indices = Side; using Indices = Side;
@ -27,8 +28,10 @@ namespace bilateral {
return 0x01; return 0x01;
case Side::Right: case Side::Right:
return 0x02; return 0x02;
default:
break;
} }
return std::numeric_limits<uint8_t>::max(); return 0x00;
} }
inline uint8_t index(Side side) { inline uint8_t index(Side side) {
@ -37,10 +40,24 @@ namespace bilateral {
return 0; return 0;
case Side::Right: case Side::Right:
return 1; return 1;
default:
break;
} }
return std::numeric_limits<uint8_t>::max(); return std::numeric_limits<uint8_t>::max();
} }
inline Side side(int index) {
switch (index) {
case 0:
return Side::Left;
case 1:
return Side::Right;
default:
break;
}
return Side::Invalid;
}
template <typename F> template <typename F>
void for_each_side(F f) { void for_each_side(F f) {
f(Side::Left); f(Side::Left);

View file

@ -28,6 +28,7 @@
#include "ui/Logging.h" #include "ui/Logging.h"
#include <pointers/PointerManager.h>
// Needs to match the constants in resources/qml/Global.js // Needs to match the constants in resources/qml/Global.js
class OffscreenFlags : public QObject { class OffscreenFlags : public QObject {
@ -84,7 +85,31 @@ bool OffscreenUi::shouldSwallowShortcut(QEvent* event) {
return false; return false;
} }
static QTouchDevice _touchDevice;
OffscreenUi::OffscreenUi() { OffscreenUi::OffscreenUi() {
static std::once_flag once;
std::call_once(once, [&] {
_touchDevice.setCapabilities(QTouchDevice::Position);
_touchDevice.setType(QTouchDevice::TouchScreen);
_touchDevice.setName("OffscreenUiTouchDevice");
_touchDevice.setMaximumTouchPoints(4);
});
auto pointerManager = DependencyManager::get<PointerManager>();
connect(pointerManager.data(), &PointerManager::hoverBeginHUD, this, &OffscreenUi::handlePointerEvent);
connect(pointerManager.data(), &PointerManager::hoverContinueHUD, this, &OffscreenUi::handlePointerEvent);
connect(pointerManager.data(), &PointerManager::hoverEndHUD, this, &OffscreenUi::hoverEndEvent);
connect(pointerManager.data(), &PointerManager::triggerBeginHUD, this, &OffscreenUi::handlePointerEvent);
connect(pointerManager.data(), &PointerManager::triggerContinueHUD, this, &OffscreenUi::handlePointerEvent);
connect(pointerManager.data(), &PointerManager::triggerEndHUD, this, &OffscreenUi::handlePointerEvent);
}
void OffscreenUi::hoverEndEvent(const PointerEvent& event) {
OffscreenQmlSurface::hoverEndEvent(event, _touchDevice);
}
void OffscreenUi::handlePointerEvent(const PointerEvent& event) {
OffscreenQmlSurface::handlePointerEvent(event, _touchDevice);
} }
QObject* OffscreenUi::getFlags() { QObject* OffscreenUi::getFlags() {
@ -1072,6 +1097,23 @@ bool OffscreenUi::eventFilter(QObject* originalDestination, QEvent* event) {
// let the parent class do it's work // let the parent class do it's work
bool result = OffscreenQmlSurface::eventFilter(originalDestination, event); bool result = OffscreenQmlSurface::eventFilter(originalDestination, event);
switch (event->type()) {
// Fall through
case QEvent::MouseButtonDblClick:
case QEvent::MouseButtonPress:
case QEvent::MouseButtonRelease:
case QEvent::MouseMove: {
QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(event);
QPointF transformedPos = mapToVirtualScreen(mouseEvent->localPos());
PointerEvent pointerEvent(choosePointerEventType(mouseEvent->type()), PointerManager::MOUSE_POINTER_ID, glm::vec2(transformedPos.x(), transformedPos.y()),
PointerEvent::Button(mouseEvent->button()), mouseEvent->buttons(), mouseEvent->modifiers());
result = OffscreenQmlSurface::handlePointerEvent(pointerEvent, _touchDevice);
break;
}
default:
break;
}
// Check if this is a key press/release event that might need special attention // Check if this is a key press/release event that might need special attention
auto type = event->type(); auto type = event->type();

View file

@ -249,6 +249,10 @@ signals:
public slots: public slots:
void removeModalDialog(QObject* modal); void removeModalDialog(QObject* modal);
private slots:
void hoverEndEvent(const PointerEvent& event);
void handlePointerEvent(const PointerEvent& event);
private: private:
QString fileDialog(const QVariantMap& properties); QString fileDialog(const QVariantMap& properties);
ModalDialogListener *fileDialogAsync(const QVariantMap &properties); ModalDialogListener *fileDialogAsync(const QVariantMap &properties);

View file

@ -49,8 +49,6 @@
#include "Logging.h" #include "Logging.h"
#include <pointers/PointerManager.h>
Q_LOGGING_CATEGORY(trace_render_qml, "trace.render.qml") Q_LOGGING_CATEGORY(trace_render_qml, "trace.render.qml")
Q_LOGGING_CATEGORY(trace_render_qml_gl, "trace.render.qml.gl") Q_LOGGING_CATEGORY(trace_render_qml_gl, "trace.render.qml.gl")
Q_LOGGING_CATEGORY(offscreenFocus, "hifi.offscreen.focus") Q_LOGGING_CATEGORY(offscreenFocus, "hifi.offscreen.focus")
@ -531,13 +529,6 @@ bool OffscreenQmlSurface::allowNewFrame(uint8_t fps) {
} }
OffscreenQmlSurface::OffscreenQmlSurface() { OffscreenQmlSurface::OffscreenQmlSurface() {
auto pointerManager = DependencyManager::get<PointerManager>();
connect(pointerManager.data(), &PointerManager::hoverBeginHUD, this, &OffscreenQmlSurface::handlePointerEvent);
connect(pointerManager.data(), &PointerManager::hoverContinueHUD, this, &OffscreenQmlSurface::handlePointerEvent);
connect(pointerManager.data(), &PointerManager::hoverEndHUD, this, &OffscreenQmlSurface::handlePointerEvent);
connect(pointerManager.data(), &PointerManager::triggerBeginHUD, this, &OffscreenQmlSurface::handlePointerEvent);
connect(pointerManager.data(), &PointerManager::triggerContinueHUD, this, &OffscreenQmlSurface::handlePointerEvent);
connect(pointerManager.data(), &PointerManager::triggerEndHUD, this, &OffscreenQmlSurface::handlePointerEvent);
} }
OffscreenQmlSurface::~OffscreenQmlSurface() { OffscreenQmlSurface::~OffscreenQmlSurface() {
@ -864,7 +855,7 @@ QPointF OffscreenQmlSurface::mapWindowToUi(const QPointF& sourcePosition, QObjec
return QPointF(offscreenPosition.x, offscreenPosition.y); return QPointF(offscreenPosition.x, offscreenPosition.y);
} }
QPointF OffscreenQmlSurface::mapToVirtualScreen(const QPointF& originalPoint, QObject* originalWidget) { QPointF OffscreenQmlSurface::mapToVirtualScreen(const QPointF& originalPoint) {
return _mouseTranslator(originalPoint); return _mouseTranslator(originalPoint);
} }
@ -918,7 +909,7 @@ bool OffscreenQmlSurface::eventFilter(QObject* originalDestination, QEvent* even
case QEvent::Wheel: { case QEvent::Wheel: {
QWheelEvent* wheelEvent = static_cast<QWheelEvent*>(event); QWheelEvent* wheelEvent = static_cast<QWheelEvent*>(event);
QPointF transformedPos = mapToVirtualScreen(wheelEvent->pos(), originalDestination); QPointF transformedPos = mapToVirtualScreen(wheelEvent->pos());
QWheelEvent mappedEvent( QWheelEvent mappedEvent(
transformedPos, transformedPos,
wheelEvent->delta(), wheelEvent->buttons(), wheelEvent->delta(), wheelEvent->buttons(),
@ -929,69 +920,158 @@ bool OffscreenQmlSurface::eventFilter(QObject* originalDestination, QEvent* even
} }
break; break;
} }
default:
// Fall through
case QEvent::MouseButtonDblClick:
case QEvent::MouseButtonPress:
case QEvent::MouseButtonRelease:
case QEvent::MouseMove: {
QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(event);
QPointF transformedPos = mapToVirtualScreen(mouseEvent->localPos(), originalDestination);
QMouseEvent mappedEvent(mouseEvent->type(),
transformedPos,
mouseEvent->screenPos(), mouseEvent->button(),
mouseEvent->buttons(), mouseEvent->modifiers());
if (sendMouseEvent(mappedEvent)) {
return true;
}
break; break;
}
return false;
}
unsigned int OffscreenQmlSurface::deviceIdByTouchPoint(qreal x, qreal y) {
auto mapped = _rootItem->mapFromGlobal(QPoint(x, y));
for (auto pair : _activeTouchPoints) {
if (mapped.x() == (int)pair.second.pos().x() && mapped.y() == (int)pair.second.pos().y()) {
return pair.first;
}
}
return PointerEvent::INVALID_POINTER_ID;
}
PointerEvent::EventType OffscreenQmlSurface::choosePointerEventType(QEvent::Type type) {
switch (type) {
case QEvent::MouseButtonDblClick:
return PointerEvent::DoublePress;
case QEvent::MouseButtonPress:
return PointerEvent::Press;
case QEvent::MouseButtonRelease:
return PointerEvent::Release;
case QEvent::MouseMove:
return PointerEvent::Move;
default:
return PointerEvent::Move;
}
}
void OffscreenQmlSurface::hoverEndEvent(const PointerEvent& event, class QTouchDevice& device) {
if (!_paused && _quickWindow && _pressed && event.sendReleaseOnHoverLeave()) {
PointerEvent endEvent(PointerEvent::Release, event.getID(), event.getPos2D(), event.getPos3D(), event.getNormal(), event.getDirection(),
event.getButton(), event.getButtons(), event.getKeyboardModifiers());
handlePointerEvent(endEvent, device);
// QML onReleased is only triggered if a click has happened first. We need to send this "fake" mouse move event to properly trigger an onExited.
PointerEvent endMoveEvent(PointerEvent::Move, event.getID());
handlePointerEvent(endMoveEvent, device);
}
}
bool OffscreenQmlSurface::handlePointerEvent(const PointerEvent& event, class QTouchDevice& device) {
// Ignore mouse interaction if we're paused
if (_paused || !_quickWindow) {
return false;
}
if (event.getType() == PointerEvent::Press) {
_pressed = true;
} else if (event.getType() == PointerEvent::Release) {
_pressed = false;
}
QPointF windowPoint(event.getPos2D().x, event.getPos2D().y);
Qt::TouchPointState state = Qt::TouchPointStationary;
if (event.getType() == PointerEvent::Press && event.getButton() == PointerEvent::PrimaryButton) {
state = Qt::TouchPointPressed;
} else if (event.getType() == PointerEvent::Release) {
state = Qt::TouchPointReleased;
} else if (_activeTouchPoints.count(event.getID()) && windowPoint != _activeTouchPoints[event.getID()].pos()) {
state = Qt::TouchPointMoved;
}
QEvent::Type touchType = QEvent::TouchUpdate;
if (_activeTouchPoints.empty()) {
// If the first active touch point is being created, send a begin
touchType = QEvent::TouchBegin;
} if (state == Qt::TouchPointReleased && _activeTouchPoints.size() == 1 && _activeTouchPoints.count(event.getID())) {
// If the last active touch point is being released, send an end
touchType = QEvent::TouchEnd;
}
{
QTouchEvent::TouchPoint point;
point.setId(event.getID());
point.setState(state);
point.setPos(windowPoint);
point.setScreenPos(windowPoint);
_activeTouchPoints[event.getID()] = point;
}
QTouchEvent touchEvent(touchType, &device, event.getKeyboardModifiers());
{
QList<QTouchEvent::TouchPoint> touchPoints;
Qt::TouchPointStates touchPointStates;
for (const auto& entry : _activeTouchPoints) {
touchPointStates |= entry.second.state();
touchPoints.push_back(entry.second);
} }
default: touchEvent.setWindow(_quickWindow);
break; touchEvent.setDevice(&device);
touchEvent.setTarget(_rootItem);
touchEvent.setTouchPoints(touchPoints);
touchEvent.setTouchPointStates(touchPointStates);
} }
return false; // Send mouse events to the surface so that HTML dialog elements work with mouse press and hover.
} //
// In Qt 5.9 mouse events must be sent before touch events to make sure some QtQuick components will
void OffscreenQmlSurface::handlePointerEvent(const QUuid& id, const PointerEvent& event) { // receive mouse events
if (_paused) { Qt::MouseButton button = Qt::NoButton;
return; Qt::MouseButtons buttons = Qt::NoButton;
if (event.getButton() == PointerEvent::PrimaryButton) {
button = Qt::LeftButton;
}
if (event.getButtons() & PointerEvent::PrimaryButton) {
buttons |= Qt::LeftButton;
} }
QEvent::Type type = QEvent::Type::MouseMove; bool eventsAccepted = false;
switch (event.getType()) {
case PointerEvent::Press:
type = QEvent::Type::MouseButtonPress;
break;
case PointerEvent::DoublePress:
type = QEvent::Type::MouseButtonDblClick;
break;
case PointerEvent::Release:
type = QEvent::Type::MouseButtonRelease;
break;
case PointerEvent::Move:
type = QEvent::Type::MouseMove;
break;
default:
break;
}
QPointF screenPos(event.getPos2D().x, event.getPos2D().y);
QMouseEvent mouseEvent(type, screenPos, Qt::MouseButton(event.getButton()), Qt::MouseButtons(event.getButtons()), event.getKeyboardModifiers());
sendMouseEvent(mouseEvent);
}
bool OffscreenQmlSurface::sendMouseEvent(QMouseEvent& mouseEvent) { #if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0)
if (mouseEvent.type() == QEvent::MouseMove) { if (event.getType() == PointerEvent::Move) {
QMouseEvent mouseEvent(QEvent::MouseMove, windowPoint, windowPoint, windowPoint, button, buttons, event.getKeyboardModifiers());
// TODO - this line necessary for the QML Tooltop to work (which is not currently being used), but it causes interface to crash on launch on a fresh install // TODO - this line necessary for the QML Tooltop to work (which is not currently being used), but it causes interface to crash on launch on a fresh install
// need to investigate into why this crash is happening. // need to investigate into why this crash is happening.
//_qmlContext->setContextProperty("lastMousePosition", mouseEvent.localPos()); //_qmlContext->setContextProperty("lastMousePosition", windowPoint);
QCoreApplication::sendEvent(_quickWindow, &mouseEvent);
eventsAccepted &= mouseEvent.isAccepted();
} }
mouseEvent.ignore(); #endif
if (QCoreApplication::sendEvent(_quickWindow, &mouseEvent)) {
return mouseEvent.isAccepted(); if (touchType == QEvent::TouchBegin) {
_touchBeginAccepted = QCoreApplication::sendEvent(_quickWindow, &touchEvent);
} else if (_touchBeginAccepted) {
QCoreApplication::sendEvent(_quickWindow, &touchEvent);
} }
return false; eventsAccepted &= touchEvent.isAccepted();
// If this was a release event, remove the point from the active touch points
if (state == Qt::TouchPointReleased) {
_activeTouchPoints.erase(event.getID());
}
#if QT_VERSION < QT_VERSION_CHECK(5, 9, 0)
if (event.getType() == PointerEvent::Move) {
// TODO - this line necessary for the QML Tooltop to work (which is not currently being used), but it causes interface to crash on launch on a fresh install
// need to investigate into why this crash is happening.
//_qmlContext->setContextProperty("lastMousePosition", windowPoint);
QMouseEvent mouseEvent(QEvent::MouseMove, windowPoint, windowPoint, windowPoint, button, buttons, event.getKeyboardModifiers());
QCoreApplication::sendEvent(_webSurface->getWindow(), &mouseEvent);
eventsAccepted &= mouseEvent.isAccepted();
}
#endif
return eventsAccepted;
} }
void OffscreenQmlSurface::pause() { void OffscreenQmlSurface::pause() {

View file

@ -21,6 +21,9 @@
#include <GLMHelpers.h> #include <GLMHelpers.h>
#include <ThreadHelpers.h> #include <ThreadHelpers.h>
#include <QTouchEvent>
#include "PointerEvent.h"
class QWindow; class QWindow;
class QMyQuickRenderControl; class QMyQuickRenderControl;
class OffscreenGLCanvas; class OffscreenGLCanvas;
@ -30,8 +33,6 @@ class QQmlContext;
class QQmlComponent; class QQmlComponent;
class QQuickWindow; class QQuickWindow;
class QQuickItem; class QQuickItem;
class QMouseEvent;
class PointerEvent;
// GPU resources are typically buffered for one copy being used by the renderer, // GPU resources are typically buffered for one copy being used by the renderer,
// one copy in flight, and one copy being used by the receiver // one copy in flight, and one copy being used by the receiver
@ -81,7 +82,7 @@ public:
QObject* getEventHandler(); QObject* getEventHandler();
QQmlContext* getSurfaceContext(); QQmlContext* getSurfaceContext();
QPointF mapToVirtualScreen(const QPointF& originalPoint, QObject* originalWidget); QPointF mapToVirtualScreen(const QPointF& originalPoint);
bool eventFilter(QObject* originalDestination, QEvent* event) override; bool eventFilter(QObject* originalDestination, QEvent* event) override;
void setKeyboardRaised(QObject* object, bool raised, bool numeric = false, bool passwordField = false); void setKeyboardRaised(QObject* object, bool raised, bool numeric = false, bool passwordField = false);
@ -97,6 +98,10 @@ public:
static std::function<void(uint32_t, void*)> getDiscardLambda(); static std::function<void(uint32_t, void*)> getDiscardLambda();
static size_t getUsedTextureMemory(); static size_t getUsedTextureMemory();
PointerEvent::EventType choosePointerEventType(QEvent::Type type);
unsigned int deviceIdByTouchPoint(qreal x, qreal y);
signals: signals:
void focusObjectChanged(QObject* newFocus); void focusObjectChanged(QObject* newFocus);
void focusTextChanged(bool focusText); void focusTextChanged(bool focusText);
@ -137,7 +142,10 @@ private:
private slots: private slots:
void updateQuick(); void updateQuick();
void onFocusObjectChanged(QObject* newFocus); void onFocusObjectChanged(QObject* newFocus);
void handlePointerEvent(const QUuid& id, const PointerEvent& event);
public slots:
void hoverEndEvent(const PointerEvent& event, class QTouchDevice& device);
bool handlePointerEvent(const PointerEvent& event, class QTouchDevice& device);
private: private:
QQuickWindow* _quickWindow { nullptr }; QQuickWindow* _quickWindow { nullptr };
@ -165,7 +173,9 @@ private:
QQuickItem* _currentFocusItem { nullptr }; QQuickItem* _currentFocusItem { nullptr };
bool sendMouseEvent(QMouseEvent& mouseEvent); bool _pressed { false };
bool _touchBeginAccepted { false };
std::map<uint32_t, QTouchEvent::TouchPoint> _activeTouchPoints;
}; };
#endif #endif

View file

@ -16,256 +16,16 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js");
Script.include("/~/system/libraries/controllers.js"); Script.include("/~/system/libraries/controllers.js");
(function() { (function() {
var TouchEventUtils = Script.require("/~/system/libraries/touchEventUtils.js"); function TabletStylusInput(hand) {
// triggered when stylus presses a web overlay/entity
var HAPTIC_STYLUS_STRENGTH = 1.0;
var HAPTIC_STYLUS_DURATION = 20.0;
var WEB_DISPLAY_STYLUS_DISTANCE = 0.5;
var WEB_STYLUS_LENGTH = 0.2;
var WEB_TOUCH_Y_OFFSET = 0.105; // how far forward (or back with a negative number) to slide stylus in hand
function isNearStylusTarget(stylusTargets, edgeBorder, minNormalDistance, maxNormalDistance) {
for (var i = 0; i < stylusTargets.length; i++) {
var stylusTarget = stylusTargets[i];
// check to see if the projected stylusTip is within within the 2d border
var borderMin = {x: -edgeBorder, y: -edgeBorder};
var borderMax = {x: stylusTarget.dimensions.x + edgeBorder, y: stylusTarget.dimensions.y + edgeBorder};
if (stylusTarget.distance >= minNormalDistance && stylusTarget.distance <= maxNormalDistance &&
stylusTarget.position2D.x >= borderMin.x && stylusTarget.position2D.y >= borderMin.y &&
stylusTarget.position2D.x <= borderMax.x && stylusTarget.position2D.y <= borderMax.y) {
return true;
}
}
return false;
}
function calculateNearestStylusTarget(stylusTargets) {
var nearestStylusTarget;
for (var i = 0; i < stylusTargets.length; i++) {
var stylusTarget = stylusTargets[i];
if ((!nearestStylusTarget || stylusTarget.distance < nearestStylusTarget.distance) &&
stylusTarget.normalizedPosition.x >= 0 && stylusTarget.normalizedPosition.y >= 0 &&
stylusTarget.normalizedPosition.x <= 1 && stylusTarget.normalizedPosition.y <= 1) {
nearestStylusTarget = stylusTarget;
}
}
return nearestStylusTarget;
}
function getFingerWorldLocation(hand) {
var fingerJointName = (hand === RIGHT_HAND) ? "RightHandIndex4" : "LeftHandIndex4";
var fingerJointIndex = MyAvatar.getJointIndex(fingerJointName);
var fingerPosition = MyAvatar.getAbsoluteJointTranslationInObjectFrame(fingerJointIndex);
var fingerRotation = MyAvatar.getAbsoluteJointRotationInObjectFrame(fingerJointIndex);
var worldFingerRotation = Quat.multiply(MyAvatar.orientation, fingerRotation);
var worldFingerPosition = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, fingerPosition));
return {
position: worldFingerPosition,
orientation: worldFingerRotation,
rotation: worldFingerRotation,
valid: true
};
}
function distance2D(a, b) {
var dx = (a.x - b.x);
var dy = (a.y - b.y);
return Math.sqrt(dx * dx + dy * dy);
}
function TabletStylusInput(hand) {
this.hand = hand; this.hand = hand;
this.previousStylusTouchingTarget = false;
this.stylusTouchingTarget = false;
this.useFingerInsteadOfStylus = false;
this.fingerPointing = false;
// initialize stylus tip
var DEFAULT_STYLUS_TIP = {
position: {x: 0, y: 0, z: 0},
orientation: {x: 0, y: 0, z: 0, w: 0},
rotation: {x: 0, y: 0, z: 0, w: 0},
velocity: {x: 0, y: 0, z: 0},
valid: false
};
this.stylusTip = DEFAULT_STYLUS_TIP;
this.parameters = makeDispatcherModuleParameters( this.parameters = makeDispatcherModuleParameters(
100, 100,
this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"], this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"],
[], [],
100); 100);
this.getOtherHandController = function() { this.pointer = Pointers.createPointer(PickType.Stylus, { hand: this.hand });
return (this.hand === RIGHT_HAND) ? leftTabletStylusInput : rightTabletStylusInput;
};
this.handToController = function() {
return (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand;
};
this.updateFingerAsStylusSetting = function () {
var DEFAULT_USE_FINGER_AS_STYLUS = false;
var USE_FINGER_AS_STYLUS = Settings.getValue("preferAvatarFingerOverStylus");
if (USE_FINGER_AS_STYLUS === "") {
USE_FINGER_AS_STYLUS = DEFAULT_USE_FINGER_AS_STYLUS;
}
if (USE_FINGER_AS_STYLUS && MyAvatar.getJointIndex("LeftHandIndex4") !== -1) {
this.useFingerInsteadOfStylus = true;
} else {
this.useFingerInsteadOfStylus = false;
}
};
this.updateStylusTip = function() {
if (this.useFingerInsteadOfStylus) {
this.stylusTip = getFingerWorldLocation(this.hand);
} else {
this.stylusTip = getControllerWorldLocation(this.handToController(), true);
// translate tip forward according to constant.
var TIP_OFFSET = Vec3.multiply(MyAvatar.sensorToWorldScale, {x: 0, y: WEB_STYLUS_LENGTH - WEB_TOUCH_Y_OFFSET, z: 0});
this.stylusTip.position = Vec3.sum(this.stylusTip.position,
Vec3.multiplyQbyV(this.stylusTip.orientation, TIP_OFFSET));
}
// compute tip velocity from hand controller motion, it is more accurate than computing it from previous positions.
var pose = Controller.getPoseValue(this.handToController());
if (pose.valid) {
var worldControllerPos = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, pose.translation));
var worldControllerLinearVel = Vec3.multiplyQbyV(MyAvatar.orientation, pose.velocity);
var worldControllerAngularVel = Vec3.multiplyQbyV(MyAvatar.orientation, pose.angularVelocity);
var tipVelocity = Vec3.sum(worldControllerLinearVel, Vec3.cross(worldControllerAngularVel,
Vec3.subtract(this.stylusTip.position, worldControllerPos)));
this.stylusTip.velocity = tipVelocity;
} else {
this.stylusTip.velocity = {x: 0, y: 0, z: 0};
}
};
this.showStylus = function() {
if (this.stylus) {
var X_ROT_NEG_90 = { x: -0.70710678, y: 0, z: 0, w: 0.70710678 };
var modelOrientation = Quat.multiply(this.stylusTip.orientation, X_ROT_NEG_90);
var modelOrientationAngles = Quat.safeEulerAngles(modelOrientation);
var rotation = Overlays.getProperty(this.stylus, "rotation");
var rotationAngles = Quat.safeEulerAngles(rotation);
if(!Vec3.withinEpsilon(modelOrientationAngles, rotationAngles, 1)) {
var modelPositionOffset = Vec3.multiplyQbyV(modelOrientation, { x: 0, y: 0, z: MyAvatar.sensorToWorldScale * -WEB_STYLUS_LENGTH / 2 });
var updatedStylusProperties = {
position: Vec3.sum(this.stylusTip.position, modelPositionOffset),
rotation: modelOrientation,
dimensions: Vec3.multiply(MyAvatar.sensorToWorldScale, { x: 0.01, y: 0.01, z: WEB_STYLUS_LENGTH }),
};
Overlays.editOverlay(this.stylus, updatedStylusProperties);
}
return;
}
var X_ROT_NEG_90 = { x: -0.70710678, y: 0, z: 0, w: 0.70710678 };
var modelOrientation = Quat.multiply(this.stylusTip.orientation, X_ROT_NEG_90);
var modelPositionOffset = Vec3.multiplyQbyV(modelOrientation, { x: 0, y: 0, z: MyAvatar.sensorToWorldScale * -WEB_STYLUS_LENGTH / 2 });
var stylusProperties = {
name: "stylus",
url: Script.resourcesPath() + "meshes/tablet-stylus-fat.fbx",
loadPriority: 10.0,
position: Vec3.sum(this.stylusTip.position, modelPositionOffset),
rotation: modelOrientation,
dimensions: Vec3.multiply(MyAvatar.sensorToWorldScale, { x: 0.01, y: 0.01, z: WEB_STYLUS_LENGTH }),
solid: true,
visible: true,
ignoreRayIntersection: true,
drawInFront: false,
parentID: MyAvatar.SELF_ID,
parentJointIndex: MyAvatar.getJointIndex(this.hand === RIGHT_HAND ?
"_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND" :
"_CAMERA_RELATIVE_CONTROLLER_LEFTHAND")
};
this.stylus = Overlays.addOverlay("model", stylusProperties);
};
this.hideStylus = function() {
if (!this.stylus) {
return;
}
Overlays.deleteOverlay(this.stylus);
this.stylus = null;
};
this.stealTouchFocus = function(stylusTarget) {
// send hover events to target
// record the entity or overlay we are hovering over.
if ((stylusTarget.entityID === this.getOtherHandController().hoverEntity) ||
(stylusTarget.overlayID === this.getOtherHandController().hoverOverlay)) {
this.getOtherHandController().relinquishTouchFocus();
}
this.requestTouchFocus(stylusTarget);
};
this.requestTouchFocus = function(stylusTarget) {
// send hover events to target if we can.
// record the entity or overlay we are hovering over.
if (stylusTarget.entityID &&
stylusTarget.entityID !== this.hoverEntity &&
stylusTarget.entityID !== this.getOtherHandController().hoverEntity) {
this.hoverEntity = stylusTarget.entityID;
TouchEventUtils.sendHoverEnterEventToTouchTarget(this.hand, stylusTarget);
} else if (stylusTarget.overlayID &&
stylusTarget.overlayID !== this.hoverOverlay &&
stylusTarget.overlayID !== this.getOtherHandController().hoverOverlay) {
this.hoverOverlay = stylusTarget.overlayID;
TouchEventUtils.sendHoverEnterEventToTouchTarget(this.hand, stylusTarget);
}
};
this.hasTouchFocus = function(stylusTarget) {
return ((stylusTarget.entityID && stylusTarget.entityID === this.hoverEntity) ||
(stylusTarget.overlayID && stylusTarget.overlayID === this.hoverOverlay));
};
this.relinquishTouchFocus = function() {
// send hover leave event.
var pointerEvent = { type: "Move", id: this.hand + 1 };
if (this.hoverEntity) {
Entities.sendHoverLeaveEntity(this.hoverEntity, pointerEvent);
this.hoverEntity = null;
} else if (this.hoverOverlay) {
Overlays.sendMouseMoveOnOverlay(this.hoverOverlay, pointerEvent);
Overlays.sendHoverOverOverlay(this.hoverOverlay, pointerEvent);
Overlays.sendHoverLeaveOverlay(this.hoverOverlay, pointerEvent);
this.hoverOverlay = null;
}
};
this.pointFinger = function(value) {
var HIFI_POINT_INDEX_MESSAGE_CHANNEL = "Hifi-Point-Index";
if (this.fingerPointing !== value) {
var message;
if (this.hand === RIGHT_HAND) {
message = { pointRightIndex: value };
} else {
message = { pointLeftIndex: value };
}
Messages.sendMessage(HIFI_POINT_INDEX_MESSAGE_CHANNEL, JSON.stringify(message), true);
this.fingerPointing = value;
}
};
this.otherModuleNeedsToRun = function(controllerData) { this.otherModuleNeedsToRun = function(controllerData) {
var grabOverlayModuleName = this.hand === RIGHT_HAND ? "RightNearParentingGrabOverlay" : "LeftNearParentingGrabOverlay"; var grabOverlayModuleName = this.hand === RIGHT_HAND ? "RightNearParentingGrabOverlay" : "LeftNearParentingGrabOverlay";
@ -277,189 +37,6 @@ Script.include("/~/system/libraries/controllers.js");
return grabOverlayModuleReady.active || farGrabModuleReady.active; return grabOverlayModuleReady.active || farGrabModuleReady.active;
}; };
this.processStylus = function(controllerData) {
this.updateStylusTip();
if (!this.stylusTip.valid || this.overlayLaserActive(controllerData) || this.otherModuleNeedsToRun(controllerData)) {
this.pointFinger(false);
this.hideStylus();
this.stylusTouchingTarget = false;
this.relinquishTouchFocus();
return false;
}
if (this.useFingerInsteadOfStylus) {
this.hideStylus();
}
// build list of stylus targets, near the stylusTip
var stylusTargets = [];
var candidateEntities = controllerData.nearbyEntityProperties;
var i, props, stylusTarget;
for (i = 0; i < candidateEntities.length; i++) {
props = candidateEntities[i];
if (props && props.type === "Web") {
stylusTarget = TouchEventUtils.calculateTouchTargetFromEntity(this.stylusTip, candidateEntities[i]);
if (stylusTarget) {
stylusTargets.push(stylusTarget);
}
}
}
// add the tabletScreen, if it is valid
if (HMD.tabletScreenID && HMD.tabletScreenID !== Uuid.NULL &&
Overlays.getProperty(HMD.tabletScreenID, "visible")) {
stylusTarget = TouchEventUtils.calculateTouchTargetFromOverlay(this.stylusTip, HMD.tabletScreenID);
if (stylusTarget) {
stylusTargets.push(stylusTarget);
}
}
// add the tablet home button.
if (HMD.homeButtonID && HMD.homeButtonID !== Uuid.NULL &&
Overlays.getProperty(HMD.homeButtonID, "visible")) {
stylusTarget = TouchEventUtils.calculateTouchTargetFromOverlay(this.stylusTip, HMD.homeButtonID);
if (stylusTarget) {
stylusTargets.push(stylusTarget);
}
}
var TABLET_MIN_HOVER_DISTANCE = 0.01;
var TABLET_MAX_HOVER_DISTANCE = 0.1;
var TABLET_MIN_TOUCH_DISTANCE = -0.05;
var TABLET_MAX_TOUCH_DISTANCE = TABLET_MIN_HOVER_DISTANCE;
var EDGE_BORDER = 0.075;
var hysteresisOffset = 0.0;
if (this.isNearStylusTarget) {
hysteresisOffset = 0.05;
}
var sensorScaleFactor = MyAvatar.sensorToWorldScale;
this.isNearStylusTarget = isNearStylusTarget(stylusTargets,
(EDGE_BORDER + hysteresisOffset) * sensorScaleFactor,
(TABLET_MIN_TOUCH_DISTANCE - hysteresisOffset) * sensorScaleFactor,
(WEB_DISPLAY_STYLUS_DISTANCE + hysteresisOffset) * sensorScaleFactor);
if (this.isNearStylusTarget) {
if (!this.useFingerInsteadOfStylus) {
this.showStylus();
} else {
this.pointFinger(true);
}
} else {
this.hideStylus();
this.pointFinger(false);
}
var nearestStylusTarget = calculateNearestStylusTarget(stylusTargets);
var SCALED_TABLET_MIN_TOUCH_DISTANCE = TABLET_MIN_TOUCH_DISTANCE * sensorScaleFactor;
var SCALED_TABLET_MAX_TOUCH_DISTANCE = TABLET_MAX_TOUCH_DISTANCE * sensorScaleFactor;
var SCALED_TABLET_MAX_HOVER_DISTANCE = TABLET_MAX_HOVER_DISTANCE * sensorScaleFactor;
if (nearestStylusTarget && nearestStylusTarget.distance > SCALED_TABLET_MIN_TOUCH_DISTANCE &&
nearestStylusTarget.distance < SCALED_TABLET_MAX_HOVER_DISTANCE && !this.getOtherHandController().stylusTouchingTarget) {
this.requestTouchFocus(nearestStylusTarget);
if (!TouchEventUtils.touchTargetHasKeyboardFocus(nearestStylusTarget)) {
TouchEventUtils.setKeyboardFocusOnTouchTarget(nearestStylusTarget);
}
if (this.hasTouchFocus(nearestStylusTarget) && !this.stylusTouchingTarget) {
TouchEventUtils.sendHoverOverEventToTouchTarget(this.hand, nearestStylusTarget);
}
// filter out presses when tip is moving away from tablet.
// ensure that stylus is within bounding box by checking normalizedPosition
if (nearestStylusTarget.valid && nearestStylusTarget.distance > SCALED_TABLET_MIN_TOUCH_DISTANCE &&
nearestStylusTarget.distance < SCALED_TABLET_MAX_TOUCH_DISTANCE &&
Vec3.dot(this.stylusTip.velocity, nearestStylusTarget.normal) < 0 &&
nearestStylusTarget.normalizedPosition.x >= 0 && nearestStylusTarget.normalizedPosition.x <= 1 &&
nearestStylusTarget.normalizedPosition.y >= 0 && nearestStylusTarget.normalizedPosition.y <= 1) {
this.stylusTarget = nearestStylusTarget;
this.stylusTouchingTarget = true;
}
} else {
this.relinquishTouchFocus();
}
this.homeButtonTouched = false;
if (this.isNearStylusTarget) {
return true;
} else {
this.pointFinger(false);
this.hideStylus();
return false;
}
};
this.stylusTouchingEnter = function () {
this.stealTouchFocus(this.stylusTarget);
TouchEventUtils.sendTouchStartEventToTouchTarget(this.hand, this.stylusTarget);
Controller.triggerHapticPulse(HAPTIC_STYLUS_STRENGTH, HAPTIC_STYLUS_DURATION, this.hand);
this.touchingEnterTimer = 0;
this.touchingEnterStylusTarget = this.stylusTarget;
this.deadspotExpired = false;
var TOUCH_PRESS_TO_MOVE_DEADSPOT = 0.0481;
this.deadspotRadius = TOUCH_PRESS_TO_MOVE_DEADSPOT;
};
this.stylusTouchingExit = function () {
if (this.stylusTarget === undefined) {
return;
}
// special case to handle home button.
if (this.stylusTarget.overlayID === HMD.homeButtonID) {
Messages.sendLocalMessage("home", this.stylusTarget.overlayID);
}
// send press event
if (this.deadspotExpired) {
TouchEventUtils.sendTouchEndEventToTouchTarget(this.hand, this.stylusTarget);
} else {
TouchEventUtils.sendTouchEndEventToTouchTarget(this.hand, this.touchingEnterStylusTarget);
}
};
this.stylusTouching = function (controllerData, dt) {
this.touchingEnterTimer += dt;
if (this.stylusTarget.entityID) {
this.stylusTarget = TouchEventUtils.calculateTouchTargetFromEntity(this.stylusTip, this.stylusTarget.entityProps);
} else if (this.stylusTarget.overlayID) {
this.stylusTarget = TouchEventUtils.calculateTouchTargetFromOverlay(this.stylusTip, this.stylusTarget.overlayID);
}
var TABLET_MIN_TOUCH_DISTANCE = -0.1;
var TABLET_MAX_TOUCH_DISTANCE = 0.01;
if (this.stylusTarget) {
if (this.stylusTarget.distance > TABLET_MIN_TOUCH_DISTANCE &&
this.stylusTarget.distance < TABLET_MAX_TOUCH_DISTANCE) {
var POINTER_PRESS_TO_MOVE_DELAY = 0.33; // seconds
if (this.deadspotExpired || this.touchingEnterTimer > POINTER_PRESS_TO_MOVE_DELAY ||
distance2D(this.stylusTarget.position2D,
this.touchingEnterStylusTarget.position2D) > this.deadspotRadius) {
TouchEventUtils.sendTouchMoveEventToTouchTarget(this.hand, this.stylusTarget);
this.deadspotExpired = true;
}
} else {
this.stylusTouchingTarget = false;
}
} else {
this.stylusTouchingTarget = false;
}
};
this.overlayLaserActive = function(controllerData) { this.overlayLaserActive = function(controllerData) {
var rightOverlayLaserModule = getEnabledModuleByName("RightOverlayLaserInput"); var rightOverlayLaserModule = getEnabledModuleByName("RightOverlayLaserInput");
var leftOverlayLaserModule = getEnabledModuleByName("LeftOverlayLaserInput"); var leftOverlayLaserModule = getEnabledModuleByName("LeftOverlayLaserInput");
@ -469,7 +46,7 @@ Script.include("/~/system/libraries/controllers.js");
}; };
this.isReady = function (controllerData) { this.isReady = function (controllerData) {
if (this.processStylus(controllerData)) { if (!this.overlayLaserActive(controllerData) && !this.otherModuleNeedsToRun(controllerData)) {
return makeRunningValues(true, [], []); return makeRunningValues(true, [], []);
} else { } else {
return makeRunningValues(false, [], []); return makeRunningValues(false, [], []);
@ -477,28 +54,11 @@ Script.include("/~/system/libraries/controllers.js");
}; };
this.run = function (controllerData, deltaTime) { this.run = function (controllerData, deltaTime) {
this.updateFingerAsStylusSetting(); return this.isReady(controllerData);
if (!this.previousStylusTouchingTarget && this.stylusTouchingTarget) {
this.stylusTouchingEnter();
}
if (this.previousStylusTouchingTarget && !this.stylusTouchingTarget) {
this.stylusTouchingExit();
}
this.previousStylusTouchingTarget = this.stylusTouchingTarget;
if (this.stylusTouchingTarget) {
this.stylusTouching(controllerData, deltaTime);
}
if (this.processStylus(controllerData)) {
return makeRunningValues(true, [], []);
} else {
return makeRunningValues(false, [], []);
}
}; };
this.cleanup = function () { this.cleanup = function () {
this.hideStylus(); Pointers.createPointer(this.pointer);
}; };
} }

View file

@ -290,11 +290,17 @@
} }
}); });
// change pricing to GET on button hover // change pricing to GET/BUY on button hover
$('body').on('mouseenter', '#price-or-edit .price', function () { $('body').on('mouseenter', '#price-or-edit .price', function () {
var $this = $(this); var $this = $(this);
$this.data('initialHtml', $this.html()); $this.data('initialHtml', $this.html());
$this.text('GET');
var cost = $(this).parent().siblings().text();
if (parseInt(cost) > 0) {
$this.text('BUY');
} else {
$this.text('GET');
}
}); });
$('body').on('mouseleave', '#price-or-edit .price', function () { $('body').on('mouseleave', '#price-or-edit .price', function () {

View file

@ -74,7 +74,7 @@ void OctreeTests::propertyFlagsTests() {
EntityPropertyFlags props; EntityPropertyFlags props;
props.setHasProperty(PROP_VISIBLE); props.setHasProperty(PROP_VISIBLE);
props.setHasProperty(PROP_POSITION); props.setHasProperty(PROP_POSITION);
props.setHasProperty(PROP_RADIUS); props.setHasProperty(PROP_DIMENSIONS);
props.setHasProperty(PROP_MODEL_URL); props.setHasProperty(PROP_MODEL_URL);
props.setHasProperty(PROP_COMPOUND_SHAPE_URL); props.setHasProperty(PROP_COMPOUND_SHAPE_URL);
props.setHasProperty(PROP_ROTATION); props.setHasProperty(PROP_ROTATION);