mirror of
https://github.com/overte-org/overte.git
synced 2025-04-20 03:44:02 +02:00
Merge branch 'master' of https://github.com/highfidelity/hifi into orange
This commit is contained in:
commit
04d3f690dc
42 changed files with 1686 additions and 941 deletions
|
@ -1,7 +1,7 @@
|
|||
[
|
||||
{
|
||||
"name": "metaverse",
|
||||
"label": "Metaverse Registration",
|
||||
"label": "Metaverse / Networking",
|
||||
"settings": [
|
||||
{
|
||||
"name": "access_token",
|
||||
|
@ -44,6 +44,29 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Paths",
|
||||
"settings": [
|
||||
{
|
||||
"name": "paths",
|
||||
"label": "",
|
||||
"help": "Clients can enter a path to reach an exact viewpoint in your domain.<br/>Add rows to the table below to map a path to a viewpoint.<br/>The index path ( / ) is where clients will enter if they do not enter an explicit path.",
|
||||
"type": "table",
|
||||
"key": {
|
||||
"name": "path",
|
||||
"label": "Path",
|
||||
"placeholder": "/"
|
||||
},
|
||||
"columns": [
|
||||
{
|
||||
"name": "viewpoint",
|
||||
"label": "Viewpoint",
|
||||
"placeholder": "/512,512,512"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "security",
|
||||
"label": "Security",
|
||||
|
|
|
@ -21,34 +21,34 @@ var Settings = {
|
|||
};
|
||||
|
||||
var viewHelpers = {
|
||||
getFormGroup: function(groupName, setting, values, isAdvanced, isLocked) {
|
||||
setting_name = groupName + "." + setting.name
|
||||
|
||||
form_group = "<div class='form-group " + (isAdvanced ? Settings.ADVANCED_CLASS : "") + "'>"
|
||||
|
||||
if (_.has(values, groupName) && _.has(values[groupName], setting.name)) {
|
||||
setting_value = values[groupName][setting.name]
|
||||
} else if (_.has(setting, 'default')) {
|
||||
setting_value = setting.default
|
||||
} else {
|
||||
setting_value = ""
|
||||
getFormGroup: function(keypath, setting, values, isAdvanced, isLocked) {
|
||||
form_group = "<div class='form-group " + (isAdvanced ? Settings.ADVANCED_CLASS : "") + "'>";
|
||||
|
||||
setting_value = _(values).valueForKeyPath(keypath);
|
||||
|
||||
if (!setting_value) {
|
||||
if (_.has(setting, 'default')) {
|
||||
setting_value = setting.default;
|
||||
} else {
|
||||
setting_value = "";
|
||||
}
|
||||
}
|
||||
|
||||
label_class = 'control-label'
|
||||
|
||||
label_class = 'control-label';
|
||||
if (isLocked) {
|
||||
label_class += ' locked'
|
||||
label_class += ' locked';
|
||||
}
|
||||
|
||||
|
||||
common_attrs = " class='" + (setting.type !== 'checkbox' ? 'form-control' : '')
|
||||
+ " " + Settings.TRIGGER_CHANGE_CLASS + "' data-short-name='" + setting.name + "' name='" + setting_name + "' "
|
||||
+ "id='" + setting_name + "'"
|
||||
|
||||
+ " " + Settings.TRIGGER_CHANGE_CLASS + "' data-short-name='" + setting.name + "' name='" + keypath + "' "
|
||||
+ "id='" + keypath + "'";
|
||||
|
||||
if (setting.type === 'checkbox') {
|
||||
if (setting.label) {
|
||||
form_group += "<label class='" + label_class + "'>" + setting.label + "</label>"
|
||||
}
|
||||
form_group += "<div class='checkbox" + (isLocked ? " disabled" : "") + "'>"
|
||||
form_group += "<label for='" + setting_name + "'>"
|
||||
form_group += "<label for='" + keypath + "'>"
|
||||
form_group += "<input type='checkbox'" + common_attrs + (setting_value ? "checked" : "") + (isLocked ? " disabled" : "") + "/>"
|
||||
form_group += " " + setting.help + "</label>";
|
||||
form_group += "</div>"
|
||||
|
@ -56,52 +56,52 @@ var viewHelpers = {
|
|||
input_type = _.has(setting, 'type') ? setting.type : "text"
|
||||
|
||||
if (setting.label) {
|
||||
form_group += "<label for='" + setting_name + "' class='" + label_class + "'>" + setting.label + "</label>";
|
||||
form_group += "<label for='" + keypath + "' class='" + label_class + "'>" + setting.label + "</label>";
|
||||
}
|
||||
|
||||
|
||||
if (input_type === 'table') {
|
||||
form_group += makeTable(setting, setting_name, setting_value, isLocked)
|
||||
form_group += makeTable(setting, keypath, setting_value, isLocked)
|
||||
} else {
|
||||
if (input_type === 'select') {
|
||||
form_group += "<select class='form-control' data-hidden-input='" + setting_name + "'>'"
|
||||
|
||||
form_group += "<select class='form-control' data-hidden-input='" + keypath + "'>'"
|
||||
|
||||
_.each(setting.options, function(option) {
|
||||
form_group += "<option value='" + option.value + "'" +
|
||||
form_group += "<option value='" + option.value + "'" +
|
||||
(option.value == setting_value ? 'selected' : '') + ">" + option.label + "</option>"
|
||||
})
|
||||
|
||||
|
||||
form_group += "</select>"
|
||||
|
||||
|
||||
form_group += "<input type='hidden'" + common_attrs + "value='" + setting_value + "'>"
|
||||
} else {
|
||||
|
||||
|
||||
if (input_type == 'integer') {
|
||||
input_type = "text"
|
||||
}
|
||||
|
||||
|
||||
form_group += "<input type='" + input_type + "'" + common_attrs +
|
||||
"placeholder='" + (_.has(setting, 'placeholder') ? setting.placeholder : "") +
|
||||
"placeholder='" + (_.has(setting, 'placeholder') ? setting.placeholder : "") +
|
||||
"' value='" + setting_value + "'" + (isLocked ? " disabled" : "") + "/>"
|
||||
}
|
||||
|
||||
|
||||
form_group += "<span class='help-block'>" + setting.help + "</span>"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
form_group += "</div>"
|
||||
return form_group
|
||||
}
|
||||
}
|
||||
|
||||
$(document).ready(function(){
|
||||
$(document).ready(function(){
|
||||
/*
|
||||
* Clamped-width.
|
||||
* Clamped-width.
|
||||
* Usage:
|
||||
* <div data-clampedwidth=".myParent">This long content will force clamped width</div>
|
||||
*
|
||||
* Author: LV
|
||||
*/
|
||||
|
||||
|
||||
$('[data-clampedwidth]').each(function () {
|
||||
var elem = $(this);
|
||||
var parentPanel = elem.data('clampedwidth');
|
||||
|
@ -113,52 +113,52 @@ $(document).ready(function(){
|
|||
resizeFn();
|
||||
$(window).resize(resizeFn);
|
||||
})
|
||||
|
||||
|
||||
$('#settings-form').on('click', '.' + Settings.ADD_ROW_BUTTON_CLASS, function(){
|
||||
addTableRow(this);
|
||||
})
|
||||
|
||||
|
||||
$('#settings-form').on('click', '.' + Settings.DEL_ROW_BUTTON_CLASS, function(){
|
||||
deleteTableRow(this);
|
||||
})
|
||||
|
||||
|
||||
$('#settings-form').on('click', '.' + Settings.MOVE_UP_BUTTON_CLASS, function(){
|
||||
moveTableRow(this, true);
|
||||
})
|
||||
|
||||
|
||||
$('#settings-form').on('click', '.' + Settings.MOVE_DOWN_BUTTON_CLASS, function(){
|
||||
moveTableRow(this, false);
|
||||
})
|
||||
|
||||
|
||||
$('#settings-form').on('keypress', 'table input', function(e){
|
||||
if (e.keyCode == 13) {
|
||||
// capture enter in table input
|
||||
// if we have a sibling next to us that has an input, jump to it, otherwise check if we have a glyphicon for add to click
|
||||
sibling = $(this).parent('td').next();
|
||||
|
||||
|
||||
if (sibling.hasClass(Settings.DATA_COL_CLASS)) {
|
||||
// set focus to next input
|
||||
sibling.find('input').focus()
|
||||
} else if (sibling.hasClass(Settings.ADD_DEL_BUTTONS_CLASS)) {
|
||||
sibling.find('.' + Settings.ADD_ROW_BUTTON_CLASS).click()
|
||||
|
||||
|
||||
// set focus to the first input in the new row
|
||||
$(this).closest('table').find('tr.inputs input:first').focus()
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
$('#settings-form').on('change', '.' + Settings.TRIGGER_CHANGE_CLASS , function(){
|
||||
// this input was changed, add the changed data attribute to it
|
||||
$(this).attr('data-changed', true)
|
||||
|
||||
|
||||
badgeSidebarForDifferences($(this))
|
||||
})
|
||||
|
||||
|
||||
$('#advanced-toggle-button').click(function(){
|
||||
Settings.showAdvanced = !Settings.showAdvanced
|
||||
var advancedSelector = $('.' + Settings.ADVANCED_CLASS)
|
||||
|
||||
|
||||
if (Settings.showAdvanced) {
|
||||
advancedSelector.show()
|
||||
$(this).html("Hide advanced")
|
||||
|
@ -166,48 +166,48 @@ $(document).ready(function(){
|
|||
advancedSelector.hide()
|
||||
$(this).html("Show advanced")
|
||||
}
|
||||
|
||||
|
||||
$(this).blur()
|
||||
})
|
||||
|
||||
|
||||
$('#settings-form').on('click', '#choose-domain-btn', function(){
|
||||
chooseFromHighFidelityDomains($(this))
|
||||
})
|
||||
|
||||
|
||||
$('#settings-form').on('change', 'select', function(){
|
||||
$("input[name='" + $(this).attr('data-hidden-input') + "']").val($(this).val()).change()
|
||||
})
|
||||
|
||||
var panelsSource = $('#panels-template').html()
|
||||
Settings.panelsTemplate = _.template(panelsSource)
|
||||
|
||||
|
||||
var sidebarTemplate = $('#list-group-template').html()
|
||||
Settings.sidebarTemplate = _.template(sidebarTemplate)
|
||||
|
||||
|
||||
// $('body').scrollspy({ target: '#setup-sidebar'})
|
||||
|
||||
reloadSettings()
|
||||
|
||||
reloadSettings();
|
||||
})
|
||||
|
||||
function reloadSettings() {
|
||||
$.getJSON('/settings.json', function(data){
|
||||
_.extend(data, viewHelpers)
|
||||
|
||||
|
||||
$('.nav-stacked').html(Settings.sidebarTemplate(data))
|
||||
$('#panels').html(Settings.panelsTemplate(data))
|
||||
|
||||
|
||||
Settings.initialValues = form2js('settings-form', ".", false, cleanupFormValues, true);
|
||||
|
||||
|
||||
// add tooltip to locked settings
|
||||
$('label.locked').tooltip({
|
||||
placement: 'right',
|
||||
title: 'This setting is in the master config file and cannot be changed'
|
||||
})
|
||||
|
||||
|
||||
if (!_.has(data["locked"], "metaverse") && !_.has(data["locked"]["metaverse"], "id")) {
|
||||
// append the domain selection modal, as long as it's not locked
|
||||
appendDomainSelectionModal()
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -224,20 +224,20 @@ $('body').on('click', '.save-button', function(e){
|
|||
$("input:not([data-changed])").each(function(){
|
||||
$(this).prop('disabled', true);
|
||||
});
|
||||
|
||||
|
||||
// grab a JSON representation of the form via form2js
|
||||
var formJSON = form2js('settings-form', ".", false, cleanupFormValues, true);
|
||||
|
||||
|
||||
console.log(formJSON);
|
||||
|
||||
|
||||
// re-enable all inputs
|
||||
$("input").each(function(){
|
||||
$(this).prop('disabled', false);
|
||||
});
|
||||
|
||||
|
||||
// remove focus from the button
|
||||
$(this).blur()
|
||||
|
||||
|
||||
// POST the form JSON to the domain-server settings.json endpoint so the settings are saved
|
||||
$.ajax('/settings.json', {
|
||||
data: JSON.stringify(formJSON),
|
||||
|
@ -254,36 +254,41 @@ $('body').on('click', '.save-button', function(e){
|
|||
showErrorMessage("Error", SETTINGS_ERROR_MESSAGE)
|
||||
reloadSettings();
|
||||
});
|
||||
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
function makeTable(setting, setting_name, setting_value, isLocked) {
|
||||
function makeTable(setting, keypath, setting_value, isLocked) {
|
||||
var isArray = !_.has(setting, 'key')
|
||||
|
||||
|
||||
if (!isArray && setting.can_order) {
|
||||
setting.can_order = false;
|
||||
}
|
||||
|
||||
var html = "<span class='help-block'>" + setting.help + "</span>"
|
||||
html += "<table class='table table-bordered " + (isLocked ? "locked-table" : "") + "' data-short-name='" + setting.name + "' name='" + setting_name
|
||||
+ "' data-setting-type='" + (isArray ? 'array' : 'hash') + "'>"
|
||||
|
||||
|
||||
var html = "";
|
||||
|
||||
if (setting.help) {
|
||||
html += "<span class='help-block'>" + setting.help + "</span>"
|
||||
}
|
||||
|
||||
html += "<table class='table table-bordered " + (isLocked ? "locked-table" : "") + "' data-short-name='" + setting.name
|
||||
+ "' name='" + keypath + "' data-setting-type='" + (isArray ? 'array' : 'hash') + "'>"
|
||||
|
||||
// Column names
|
||||
html += "<tr class='headers'>"
|
||||
|
||||
|
||||
if (setting.numbered === true) {
|
||||
html += "<td class='number'><strong>#</strong></td>" // Row number
|
||||
}
|
||||
|
||||
|
||||
if (setting.key) {
|
||||
html += "<td class='key'><strong>" + setting.key.label + "</strong></td>" // Key
|
||||
}
|
||||
|
||||
|
||||
_.each(setting.columns, function(col) {
|
||||
html += "<td class='data'><strong>" + col.label + "</strong></td>" // Data
|
||||
})
|
||||
|
||||
|
||||
if (!isLocked) {
|
||||
if (setting.can_order) {
|
||||
html += "<td class=" + Settings.REORDER_BUTTONS_CLASSES +
|
||||
|
@ -291,39 +296,39 @@ function makeTable(setting, setting_name, setting_value, isLocked) {
|
|||
}
|
||||
html += "<td class=" + Settings.ADD_DEL_BUTTONS_CLASSES + "></td></tr>"
|
||||
}
|
||||
|
||||
|
||||
// populate rows in the table from existing values
|
||||
var row_num = 1
|
||||
|
||||
|
||||
_.each(setting_value, function(row, indexOrName) {
|
||||
html += "<tr class='" + Settings.DATA_ROW_CLASS + "'" + (isArray ? "" : "name='" + setting_name + "." + indexOrName + "'") + ">"
|
||||
|
||||
html += "<tr class='" + Settings.DATA_ROW_CLASS + "'" + (isArray ? "" : "name='" + keypath + "." + indexOrName + "'") + ">"
|
||||
|
||||
if (setting.numbered === true) {
|
||||
html += "<td class='numbered'>" + row_num + "</td>"
|
||||
}
|
||||
|
||||
|
||||
if (setting.key) {
|
||||
html += "<td class='key'>" + indexOrName + "</td>"
|
||||
}
|
||||
|
||||
|
||||
_.each(setting.columns, function(col) {
|
||||
html += "<td class='" + Settings.DATA_COL_CLASS + "'>"
|
||||
|
||||
|
||||
if (isArray) {
|
||||
rowIsObject = setting.columns.length > 1
|
||||
colValue = rowIsObject ? row[col.name] : row
|
||||
html += colValue
|
||||
|
||||
|
||||
// for arrays we add a hidden input to this td so that values can be posted appropriately
|
||||
html += "<input type='hidden' name='" + setting_name + "[" + indexOrName + "]"
|
||||
html += "<input type='hidden' name='" + keypath + "[" + indexOrName + "]"
|
||||
+ (rowIsObject ? "." + col.name : "") + "' value='" + colValue + "'/>"
|
||||
} else if (row.hasOwnProperty(col.name)) {
|
||||
html += row[col.name]
|
||||
html += row[col.name]
|
||||
}
|
||||
|
||||
|
||||
html += "</td>"
|
||||
})
|
||||
|
||||
|
||||
if (!isLocked) {
|
||||
if (setting.can_order) {
|
||||
html += "<td class='" + Settings.REORDER_BUTTONS_CLASSES+
|
||||
|
@ -333,86 +338,97 @@ function makeTable(setting, setting_name, setting_value, isLocked) {
|
|||
html += "<td class='" + Settings.ADD_DEL_BUTTONS_CLASSES +
|
||||
"'><span class='" + Settings.DEL_ROW_SPAN_CLASSES + "'></span></td>"
|
||||
}
|
||||
|
||||
|
||||
html += "</tr>"
|
||||
|
||||
|
||||
row_num++
|
||||
})
|
||||
|
||||
|
||||
// populate inputs in the table for new values
|
||||
if (!isLocked) {
|
||||
html += makeTableInputs(setting)
|
||||
}
|
||||
html += "</table>"
|
||||
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
function makeTableInputs(setting) {
|
||||
var html = "<tr class='inputs'>"
|
||||
|
||||
|
||||
if (setting.numbered === true) {
|
||||
html += "<td class='numbered'></td>"
|
||||
}
|
||||
|
||||
|
||||
if (setting.key) {
|
||||
html += "<td class='key' name='" + setting.key.name + "'>\
|
||||
<input type='text' class='form-control' placeholder='" + (_.has(setting.key, 'placeholder') ? setting.key.placeholder : "") + "' value=''>\
|
||||
</td>"
|
||||
}
|
||||
|
||||
|
||||
_.each(setting.columns, function(col) {
|
||||
html += "<td class='" + Settings.DATA_COL_CLASS + "'name='" + col.name + "'>\
|
||||
<input type='text' class='form-control' placeholder='" + (col.placeholder ? col.placeholder : "") + "'\
|
||||
value='" + (col.default ? col.default : "") + "' data-default='" + (col.default ? col.default : "") + "'>\
|
||||
</td>"
|
||||
})
|
||||
|
||||
|
||||
if (setting.can_order) {
|
||||
html += "<td class='" + Settings.REORDER_BUTTONS_CLASSES + "'></td>"
|
||||
}
|
||||
html += "<td class='" + Settings.ADD_DEL_BUTTONS_CLASSES +
|
||||
"'><span class='glyphicon glyphicon-plus " + Settings.ADD_ROW_BUTTON_CLASS + "'></span></td>"
|
||||
html += "</tr>"
|
||||
|
||||
|
||||
return html
|
||||
}
|
||||
|
||||
function badgeSidebarForDifferences(changedElement) {
|
||||
// figure out which group this input is in
|
||||
var panelParentID = changedElement.closest('.panel').attr('id')
|
||||
|
||||
// get a JSON representation of that section
|
||||
var panelJSON = form2js(panelParentID, ".", false, cleanupFormValues, true)[panelParentID]
|
||||
var initialPanelJSON = Settings.initialValues[panelParentID]
|
||||
|
||||
|
||||
// if the panel contains non-grouped settings, the initial value is Settings.initialValues
|
||||
var isGrouped = $(panelParentID).hasClass('grouped');
|
||||
|
||||
if (isGrouped) {
|
||||
var initialPanelJSON = Settings.initialValues[panelParentID];
|
||||
|
||||
// get a JSON representation of that section
|
||||
var panelJSON = form2js(panelParentID, ".", false, cleanupFormValues, true)[panelParentID];
|
||||
} else {
|
||||
var initialPanelJSON = Settings.initialValues;
|
||||
|
||||
// get a JSON representation of that section
|
||||
var panelJSON = form2js(panelParentID, ".", false, cleanupFormValues, true);
|
||||
}
|
||||
|
||||
var badgeValue = 0
|
||||
|
||||
|
||||
// badge for any settings we have that are not the same or are not present in initialValues
|
||||
for (var setting in panelJSON) {
|
||||
if ((!_.has(initialPanelJSON, setting) && panelJSON[setting] !== "") ||
|
||||
(!_.isEqual(panelJSON[setting], initialPanelJSON[setting])
|
||||
if ((!_.has(initialPanelJSON, setting) && panelJSON[setting] !== "") ||
|
||||
(!_.isEqual(panelJSON[setting], initialPanelJSON[setting])
|
||||
&& (panelJSON[setting] !== "" || _.has(initialPanelJSON, setting)))) {
|
||||
badgeValue += 1
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// update the list-group-item badge to have the new value
|
||||
if (badgeValue == 0) {
|
||||
badgeValue = ""
|
||||
}
|
||||
|
||||
|
||||
$("a[href='#" + panelParentID + "'] .badge").html(badgeValue);
|
||||
}
|
||||
|
||||
function addTableRow(add_glyphicon) {
|
||||
var row = $(add_glyphicon).closest('tr')
|
||||
|
||||
|
||||
var table = row.parents('table')
|
||||
var isArray = table.data('setting-type') === 'array'
|
||||
|
||||
|
||||
var columns = row.parent().children('.' + Settings.DATA_ROW_CLASS)
|
||||
|
||||
|
||||
if (!isArray) {
|
||||
// Check key spaces
|
||||
var key = row.children(".key").children("input").val()
|
||||
|
@ -433,7 +449,7 @@ function addTableRow(add_glyphicon) {
|
|||
return
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Check empty fields
|
||||
var empty = false;
|
||||
_.each(row.children('.' + Settings.DATA_COL_CLASS + ' input'), function(element) {
|
||||
|
@ -442,23 +458,23 @@ function addTableRow(add_glyphicon) {
|
|||
return
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
if (empty) {
|
||||
showErrorMessage("Error", "Empty field(s)")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
var input_clone = row.clone()
|
||||
|
||||
|
||||
// Change input row to data row
|
||||
var table = row.parents("table")
|
||||
var setting_name = table.attr("name")
|
||||
var setting_name = table.attr("name")
|
||||
var full_name = setting_name + "." + key
|
||||
row.addClass(Settings.DATA_ROW_CLASS + " " + Settings.NEW_ROW_CLASS)
|
||||
row.removeClass("inputs")
|
||||
|
||||
|
||||
_.each(row.children(), function(element) {
|
||||
if ($(element).hasClass("numbered")) {
|
||||
if ($(element).hasClass("numbered")) {
|
||||
// Index row
|
||||
var numbers = columns.children(".numbered")
|
||||
if (numbers.length > 0) {
|
||||
|
@ -478,88 +494,88 @@ function addTableRow(add_glyphicon) {
|
|||
var input = $(element).children("input")
|
||||
$(element).html(input.val())
|
||||
input.remove()
|
||||
} else if ($(element).hasClass(Settings.DATA_COL_CLASS)) {
|
||||
} else if ($(element).hasClass(Settings.DATA_COL_CLASS)) {
|
||||
// Hide inputs
|
||||
var input = $(element).children("input")
|
||||
input.attr("type", "hidden")
|
||||
|
||||
|
||||
if (isArray) {
|
||||
var row_index = row.siblings('.' + Settings.DATA_ROW_CLASS).length
|
||||
var key = $(element).attr('name')
|
||||
|
||||
|
||||
// are there multiple columns or just one?
|
||||
// with multiple we have an array of Objects, with one we have an array of whatever the value type is
|
||||
var num_columns = row.children('.' + Settings.DATA_COL_CLASS).length
|
||||
input.attr("name", setting_name + "[" + row_index + "]" + (num_columns > 1 ? "." + key : ""))
|
||||
input.attr("name", setting_name + "[" + row_index + "]" + (num_columns > 1 ? "." + key : ""))
|
||||
} else {
|
||||
input.attr("name", full_name + "." + $(element).attr("name"))
|
||||
}
|
||||
|
||||
|
||||
input.attr("data-changed", "true")
|
||||
|
||||
|
||||
$(element).append(input.val())
|
||||
} else {
|
||||
console.log("Unknown table element")
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
input_clone.find('input').each(function(){
|
||||
$(this).val($(this).attr('data-default'));
|
||||
});
|
||||
|
||||
|
||||
if (isArray) {
|
||||
updateDataChangedForSiblingRows(row, true)
|
||||
|
||||
|
||||
// the addition of any table row should remove the empty-array-row
|
||||
row.siblings('.empty-array-row').remove()
|
||||
}
|
||||
|
||||
|
||||
badgeSidebarForDifferences($(table))
|
||||
|
||||
|
||||
row.parent().append(input_clone)
|
||||
}
|
||||
|
||||
function deleteTableRow(delete_glyphicon) {
|
||||
var row = $(delete_glyphicon).closest('tr')
|
||||
|
||||
|
||||
var table = $(row).closest('table')
|
||||
var isArray = table.data('setting-type') === 'array'
|
||||
|
||||
|
||||
row.empty();
|
||||
|
||||
|
||||
if (!isArray) {
|
||||
row.html("<input type='hidden' class='form-control' name='"
|
||||
row.html("<input type='hidden' class='form-control' name='"
|
||||
+ row.attr('name') + "' data-changed='true' value=''>");
|
||||
} else {
|
||||
if (table.find('.' + Settings.DATA_ROW_CLASS).length > 1) {
|
||||
updateDataChangedForSiblingRows(row)
|
||||
|
||||
|
||||
// this isn't the last row - we can just remove it
|
||||
row.remove()
|
||||
} else {
|
||||
// this is the last row, we can't remove it completely since we need to post an empty array
|
||||
|
||||
|
||||
row.removeClass(Settings.DATA_ROW_CLASS).removeClass(Settings.NEW_ROW_CLASS)
|
||||
row.addClass('empty-array-row')
|
||||
|
||||
row.html("<input type='hidden' class='form-control' name='" + table.attr("name").replace('[]', '')
|
||||
|
||||
row.html("<input type='hidden' class='form-control' name='" + table.attr("name").replace('[]', '')
|
||||
+ "' data-changed='true' value=''>");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// we need to fire a change event on one of the remaining inputs so that the sidebar badge is updated
|
||||
badgeSidebarForDifferences($(table))
|
||||
}
|
||||
|
||||
function moveTableRow(move_glyphicon, move_up) {
|
||||
var row = $(move_glyphicon).closest('tr')
|
||||
|
||||
|
||||
var table = $(row).closest('table')
|
||||
var isArray = table.data('setting-type') === 'array'
|
||||
if (!isArray) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (move_up) {
|
||||
var prev_row = row.prev()
|
||||
if (prev_row.hasClass(Settings.DATA_ROW_CLASS)) {
|
||||
|
@ -571,7 +587,7 @@ function moveTableRow(move_glyphicon, move_up) {
|
|||
next_row.after(row)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// we need to fire a change event on one of the remaining inputs so that the sidebar badge is updated
|
||||
badgeSidebarForDifferences($(table))
|
||||
}
|
||||
|
@ -579,23 +595,23 @@ function moveTableRow(move_glyphicon, move_up) {
|
|||
function updateDataChangedForSiblingRows(row, forceTrue) {
|
||||
// anytime a new row is added to an array we need to set data-changed for all sibling row inputs to true
|
||||
// unless it matches the inital set of values
|
||||
|
||||
|
||||
if (!forceTrue) {
|
||||
// figure out which group this row is in
|
||||
var panelParentID = row.closest('.panel').attr('id')
|
||||
// get the short name for the setting from the table
|
||||
var tableShortName = row.closest('table').data('short-name')
|
||||
|
||||
|
||||
// get a JSON representation of that section
|
||||
var panelSettingJSON = form2js(panelParentID, ".", false, cleanupFormValues, true)[panelParentID][tableShortName]
|
||||
var initialPanelSettingJSON = Settings.initialValues[panelParentID][tableShortName]
|
||||
|
||||
|
||||
// if they are equal, we don't need data-changed
|
||||
isTrue = !_.isEqual(panelSettingJSON, initialPanelSettingJSON)
|
||||
} else {
|
||||
isTrue = true
|
||||
}
|
||||
|
||||
|
||||
row.siblings('.' + Settings.DATA_ROW_CLASS).each(function(){
|
||||
var hiddenInput = $(this).find('td.' + Settings.DATA_COL_CLASS + ' input')
|
||||
if (isTrue) {
|
||||
|
@ -611,19 +627,19 @@ function showRestartModal() {
|
|||
backdrop: 'static',
|
||||
keyboard: false
|
||||
});
|
||||
|
||||
|
||||
var secondsElapsed = 0;
|
||||
var numberOfSecondsToWait = 3;
|
||||
|
||||
|
||||
var refreshSpan = $('span#refresh-time')
|
||||
refreshSpan.html(numberOfSecondsToWait + " seconds");
|
||||
|
||||
|
||||
// call ourselves every 1 second to countdown
|
||||
var refreshCountdown = setInterval(function(){
|
||||
secondsElapsed++;
|
||||
secondsLeft = numberOfSecondsToWait - secondsElapsed
|
||||
refreshSpan.html(secondsLeft + (secondsLeft == 1 ? " second" : " seconds"))
|
||||
|
||||
|
||||
if (secondsElapsed == numberOfSecondsToWait) {
|
||||
location.reload(true);
|
||||
clearInterval(refreshCountdown);
|
||||
|
@ -646,22 +662,22 @@ function showErrorMessage(title, message) {
|
|||
function chooseFromHighFidelityDomains(clickedButton) {
|
||||
// setup the modal to help user pick their domain
|
||||
if (Settings.initialValues.metaverse.access_token) {
|
||||
|
||||
|
||||
// add a spinner to the choose button
|
||||
clickedButton.html("Loading domains...")
|
||||
clickedButton.attr('disabled', 'disabled')
|
||||
|
||||
|
||||
// get a list of user domains from data-web
|
||||
data_web_domains_url = "https://metaverse.highfidelity.com/api/v1/domains?access_token="
|
||||
$.getJSON(data_web_domains_url + Settings.initialValues.metaverse.access_token, function(data){
|
||||
|
||||
|
||||
modal_buttons = {
|
||||
cancel: {
|
||||
label: 'Cancel',
|
||||
className: 'btn-default'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (data.data.domains.length) {
|
||||
// setup a select box for the returned domains
|
||||
modal_body = "<p>Choose the High Fidelity domain you want this domain-server to represent.<br/>This will set your domain ID on the settings page.</p>"
|
||||
|
@ -685,25 +701,25 @@ function chooseFromHighFidelityDomains(clickedButton) {
|
|||
window.open("https://metaverse.highfidelity.com/user/domains", '_blank');
|
||||
}
|
||||
}
|
||||
modal_body = "<p>You do not have any domains in your High Fidelity account." +
|
||||
modal_body = "<p>You do not have any domains in your High Fidelity account." +
|
||||
"<br/><br/>Go to your domains page to create a new one. Once your domain is created re-open this dialog to select it.</p>"
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
bootbox.dialog({
|
||||
title: "Choose matching domain",
|
||||
message: modal_body,
|
||||
buttons: modal_buttons
|
||||
})
|
||||
|
||||
|
||||
// remove the spinner from the choose button
|
||||
clickedButton.html("Choose from my domains")
|
||||
clickedButton.removeAttr('disabled')
|
||||
})
|
||||
|
||||
|
||||
} else {
|
||||
bootbox.alert({
|
||||
message: "You must have an access token to query your High Fidelity domains.<br><br>" +
|
||||
message: "You must have an access token to query your High Fidelity domains.<br><br>" +
|
||||
"Please follow the instructions on the settings page to add an access token.",
|
||||
title: "Access token required"
|
||||
})
|
||||
|
|
|
@ -5,31 +5,32 @@
|
|||
<div class="col-md-12">
|
||||
<div class="alert" style="display:none;"></div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="col-md-3 col-sm-3" id="setup-sidebar-col">
|
||||
<div id="setup-sidebar" data-clampedwidth="#setup-sidebar-col" class="hidden-xs" data-spy="affix" data-offset-top="55">
|
||||
<script id="list-group-template" type="text/template">
|
||||
<% _.each(descriptions, function(group){ %>
|
||||
<% panelID = group.name ? group.name : group.label %>
|
||||
<li>
|
||||
<a href="#<%-group.name %>" class="list-group-item">
|
||||
<a href="#<%- panelID %>" class="list-group-item">
|
||||
<span class="badge"></span>
|
||||
<%- group.label %>
|
||||
</a>
|
||||
</li>
|
||||
<% }); %>
|
||||
</script>
|
||||
|
||||
|
||||
<ul class="nav nav-pills nav-stacked">
|
||||
</ul>
|
||||
|
||||
|
||||
<button id="advanced-toggle-button" hidden=true class="btn btn-info">Show advanced</button>
|
||||
<button class="btn btn-success save-button">Save and restart</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="col-md-9 col-sm-9 col-xs-12">
|
||||
<form id="settings-form" role="form">
|
||||
|
||||
|
||||
<script id="panels-template" type="text/template">
|
||||
<% _.each(descriptions, function(group){ %>
|
||||
<% split_settings = _.partition(group.settings, function(value, index) { return !value.advanced }) %>
|
||||
|
@ -37,32 +38,40 @@
|
|||
<% if (isAdvanced) { %>
|
||||
<% $("a[href=#" + group.name + "]").addClass('advanced-setting').hide() %>
|
||||
<% } %>
|
||||
<div class="panel panel-default <%- (isAdvanced) ? 'advanced-setting' : '' %>" id="<%- group.name %>">
|
||||
|
||||
<% isGrouped = !!group.name %>
|
||||
<% panelID = isGrouped ? group.name : group.label %>
|
||||
|
||||
<div class="panel panel-default<%- (isAdvanced) ? ' advanced-setting' : '' %><%- (isGrouped) ? ' grouped' : '' %>"
|
||||
id="<%- panelID %>">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title"><%- group.label %></h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<% _.each(split_settings[0], function(setting) { %>
|
||||
<%= getFormGroup(group.name, setting, values, false,
|
||||
<% keypath = isGrouped ? group.name + "." + setting.name : setting.name %>
|
||||
<%= getFormGroup(keypath, setting, values, false,
|
||||
(_.has(locked, group.name) && _.has(locked[group.name], setting.name))) %>
|
||||
<% }); %>
|
||||
<% if (!_.isEmpty(split_settings[1])) { %>
|
||||
<% $("#advanced-toggle-button").show() %>
|
||||
<% _.each(split_settings[1], function(setting) { %>
|
||||
<%= getFormGroup(group.name, setting, values, true,
|
||||
<% keypath = isGrouped ? group.name + "." + setting.name : setting.name %>
|
||||
<%= getFormGroup(keypath, setting, values, true,
|
||||
(_.has(locked, group.name) && _.has(locked[group.name], setting.name))) %>
|
||||
<% }); %>
|
||||
<% }%>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<% }); %>
|
||||
</script>
|
||||
|
||||
|
||||
<div id="panels"></div>
|
||||
|
||||
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="col-xs-12 hidden-sm hidden-md hidden-lg">
|
||||
<button class="btn btn-success save-button" id="small-save-button">Save and restart</button>
|
||||
</div>
|
||||
|
@ -83,8 +92,9 @@
|
|||
|
||||
<!--#include virtual="footer.html"-->
|
||||
<script src='/js/underscore-min.js'></script>
|
||||
<script src='/js/underscore-keypath.min.js'></script>
|
||||
<script src='/js/bootbox.min.js'></script>
|
||||
<script src='/js/sweet-alert.min.js'></script>
|
||||
<script src='/js/settings.js'></script>
|
||||
<script src='/js/form2js.min.js'></script>
|
||||
<!--#include virtual="page-end.html"-->
|
||||
<!--#include virtual="page-end.html"-->
|
||||
|
|
|
@ -9,6 +9,6 @@
|
|||
<script src='js/json.human.js'></script>
|
||||
<script src='js/highcharts-custom.js'></script>
|
||||
<script src='/js/underscore-min.js'></script>
|
||||
<script src='js/underscore-keypath.min.js'></script>
|
||||
<script src='/js/underscore-keypath.min.js'></script>
|
||||
<script src='/js/bootbox.min.js'></script>
|
||||
<!--#include virtual="page-end.html"-->
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -39,29 +39,29 @@ class DomainServer : public QCoreApplication, public HTTPSRequestHandler {
|
|||
Q_OBJECT
|
||||
public:
|
||||
DomainServer(int argc, char* argv[]);
|
||||
|
||||
|
||||
static int const EXIT_CODE_REBOOT;
|
||||
|
||||
|
||||
bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler = false);
|
||||
bool handleHTTPSRequest(HTTPSConnection* connection, const QUrl& url, bool skipSubHandler = false);
|
||||
|
||||
|
||||
public slots:
|
||||
/// Called by NodeList to inform us a node has been added
|
||||
void nodeAdded(SharedNodePointer node);
|
||||
/// Called by NodeList to inform us a node has been killed
|
||||
void nodeKilled(SharedNodePointer node);
|
||||
|
||||
|
||||
void publicKeyJSONCallback(QNetworkReply& requestReply);
|
||||
void transactionJSONCallback(const QJsonObject& data);
|
||||
|
||||
|
||||
void restart();
|
||||
|
||||
|
||||
private slots:
|
||||
void loginFailed();
|
||||
void readAvailableDatagrams();
|
||||
void setupPendingAssignmentCredits();
|
||||
void sendPendingTransactionsToServer();
|
||||
|
||||
|
||||
void requestCurrentPublicSocketViaSTUN();
|
||||
void performIPAddressUpdate(const HifiSockAddr& newPublicSockAddr);
|
||||
void performICEUpdates();
|
||||
|
@ -74,23 +74,23 @@ private:
|
|||
bool optionallyReadX509KeyAndCertificate();
|
||||
bool didSetupAccountManagerWithAccessToken();
|
||||
bool optionallySetupAssignmentPayment();
|
||||
|
||||
|
||||
void setupAutomaticNetworking();
|
||||
void sendHeartbeatToDataServer(const QString& networkAddress);
|
||||
void processICEPingReply(const QByteArray& packet, const HifiSockAddr& senderSockAddr);
|
||||
void processICEHeartbeatResponse(const QByteArray& packet);
|
||||
|
||||
|
||||
void processDatagram(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr);
|
||||
|
||||
|
||||
void handleConnectRequest(const QByteArray& packet, const HifiSockAddr& senderSockAddr);
|
||||
unsigned int countConnectedUsers();
|
||||
bool verifyUsersKey (const QString& username, const QByteArray& usernameSignature, QString& reasonReturn);
|
||||
bool shouldAllowConnectionFromNode(const QString& username, const QByteArray& usernameSignature,
|
||||
const HifiSockAddr& senderSockAddr, QString& reasonReturn);
|
||||
|
||||
|
||||
void preloadAllowedUserPublicKeys();
|
||||
void requestUserPublicKey(const QString& username);
|
||||
|
||||
|
||||
int parseNodeDataFromByteArray(QDataStream& packetStream,
|
||||
NodeType_t& nodeType,
|
||||
HifiSockAddr& publicSockAddr,
|
||||
|
@ -99,61 +99,63 @@ private:
|
|||
NodeSet nodeInterestListFromPacket(const QByteArray& packet, int numPreceedingBytes);
|
||||
void sendDomainListToNode(const SharedNodePointer& node, const HifiSockAddr& senderSockAddr,
|
||||
const NodeSet& nodeInterestList);
|
||||
|
||||
|
||||
void parseAssignmentConfigs(QSet<Assignment::Type>& excludedTypes);
|
||||
void addStaticAssignmentToAssignmentHash(Assignment* newAssignment);
|
||||
void createStaticAssignmentsForType(Assignment::Type type, const QVariantList& configList);
|
||||
void populateDefaultStaticAssignmentsExcludingTypes(const QSet<Assignment::Type>& excludedTypes);
|
||||
void populateStaticScriptedAssignmentsFromSettings();
|
||||
|
||||
|
||||
SharedAssignmentPointer matchingQueuedAssignmentForCheckIn(const QUuid& checkInUUID, NodeType_t nodeType);
|
||||
SharedAssignmentPointer deployableAssignmentForRequest(const Assignment& requestAssignment);
|
||||
void removeMatchingAssignmentFromQueue(const SharedAssignmentPointer& removableAssignment);
|
||||
void refreshStaticAssignmentAndAddToQueue(SharedAssignmentPointer& assignment);
|
||||
void addStaticAssignmentsToQueue();
|
||||
|
||||
|
||||
void respondToPathQuery(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr);
|
||||
|
||||
QUrl oauthRedirectURL();
|
||||
QUrl oauthAuthorizationURL(const QUuid& stateUUID = QUuid::createUuid());
|
||||
|
||||
|
||||
bool isAuthenticatedRequest(HTTPConnection* connection, const QUrl& url);
|
||||
|
||||
void handleTokenRequestFinished();
|
||||
QNetworkReply* profileRequestGivenTokenReply(QNetworkReply* tokenReply);
|
||||
void handleProfileRequestFinished();
|
||||
Headers setupCookieHeadersFromProfileReply(QNetworkReply* profileReply);
|
||||
|
||||
|
||||
void loadExistingSessionsFromSettings();
|
||||
|
||||
|
||||
QJsonObject jsonForSocket(const HifiSockAddr& socket);
|
||||
QJsonObject jsonObjectForNode(const SharedNodePointer& node);
|
||||
|
||||
|
||||
HTTPManager _httpManager;
|
||||
HTTPSManager* _httpsManager;
|
||||
|
||||
|
||||
QHash<QUuid, SharedAssignmentPointer> _allAssignments;
|
||||
QQueue<SharedAssignmentPointer> _unfulfilledAssignments;
|
||||
QHash<QUuid, PendingAssignedNodeData*> _pendingAssignedNodes;
|
||||
TransactionHash _pendingAssignmentCredits;
|
||||
|
||||
|
||||
bool _isUsingDTLS;
|
||||
|
||||
|
||||
QUrl _oauthProviderURL;
|
||||
QString _oauthClientID;
|
||||
QString _oauthClientSecret;
|
||||
QString _hostname;
|
||||
|
||||
|
||||
QSet<QUuid> _webAuthenticationStateSet;
|
||||
QHash<QUuid, DomainServerWebSessionData> _cookieSessionHash;
|
||||
|
||||
|
||||
QHash<QString, QByteArray> _userPublicKeys;
|
||||
|
||||
|
||||
QHash<QUuid, NetworkPeer> _connectingICEPeers;
|
||||
QHash<QUuid, HifiSockAddr> _connectedICEPeers;
|
||||
|
||||
|
||||
QString _automaticNetworkingSetting;
|
||||
|
||||
|
||||
DomainServerSettingsManager _settingsManager;
|
||||
|
||||
|
||||
HifiSockAddr _iceServerSocket;
|
||||
};
|
||||
|
||||
|
|
|
@ -32,6 +32,8 @@ const QString DESCRIPTION_NAME_KEY = "name";
|
|||
const QString SETTING_DESCRIPTION_TYPE_KEY = "type";
|
||||
const QString DESCRIPTION_COLUMNS_KEY = "columns";
|
||||
|
||||
const QString SETTINGS_VIEWPOINT_KEY = "viewpoint";
|
||||
|
||||
DomainServerSettingsManager::DomainServerSettingsManager() :
|
||||
_descriptionArray(),
|
||||
_configMap()
|
||||
|
@ -39,31 +41,31 @@ DomainServerSettingsManager::DomainServerSettingsManager() :
|
|||
// load the description object from the settings description
|
||||
QFile descriptionFile(QCoreApplication::applicationDirPath() + SETTINGS_DESCRIPTION_RELATIVE_PATH);
|
||||
descriptionFile.open(QIODevice::ReadOnly);
|
||||
|
||||
|
||||
_descriptionArray = QJsonDocument::fromJson(descriptionFile.readAll()).array();
|
||||
}
|
||||
|
||||
void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList) {
|
||||
_configMap.loadMasterAndUserConfig(argumentList);
|
||||
|
||||
|
||||
// for now we perform a temporary transition from http-username and http-password to http_username and http_password
|
||||
const QVariant* oldUsername = valueForKeyPath(_configMap.getUserConfig(), "security.http-username");
|
||||
const QVariant* oldPassword = valueForKeyPath(_configMap.getUserConfig(), "security.http-password");
|
||||
|
||||
|
||||
if (oldUsername || oldPassword) {
|
||||
QVariantMap& settingsMap = *reinterpret_cast<QVariantMap*>(_configMap.getUserConfig()["security"].data());
|
||||
|
||||
|
||||
// remove old keys, move to new format
|
||||
if (oldUsername) {
|
||||
settingsMap["http_username"] = oldUsername->toString();
|
||||
settingsMap.remove("http-username");
|
||||
}
|
||||
|
||||
|
||||
if (oldPassword) {
|
||||
settingsMap["http_password"] = oldPassword->toString();
|
||||
settingsMap.remove("http-password");
|
||||
}
|
||||
|
||||
|
||||
// save the updated settings
|
||||
persistToFile();
|
||||
}
|
||||
|
@ -71,18 +73,18 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList
|
|||
|
||||
QVariant DomainServerSettingsManager::valueOrDefaultValueForKeyPath(const QString &keyPath) {
|
||||
const QVariant* foundValue = valueForKeyPath(_configMap.getMergedConfig(), keyPath);
|
||||
|
||||
|
||||
if (foundValue) {
|
||||
return *foundValue;
|
||||
} else {
|
||||
int dotIndex = keyPath.indexOf('.');
|
||||
|
||||
|
||||
QString groupKey = keyPath.mid(0, dotIndex);
|
||||
QString settingKey = keyPath.mid(dotIndex + 1);
|
||||
|
||||
|
||||
foreach(const QVariant& group, _descriptionArray.toVariantList()) {
|
||||
QVariantMap groupMap = group.toMap();
|
||||
|
||||
|
||||
if (groupMap[DESCRIPTION_NAME_KEY].toString() == groupKey) {
|
||||
foreach(const QVariant& setting, groupMap[DESCRIPTION_SETTINGS_KEY].toList()) {
|
||||
QVariantMap settingMap = setting.toMap();
|
||||
|
@ -90,12 +92,12 @@ QVariant DomainServerSettingsManager::valueOrDefaultValueForKeyPath(const QStrin
|
|||
return settingMap[SETTING_DEFAULT_KEY];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
|
@ -104,23 +106,23 @@ const QString SETTINGS_PATH = "/settings.json";
|
|||
bool DomainServerSettingsManager::handlePublicHTTPRequest(HTTPConnection* connection, const QUrl &url) {
|
||||
if (connection->requestOperation() == QNetworkAccessManager::GetOperation && url.path() == SETTINGS_PATH) {
|
||||
// this is a GET operation for our settings
|
||||
|
||||
|
||||
// check if there is a query parameter for settings affecting a particular type of assignment
|
||||
const QString SETTINGS_TYPE_QUERY_KEY = "type";
|
||||
QUrlQuery settingsQuery(url);
|
||||
QString typeValue = settingsQuery.queryItemValue(SETTINGS_TYPE_QUERY_KEY);
|
||||
|
||||
|
||||
if (!typeValue.isEmpty()) {
|
||||
QJsonObject responseObject = responseObjectForType(typeValue);
|
||||
|
||||
|
||||
connection->respond(HTTPConnection::StatusCode200, QJsonDocument(responseObject).toJson(), "application/json");
|
||||
|
||||
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -129,130 +131,125 @@ bool DomainServerSettingsManager::handleAuthenticatedHTTPRequest(HTTPConnection
|
|||
// this is a POST operation to change one or more settings
|
||||
QJsonDocument postedDocument = QJsonDocument::fromJson(connection->requestContent());
|
||||
QJsonObject postedObject = postedDocument.object();
|
||||
|
||||
qDebug() << "The postedObject is" << postedObject;
|
||||
|
||||
qDebug() << "DomainServerSettingsManager postedObject -" << postedObject;
|
||||
|
||||
// we recurse one level deep below each group for the appropriate setting
|
||||
recurseJSONObjectAndOverwriteSettings(postedObject, _configMap.getUserConfig(), _descriptionArray);
|
||||
|
||||
recurseJSONObjectAndOverwriteSettings(postedObject, _configMap.getUserConfig());
|
||||
|
||||
// store whatever the current _settingsMap is to file
|
||||
persistToFile();
|
||||
|
||||
|
||||
// return success to the caller
|
||||
QString jsonSuccess = "{\"status\": \"success\"}";
|
||||
connection->respond(HTTPConnection::StatusCode200, jsonSuccess.toUtf8(), "application/json");
|
||||
|
||||
|
||||
// defer a restart to the domain-server, this gives our HTTPConnection enough time to respond
|
||||
const int DOMAIN_SERVER_RESTART_TIMER_MSECS = 1000;
|
||||
QTimer::singleShot(DOMAIN_SERVER_RESTART_TIMER_MSECS, qApp, SLOT(restart()));
|
||||
|
||||
|
||||
return true;
|
||||
} else if (connection->requestOperation() == QNetworkAccessManager::GetOperation && url.path() == SETTINGS_PATH) {
|
||||
// setup a JSON Object with descriptions and non-omitted settings
|
||||
const QString SETTINGS_RESPONSE_DESCRIPTION_KEY = "descriptions";
|
||||
const QString SETTINGS_RESPONSE_VALUE_KEY = "values";
|
||||
const QString SETTINGS_RESPONSE_LOCKED_VALUES_KEY = "locked";
|
||||
|
||||
|
||||
QJsonObject rootObject;
|
||||
rootObject[SETTINGS_RESPONSE_DESCRIPTION_KEY] = _descriptionArray;
|
||||
rootObject[SETTINGS_RESPONSE_VALUE_KEY] = responseObjectForType("", true);
|
||||
rootObject[SETTINGS_RESPONSE_LOCKED_VALUES_KEY] = QJsonDocument::fromVariant(_configMap.getMasterConfig()).object();
|
||||
|
||||
|
||||
|
||||
|
||||
connection->respond(HTTPConnection::StatusCode200, QJsonDocument(rootObject).toJson(), "application/json");
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
QJsonObject DomainServerSettingsManager::responseObjectForType(const QString& typeValue, bool isAuthenticated) {
|
||||
QJsonObject responseObject;
|
||||
|
||||
|
||||
if (!typeValue.isEmpty() || isAuthenticated) {
|
||||
// convert the string type value to a QJsonValue
|
||||
QJsonValue queryType = typeValue.isEmpty() ? QJsonValue() : QJsonValue(typeValue.toInt());
|
||||
|
||||
|
||||
const QString AFFECTED_TYPES_JSON_KEY = "assignment-types";
|
||||
|
||||
|
||||
// enumerate the groups in the description object to find which settings to pass
|
||||
foreach(const QJsonValue& groupValue, _descriptionArray) {
|
||||
QJsonObject groupObject = groupValue.toObject();
|
||||
QString groupKey = groupObject[DESCRIPTION_NAME_KEY].toString();
|
||||
QJsonArray groupSettingsArray = groupObject[DESCRIPTION_SETTINGS_KEY].toArray();
|
||||
|
||||
|
||||
QJsonObject groupResponseObject;
|
||||
|
||||
|
||||
foreach(const QJsonValue& settingValue, groupSettingsArray) {
|
||||
const QString VALUE_HIDDEN_FLAG_KEY = "value-hidden";
|
||||
|
||||
|
||||
QJsonObject settingObject = settingValue.toObject();
|
||||
|
||||
|
||||
if (!settingObject[VALUE_HIDDEN_FLAG_KEY].toBool()) {
|
||||
QJsonArray affectedTypesArray = settingObject[AFFECTED_TYPES_JSON_KEY].toArray();
|
||||
if (affectedTypesArray.isEmpty()) {
|
||||
affectedTypesArray = groupObject[AFFECTED_TYPES_JSON_KEY].toArray();
|
||||
}
|
||||
|
||||
|
||||
if (affectedTypesArray.contains(queryType) ||
|
||||
(queryType.isNull() && isAuthenticated)) {
|
||||
// this is a setting we should include in the responseObject
|
||||
|
||||
|
||||
QString settingName = settingObject[DESCRIPTION_NAME_KEY].toString();
|
||||
|
||||
|
||||
// we need to check if the settings map has a value for this setting
|
||||
QVariant variantValue;
|
||||
QVariant settingsMapGroupValue = _configMap.getMergedConfig()
|
||||
.value(groupObject[DESCRIPTION_NAME_KEY].toString());
|
||||
|
||||
if (!settingsMapGroupValue.isNull()) {
|
||||
variantValue = settingsMapGroupValue.toMap().value(settingName);
|
||||
|
||||
if (!groupKey.isEmpty()) {
|
||||
QVariant settingsMapGroupValue = _configMap.getMergedConfig().value(groupKey);
|
||||
|
||||
if (!settingsMapGroupValue.isNull()) {
|
||||
variantValue = settingsMapGroupValue.toMap().value(settingName);
|
||||
}
|
||||
} else {
|
||||
variantValue = _configMap.getMergedConfig().value(settingName);
|
||||
}
|
||||
|
||||
|
||||
QJsonValue result;
|
||||
|
||||
if (variantValue.isNull()) {
|
||||
// no value for this setting, pass the default
|
||||
if (settingObject.contains(SETTING_DEFAULT_KEY)) {
|
||||
groupResponseObject[settingName] = settingObject[SETTING_DEFAULT_KEY];
|
||||
result = settingObject[SETTING_DEFAULT_KEY];
|
||||
} else {
|
||||
// users are allowed not to provide a default for string values
|
||||
// if so we set to the empty string
|
||||
groupResponseObject[settingName] = QString("");
|
||||
result = QString("");
|
||||
}
|
||||
|
||||
|
||||
} else {
|
||||
groupResponseObject[settingName] = QJsonValue::fromVariant(variantValue);
|
||||
result = QJsonValue::fromVariant(variantValue);
|
||||
}
|
||||
|
||||
if (!groupKey.isEmpty()) {
|
||||
// this belongs in the group object
|
||||
groupResponseObject[settingName] = result;
|
||||
} else {
|
||||
// this is a value that should be at the root
|
||||
responseObject[settingName] = result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!groupResponseObject.isEmpty()) {
|
||||
|
||||
if (!groupKey.isEmpty() && !groupResponseObject.isEmpty()) {
|
||||
// set this group's object to the constructed object
|
||||
responseObject[groupKey] = groupResponseObject;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return responseObject;
|
||||
}
|
||||
|
||||
bool DomainServerSettingsManager::settingExists(const QString& groupName, const QString& settingName,
|
||||
const QJsonArray& descriptionArray, QJsonObject& settingDescription) {
|
||||
foreach(const QJsonValue& groupValue, descriptionArray) {
|
||||
QJsonObject groupObject = groupValue.toObject();
|
||||
if (groupObject[DESCRIPTION_NAME_KEY].toString() == groupName) {
|
||||
|
||||
foreach(const QJsonValue& settingValue, groupObject[DESCRIPTION_SETTINGS_KEY].toArray()) {
|
||||
QJsonObject settingObject = settingValue.toObject();
|
||||
if (settingObject[DESCRIPTION_NAME_KEY].toString() == settingName) {
|
||||
settingDescription = settingObject;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
settingDescription = QJsonObject();
|
||||
return false;
|
||||
|
||||
return responseObject;
|
||||
}
|
||||
|
||||
void DomainServerSettingsManager::updateSetting(const QString& key, const QJsonValue& newValue, QVariantMap& settingMap,
|
||||
|
@ -266,13 +263,21 @@ void DomainServerSettingsManager::updateSetting(const QString& key, const QJsonV
|
|||
QString settingType = settingDescription[SETTING_DESCRIPTION_TYPE_KEY].toString();
|
||||
const QString INPUT_DOUBLE_TYPE = "double";
|
||||
const QString INPUT_INTEGER_TYPE = "int";
|
||||
|
||||
|
||||
if (settingType == INPUT_DOUBLE_TYPE) {
|
||||
settingMap[key] = newValue.toString().toDouble();
|
||||
} else if (settingType == INPUT_INTEGER_TYPE) {
|
||||
settingMap[key] = newValue.toString().toInt();
|
||||
} else {
|
||||
settingMap[key] = newValue.toString();
|
||||
QString sanitizedValue = newValue.toString();
|
||||
|
||||
// we perform special handling for viewpoints here
|
||||
// we do not want them to be prepended with a slash
|
||||
if (key == SETTINGS_VIEWPOINT_KEY && !sanitizedValue.startsWith('/')) {
|
||||
sanitizedValue.prepend('/');
|
||||
}
|
||||
|
||||
settingMap[key] = sanitizedValue;
|
||||
}
|
||||
}
|
||||
} else if (newValue.isBool()) {
|
||||
|
@ -282,12 +287,12 @@ void DomainServerSettingsManager::updateSetting(const QString& key, const QJsonV
|
|||
// we don't have a map below this key yet, so set it up now
|
||||
settingMap[key] = QVariantMap();
|
||||
}
|
||||
|
||||
|
||||
QVariantMap& thisMap = *reinterpret_cast<QVariantMap*>(settingMap[key].data());
|
||||
foreach(const QString childKey, newValue.toObject().keys()) {
|
||||
|
||||
QJsonObject childDescriptionObject = settingDescription;
|
||||
|
||||
|
||||
// is this the key? if so we have the description already
|
||||
if (key != settingDescription[DESCRIPTION_NAME_KEY].toString()) {
|
||||
// otherwise find the description object for this childKey under columns
|
||||
|
@ -299,12 +304,20 @@ void DomainServerSettingsManager::updateSetting(const QString& key, const QJsonV
|
|||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateSetting(childKey, newValue.toObject()[childKey], thisMap, childDescriptionObject);
|
||||
|
||||
QString sanitizedKey = childKey;
|
||||
|
||||
if (key == SETTINGS_PATHS_KEY && !sanitizedKey.startsWith('/')) {
|
||||
// We perform special handling for paths here.
|
||||
// If we got sent a path without a leading slash then we add it.
|
||||
sanitizedKey.prepend("/");
|
||||
}
|
||||
|
||||
updateSetting(sanitizedKey, newValue.toObject()[childKey], thisMap, childDescriptionObject);
|
||||
}
|
||||
|
||||
|
||||
if (settingMap[key].toMap().isEmpty()) {
|
||||
// we've cleared all of the settings below this value, so remove this one too
|
||||
settingMap.remove(key);
|
||||
|
@ -316,47 +329,104 @@ void DomainServerSettingsManager::updateSetting(const QString& key, const QJsonV
|
|||
}
|
||||
}
|
||||
|
||||
void DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject,
|
||||
QVariantMap& settingsVariant,
|
||||
const QJsonArray& descriptionArray) {
|
||||
// Iterate on the setting groups
|
||||
foreach(const QString& groupKey, postedObject.keys()) {
|
||||
QJsonValue groupValue = postedObject[groupKey];
|
||||
|
||||
if (!settingsVariant.contains(groupKey)) {
|
||||
// we don't have a map below this key yet, so set it up now
|
||||
settingsVariant[groupKey] = QVariantMap();
|
||||
QJsonObject DomainServerSettingsManager::settingDescriptionFromGroup(const QJsonObject& groupObject, const QString& settingName) {
|
||||
foreach(const QJsonValue& settingValue, groupObject[DESCRIPTION_SETTINGS_KEY].toArray()) {
|
||||
QJsonObject settingObject = settingValue.toObject();
|
||||
if (settingObject[DESCRIPTION_NAME_KEY].toString() == settingName) {
|
||||
return settingObject;
|
||||
}
|
||||
|
||||
// Iterate on the settings
|
||||
foreach(const QString& settingKey, groupValue.toObject().keys()) {
|
||||
QJsonValue settingValue = groupValue.toObject()[settingKey];
|
||||
|
||||
QJsonObject thisDescription;
|
||||
if (settingExists(groupKey, settingKey, descriptionArray, thisDescription)) {
|
||||
QVariantMap& thisMap = *reinterpret_cast<QVariantMap*>(settingsVariant[groupKey].data());
|
||||
updateSetting(settingKey, settingValue, thisMap, thisDescription);
|
||||
}
|
||||
|
||||
return QJsonObject();
|
||||
}
|
||||
|
||||
void DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject,
|
||||
QVariantMap& settingsVariant) {
|
||||
// Iterate on the setting groups
|
||||
foreach(const QString& rootKey, postedObject.keys()) {
|
||||
QJsonValue rootValue = postedObject[rootKey];
|
||||
|
||||
if (!settingsVariant.contains(rootKey)) {
|
||||
// we don't have a map below this key yet, so set it up now
|
||||
settingsVariant[rootKey] = QVariantMap();
|
||||
}
|
||||
|
||||
QVariantMap& thisMap = settingsVariant;
|
||||
|
||||
QJsonObject groupDescriptionObject;
|
||||
|
||||
// we need to check the description array to see if this is a root setting or a group setting
|
||||
foreach(const QJsonValue& groupValue, _descriptionArray) {
|
||||
if (groupValue.toObject()[DESCRIPTION_NAME_KEY] == rootKey) {
|
||||
// we matched a group - keep this since we'll use it below to update the settings
|
||||
groupDescriptionObject = groupValue.toObject();
|
||||
|
||||
// change the map we will update to be the map for this group
|
||||
thisMap = *reinterpret_cast<QVariantMap*>(settingsVariant[rootKey].data());
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (settingsVariant[groupKey].toMap().empty()) {
|
||||
|
||||
if (groupDescriptionObject.isEmpty()) {
|
||||
// this is a root value, so we can call updateSetting for it directly
|
||||
// first we need to find our description value for it
|
||||
|
||||
QJsonObject matchingDescriptionObject;
|
||||
|
||||
foreach(const QJsonValue& groupValue, _descriptionArray) {
|
||||
// find groups with root values (they don't have a group name)
|
||||
QJsonObject groupObject = groupValue.toObject();
|
||||
if (!groupObject.contains(DESCRIPTION_NAME_KEY)) {
|
||||
// this is a group with root values - check if our setting is in here
|
||||
matchingDescriptionObject = settingDescriptionFromGroup(groupObject, rootKey);
|
||||
|
||||
if (!matchingDescriptionObject.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!matchingDescriptionObject.isEmpty()) {
|
||||
updateSetting(rootKey, rootValue, thisMap, matchingDescriptionObject);
|
||||
} else {
|
||||
qDebug() << "Setting for root key" << rootKey << "does not exist - cannot update setting.";
|
||||
}
|
||||
} else {
|
||||
// this is a group - iterate on the settings in the group
|
||||
foreach(const QString& settingKey, rootValue.toObject().keys()) {
|
||||
// make sure this particular setting exists and we have a description object for it
|
||||
QJsonObject matchingDescriptionObject = settingDescriptionFromGroup(groupDescriptionObject, settingKey);
|
||||
|
||||
// if we matched the setting then update the value
|
||||
if (!matchingDescriptionObject.isEmpty()) {
|
||||
QJsonValue settingValue = rootValue.toObject()[settingKey];
|
||||
updateSetting(settingKey, settingValue, thisMap, matchingDescriptionObject);
|
||||
} else {
|
||||
qDebug() << "Could not find description for setting" << settingKey << "in group" << rootKey <<
|
||||
"- cannot update setting.";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (settingsVariant[rootKey].toMap().empty()) {
|
||||
// we've cleared all of the settings below this value, so remove this one too
|
||||
settingsVariant.remove(groupKey);
|
||||
settingsVariant.remove(rootKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DomainServerSettingsManager::persistToFile() {
|
||||
|
||||
|
||||
// make sure we have the dir the settings file is supposed to live in
|
||||
QFileInfo settingsFileInfo(_configMap.getUserConfigFilename());
|
||||
|
||||
|
||||
if (!settingsFileInfo.dir().exists()) {
|
||||
settingsFileInfo.dir().mkpath(".");
|
||||
}
|
||||
|
||||
|
||||
QFile settingsFile(_configMap.getUserConfigFilename());
|
||||
|
||||
|
||||
if (settingsFile.open(QIODevice::WriteOnly)) {
|
||||
settingsFile.write(QJsonDocument::fromVariant(_configMap.getUserConfig()).toJson());
|
||||
} else {
|
||||
|
|
|
@ -18,27 +18,28 @@
|
|||
#include <HifiConfigVariantMap.h>
|
||||
#include <HTTPManager.h>
|
||||
|
||||
const QString SETTINGS_PATHS_KEY = "paths";
|
||||
|
||||
class DomainServerSettingsManager : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
DomainServerSettingsManager();
|
||||
bool handlePublicHTTPRequest(HTTPConnection* connection, const QUrl& url);
|
||||
bool handleAuthenticatedHTTPRequest(HTTPConnection* connection, const QUrl& url);
|
||||
|
||||
|
||||
void setupConfigMap(const QStringList& argumentList);
|
||||
QVariant valueOrDefaultValueForKeyPath(const QString& keyPath);
|
||||
|
||||
|
||||
QVariantMap& getSettingsMap() { return _configMap.getMergedConfig(); }
|
||||
private:
|
||||
QJsonObject responseObjectForType(const QString& typeValue, bool isAuthenticated = false);
|
||||
void recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject, QVariantMap& settingsVariant,
|
||||
const QJsonArray& descriptionArray);
|
||||
bool settingExists(const QString& groupName, const QString& settingName,
|
||||
const QJsonArray& descriptionArray, QJsonObject& settingDescription);
|
||||
void recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject, QVariantMap& settingsVariant);
|
||||
|
||||
void updateSetting(const QString& key, const QJsonValue& newValue, QVariantMap& settingMap,
|
||||
const QJsonObject& settingDescription);
|
||||
QJsonObject settingDescriptionFromGroup(const QJsonObject& groupObject, const QString& settingName);
|
||||
void persistToFile();
|
||||
|
||||
|
||||
QJsonArray _descriptionArray;
|
||||
HifiConfigVariantMap _configMap;
|
||||
};
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
|
||||
var isGrabbing = false;
|
||||
var grabbedEntity = null;
|
||||
var lineEntityID = null;
|
||||
var prevMouse = {};
|
||||
var deltaMouse = {
|
||||
z: 0
|
||||
|
@ -60,8 +61,13 @@ function vectorIsZero(v) {
|
|||
return v.x == 0 && v.y == 0 && v.z == 0;
|
||||
}
|
||||
|
||||
function vectorToString(v) {
|
||||
return "(" + v.x + ", " + v.y + ", " + v.z + ")"
|
||||
function nearLinePoint(targetPosition) {
|
||||
// var handPosition = Vec3.sum(MyAvatar.position, {x:0, y:0.2, z:0});
|
||||
var handPosition = MyAvatar.getRightPalmPosition();
|
||||
var along = Vec3.subtract(targetPosition, handPosition);
|
||||
along = Vec3.normalize(along);
|
||||
along = Vec3.multiply(along, 0.4);
|
||||
return Vec3.sum(handPosition, along);
|
||||
}
|
||||
|
||||
|
||||
|
@ -76,7 +82,6 @@ function mousePressEvent(event) {
|
|||
var props = Entities.getEntityProperties(grabbedEntity)
|
||||
isGrabbing = true;
|
||||
originalGravity = props.gravity;
|
||||
print("mouse-press setting originalGravity " + originalGravity + " " + vectorToString(originalGravity));
|
||||
targetPosition = props.position;
|
||||
currentPosition = props.position;
|
||||
currentVelocity = props.velocity;
|
||||
|
@ -86,6 +91,14 @@ function mousePressEvent(event) {
|
|||
gravity: {x: 0, y: 0, z: 0}
|
||||
});
|
||||
|
||||
lineEntityID = Entities.addEntity({
|
||||
type: "Line",
|
||||
position: nearLinePoint(targetPosition),
|
||||
dimensions: Vec3.subtract(targetPosition, nearLinePoint(targetPosition)),
|
||||
color: { red: 255, green: 255, blue: 255 },
|
||||
lifetime: 300 // if someone crashes while moving something, don't leave the line there forever.
|
||||
});
|
||||
|
||||
Audio.playSound(grabSound, {
|
||||
position: props.position,
|
||||
volume: 0.4
|
||||
|
@ -121,18 +134,18 @@ function mouseReleaseEvent() {
|
|||
// 4. interface A releases the entity and puts the original gravity back
|
||||
// 5. interface B releases the entity and puts the original gravity back (to zero)
|
||||
if (!vectorIsZero(originalGravity)) {
|
||||
print("mouse-release restoring originalGravity" + vectorToString(originalGravity));
|
||||
Entities.editEntity(grabbedEntity, {
|
||||
gravity: originalGravity
|
||||
});
|
||||
} else {
|
||||
print("mouse-release not restoring originalGravity of zero");
|
||||
}
|
||||
|
||||
Overlays.editOverlay(dropLine, {
|
||||
visible: false
|
||||
});
|
||||
targetPosition = null;
|
||||
|
||||
Entities.deleteEntity(lineEntityID);
|
||||
|
||||
Audio.playSound(grabSound, {
|
||||
position: entityProps.position,
|
||||
volume: 0.25
|
||||
|
@ -147,7 +160,6 @@ function mouseMoveEvent(event) {
|
|||
var props = Entities.getEntityProperties(grabbedEntity);
|
||||
if (!vectorIsZero(props.gravity)) {
|
||||
originalGravity = props.gravity;
|
||||
print("mouse-move adopting originalGravity" + vectorToString(originalGravity));
|
||||
}
|
||||
|
||||
deltaMouse.x = event.x - prevMouse.x;
|
||||
|
@ -179,6 +191,11 @@ function mouseMoveEvent(event) {
|
|||
axisAngle = Quat.axis(dQ);
|
||||
angularVelocity = Vec3.multiply((theta / dT), axisAngle);
|
||||
}
|
||||
|
||||
Entities.editEntity(lineEntityID, {
|
||||
position: nearLinePoint(targetPosition),
|
||||
dimensions: Vec3.subtract(targetPosition, nearLinePoint(targetPosition))
|
||||
});
|
||||
}
|
||||
prevMouse.x = event.x;
|
||||
prevMouse.y = event.y;
|
||||
|
@ -243,9 +260,11 @@ function update(deltaTime) {
|
|||
}
|
||||
|
||||
Entities.editEntity(grabbedEntity, {
|
||||
position: currentPosition,
|
||||
rotation: currentRotation,
|
||||
velocity: newVelocity,
|
||||
angularVelocity: angularVelocity
|
||||
})
|
||||
});
|
||||
updateDropLine(targetPosition);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -623,7 +623,7 @@
|
|||
elColorBlue.addEventListener('change', colorChangeFunction);
|
||||
$('#property-color').colpick({
|
||||
colorScheme:'dark',
|
||||
layout:'rgbhex',
|
||||
layout:'hex',
|
||||
color:'000000',
|
||||
onSubmit: function(hsb, hex, rgb, el) {
|
||||
$(el).css('background-color', '#'+hex);
|
||||
|
@ -641,7 +641,7 @@
|
|||
elLightColorBlue.addEventListener('change', lightColorChangeFunction);
|
||||
$('#property-light-color').colpick({
|
||||
colorScheme:'dark',
|
||||
layout:'rgbhex',
|
||||
layout:'hex',
|
||||
color:'000000',
|
||||
onSubmit: function(hsb, hex, rgb, el) {
|
||||
$(el).css('background-color', '#'+hex);
|
||||
|
@ -674,7 +674,7 @@
|
|||
elTextTextColorBlue.addEventListener('change', textTextColorChangeFunction);
|
||||
$('#property-text-text-color').colpick({
|
||||
colorScheme:'dark',
|
||||
layout:'rgbhex',
|
||||
layout:'hex',
|
||||
color:'000000',
|
||||
onSubmit: function(hsb, hex, rgb, el) {
|
||||
$(el).css('background-color', '#'+hex);
|
||||
|
@ -690,7 +690,7 @@
|
|||
elTextBackgroundColorBlue.addEventListener('change', textBackgroundColorChangeFunction);
|
||||
$('#property-text-background-color').colpick({
|
||||
colorScheme:'dark',
|
||||
layout:'rgbhex',
|
||||
layout:'hex',
|
||||
color:'000000',
|
||||
onSubmit: function(hsb, hex, rgb, el) {
|
||||
$(el).css('background-color', '#'+hex);
|
||||
|
@ -702,7 +702,7 @@
|
|||
elZoneStageSunModelEnabled.addEventListener('change', createEmitGroupCheckedPropertyUpdateFunction('stage','sunModelEnabled'));
|
||||
$('#property-zone-key-light-color').colpick({
|
||||
colorScheme:'dark',
|
||||
layout:'rgbhex',
|
||||
layout:'hex',
|
||||
color:'000000',
|
||||
onSubmit: function(hsb, hex, rgb, el) {
|
||||
$(el).css('background-color', '#'+hex);
|
||||
|
@ -739,7 +739,7 @@
|
|||
elZoneSkyboxColorBlue.addEventListener('change', zoneSkyboxColorChangeFunction);
|
||||
$('#property-zone-skybox-color').colpick({
|
||||
colorScheme:'dark',
|
||||
layout:'rgbhex',
|
||||
layout:'hex',
|
||||
color:'000000',
|
||||
onSubmit: function(hsb, hex, rgb, el) {
|
||||
$(el).css('background-color', '#'+hex);
|
||||
|
|
|
@ -1949,6 +1949,7 @@ void Application::setActiveFaceTracker() {
|
|||
#endif
|
||||
#ifdef HAVE_DDE
|
||||
bool isUsingDDE = Menu::getInstance()->isOptionChecked(MenuOption::UseCamera);
|
||||
Menu::getInstance()->getActionForOption(MenuOption::BinaryEyelidControl)->setVisible(isUsingDDE);
|
||||
Menu::getInstance()->getActionForOption(MenuOption::UseAudioForMouth)->setVisible(isUsingDDE);
|
||||
Menu::getInstance()->getActionForOption(MenuOption::VelocityFilter)->setVisible(isUsingDDE);
|
||||
Menu::getInstance()->getActionForOption(MenuOption::CalibrateCamera)->setVisible(isUsingDDE);
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
DatagramProcessor::DatagramProcessor(QObject* parent) :
|
||||
QObject(parent)
|
||||
{
|
||||
|
||||
|
||||
}
|
||||
|
||||
void DatagramProcessor::processDatagrams() {
|
||||
|
@ -35,23 +35,23 @@ void DatagramProcessor::processDatagrams() {
|
|||
if (_isShuttingDown) {
|
||||
return; // bail early... we're shutting down.
|
||||
}
|
||||
|
||||
|
||||
HifiSockAddr senderSockAddr;
|
||||
|
||||
|
||||
static QByteArray incomingPacket;
|
||||
|
||||
|
||||
Application* application = Application::getInstance();
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
||||
|
||||
while (DependencyManager::get<NodeList>()->getNodeSocket().hasPendingDatagrams()) {
|
||||
incomingPacket.resize(nodeList->getNodeSocket().pendingDatagramSize());
|
||||
nodeList->readDatagram(incomingPacket, senderSockAddr.getAddressPointer(), senderSockAddr.getPortPointer());
|
||||
|
||||
|
||||
_inPacketCount++;
|
||||
_inByteCount += incomingPacket.size();
|
||||
|
||||
|
||||
if (nodeList->packetVersionAndHashMatch(incomingPacket)) {
|
||||
|
||||
|
||||
PacketType incomingType = packetTypeForPacket(incomingPacket);
|
||||
// only process this packet if we have a match on the packet version
|
||||
switch (incomingType) {
|
||||
|
@ -72,14 +72,14 @@ void DatagramProcessor::processDatagrams() {
|
|||
Qt::QueuedConnection,
|
||||
Q_ARG(QByteArray, incomingPacket));
|
||||
}
|
||||
|
||||
|
||||
// update having heard from the audio-mixer and record the bytes received
|
||||
SharedNodePointer audioMixer = nodeList->sendingNodeForPacket(incomingPacket);
|
||||
|
||||
|
||||
if (audioMixer) {
|
||||
audioMixer->setLastHeardMicrostamp(usecTimestampNow());
|
||||
}
|
||||
|
||||
|
||||
break;
|
||||
}
|
||||
case PacketTypeEntityAddResponse:
|
||||
|
@ -94,7 +94,7 @@ void DatagramProcessor::processDatagrams() {
|
|||
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
|
||||
"Application::networkReceive()... _octreeProcessor.queueReceivedPacket()");
|
||||
SharedNodePointer matchedNode = DependencyManager::get<NodeList>()->sendingNodeForPacket(incomingPacket);
|
||||
|
||||
|
||||
if (matchedNode) {
|
||||
// add this packet to our list of octree packets and process them on the octree data processing
|
||||
application->_octreeProcessor.queueReceivedPacket(matchedNode, incomingPacket);
|
||||
|
@ -107,10 +107,10 @@ void DatagramProcessor::processDatagrams() {
|
|||
case PacketTypeAvatarBillboard: {
|
||||
// update having heard from the avatar-mixer and record the bytes received
|
||||
SharedNodePointer avatarMixer = nodeList->sendingNodeForPacket(incomingPacket);
|
||||
|
||||
|
||||
if (avatarMixer) {
|
||||
avatarMixer->setLastHeardMicrostamp(usecTimestampNow());
|
||||
|
||||
|
||||
QMetaObject::invokeMethod(DependencyManager::get<AvatarManager>().data(), "processAvatarMixerDatagram",
|
||||
Q_ARG(const QByteArray&, incomingPacket),
|
||||
Q_ARG(const QWeakPointer<Node>&, avatarMixer));
|
||||
|
@ -135,20 +135,20 @@ void DatagramProcessor::processDatagrams() {
|
|||
case PacketTypeNoisyMute:
|
||||
case PacketTypeMuteEnvironment: {
|
||||
bool mute = !DependencyManager::get<AudioClient>()->isMuted();
|
||||
|
||||
|
||||
if (incomingType == PacketTypeMuteEnvironment) {
|
||||
glm::vec3 position;
|
||||
float radius;
|
||||
|
||||
|
||||
int headerSize = numBytesForPacketHeaderGivenPacketType(PacketTypeMuteEnvironment);
|
||||
memcpy(&position, incomingPacket.constData() + headerSize, sizeof(glm::vec3));
|
||||
memcpy(&radius, incomingPacket.constData() + headerSize + sizeof(glm::vec3), sizeof(float));
|
||||
float distance = glm::distance(DependencyManager::get<AvatarManager>()->getMyAvatar()->getPosition(),
|
||||
position);
|
||||
|
||||
|
||||
mute = mute && (distance < radius);
|
||||
}
|
||||
|
||||
|
||||
if (mute) {
|
||||
DependencyManager::get<AudioClient>()->toggleMute();
|
||||
if (incomingType == PacketTypeMuteEnvironment) {
|
||||
|
|
|
@ -388,6 +388,8 @@ Menu::Menu() {
|
|||
}
|
||||
#ifdef HAVE_DDE
|
||||
faceTrackingMenu->addSeparator();
|
||||
QAction* binaryEyelidControl = addCheckableActionToQMenuAndActionHash(faceTrackingMenu, MenuOption::BinaryEyelidControl, 0, true);
|
||||
binaryEyelidControl->setVisible(false);
|
||||
QAction* useAudioForMouth = addCheckableActionToQMenuAndActionHash(faceTrackingMenu, MenuOption::UseAudioForMouth, 0, true);
|
||||
useAudioForMouth->setVisible(false);
|
||||
QAction* ddeFiltering = addCheckableActionToQMenuAndActionHash(faceTrackingMenu, MenuOption::VelocityFilter, 0, true);
|
||||
|
|
|
@ -149,6 +149,7 @@ namespace MenuOption {
|
|||
const QString AudioStatsShowInjectedStreams = "Audio Stats Show Injected Streams";
|
||||
const QString AvatarReceiveStats = "Show Receive Stats";
|
||||
const QString BandwidthDetails = "Bandwidth Details";
|
||||
const QString BinaryEyelidControl = "Binary Eyelid Control";
|
||||
const QString BlueSpeechSphere = "Blue Sphere While Speaking";
|
||||
const QString BookmarkLocation = "Bookmark Location";
|
||||
const QString Bookmarks = "Bookmarks";
|
||||
|
|
|
@ -137,7 +137,7 @@ struct Packet {
|
|||
};
|
||||
|
||||
static const float STARTING_DDE_MESSAGE_TIME = 0.033f;
|
||||
|
||||
static const float DEFAULT_DDE_EYE_CLOSING_THRESHOLD = 0.8f;
|
||||
static const int CALIBRATION_SAMPLES = 150;
|
||||
|
||||
#ifdef WIN32
|
||||
|
@ -182,6 +182,7 @@ DdeFaceTracker::DdeFaceTracker(const QHostAddress& host, quint16 serverPort, qui
|
|||
_lastEyeBlinks(),
|
||||
_filteredEyeBlinks(),
|
||||
_lastEyeCoefficients(),
|
||||
_eyeClosingThreshold("ddeEyeClosingThreshold", DEFAULT_DDE_EYE_CLOSING_THRESHOLD),
|
||||
_isCalibrating(false),
|
||||
_calibrationCount(0),
|
||||
_calibrationValues(),
|
||||
|
@ -194,8 +195,8 @@ DdeFaceTracker::DdeFaceTracker(const QHostAddress& host, quint16 serverPort, qui
|
|||
_coefficientAverages.resize(NUM_FACESHIFT_BLENDSHAPES);
|
||||
_calibrationValues.resize(NUM_FACESHIFT_BLENDSHAPES);
|
||||
|
||||
_eyeStates[0] = EYE_OPEN;
|
||||
_eyeStates[1] = EYE_OPEN;
|
||||
_eyeStates[0] = EYE_UNCONTROLLED;
|
||||
_eyeStates[1] = EYE_UNCONTROLLED;
|
||||
|
||||
connect(&_udpSocket, SIGNAL(readyRead()), SLOT(readPendingDatagrams()));
|
||||
connect(&_udpSocket, SIGNAL(error(QAbstractSocket::SocketError)), SLOT(socketErrorOccurred(QAbstractSocket::SocketError)));
|
||||
|
@ -450,57 +451,72 @@ void DdeFaceTracker::decodePacket(const QByteArray& buffer) {
|
|||
|
||||
// Finesse EyeBlink values
|
||||
float eyeCoefficients[2];
|
||||
for (int i = 0; i < 2; i++) {
|
||||
// Scale EyeBlink values so that they can be used to control both EyeBlink and EyeOpen
|
||||
// -ve values control EyeOpen; +ve values control EyeBlink
|
||||
static const float EYE_CONTROL_THRESHOLD = 0.5f; // Resting eye value
|
||||
eyeCoefficients[i] = (_filteredEyeBlinks[i] - EYE_CONTROL_THRESHOLD) / (1.0f - EYE_CONTROL_THRESHOLD);
|
||||
|
||||
// Change to closing or opening states
|
||||
const float EYE_CONTROL_HYSTERISIS = 0.25f;
|
||||
const float EYE_CLOSING_THRESHOLD = 0.8f;
|
||||
const float EYE_OPENING_THRESHOLD = EYE_CONTROL_THRESHOLD - EYE_CONTROL_HYSTERISIS;
|
||||
if ((_eyeStates[i] == EYE_OPEN || _eyeStates[i] == EYE_OPENING) && eyeCoefficients[i] > EYE_CLOSING_THRESHOLD) {
|
||||
_eyeStates[i] = EYE_CLOSING;
|
||||
} else if ((_eyeStates[i] == EYE_CLOSED || _eyeStates[i] == EYE_CLOSING)
|
||||
&& eyeCoefficients[i] < EYE_OPENING_THRESHOLD) {
|
||||
_eyeStates[i] = EYE_OPENING;
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::BinaryEyelidControl)) {
|
||||
if (_eyeStates[0] == EYE_UNCONTROLLED) {
|
||||
_eyeStates[0] = EYE_OPEN;
|
||||
_eyeStates[1] = EYE_OPEN;
|
||||
}
|
||||
|
||||
const float EYELID_MOVEMENT_RATE = 10.0f; // units/second
|
||||
const float EYE_OPEN_SCALE = 0.2f;
|
||||
if (_eyeStates[i] == EYE_CLOSING) {
|
||||
// Close eyelid until it's fully closed
|
||||
float closingValue = _lastEyeCoefficients[i] + EYELID_MOVEMENT_RATE * _averageMessageTime;
|
||||
if (closingValue >= 1.0) {
|
||||
_eyeStates[i] = EYE_CLOSED;
|
||||
eyeCoefficients[i] = 1.0;
|
||||
} else {
|
||||
eyeCoefficients[i] = closingValue;
|
||||
for (int i = 0; i < 2; i++) {
|
||||
// Scale EyeBlink values so that they can be used to control both EyeBlink and EyeOpen
|
||||
// -ve values control EyeOpen; +ve values control EyeBlink
|
||||
static const float EYE_CONTROL_THRESHOLD = 0.5f; // Resting eye value
|
||||
eyeCoefficients[i] = (_filteredEyeBlinks[i] - EYE_CONTROL_THRESHOLD) / (1.0f - EYE_CONTROL_THRESHOLD);
|
||||
|
||||
// Change to closing or opening states
|
||||
const float EYE_CONTROL_HYSTERISIS = 0.25f;
|
||||
float eyeClosingThreshold = getEyeClosingThreshold();
|
||||
float eyeOpeningThreshold = eyeClosingThreshold - EYE_CONTROL_HYSTERISIS;
|
||||
if ((_eyeStates[i] == EYE_OPEN || _eyeStates[i] == EYE_OPENING) && eyeCoefficients[i] > eyeClosingThreshold) {
|
||||
_eyeStates[i] = EYE_CLOSING;
|
||||
} else if ((_eyeStates[i] == EYE_CLOSED || _eyeStates[i] == EYE_CLOSING)
|
||||
&& eyeCoefficients[i] < eyeOpeningThreshold) {
|
||||
_eyeStates[i] = EYE_OPENING;
|
||||
}
|
||||
} else if (_eyeStates[i] == EYE_OPENING) {
|
||||
// Open eyelid until it meets the current adjusted value
|
||||
float openingValue = _lastEyeCoefficients[i] - EYELID_MOVEMENT_RATE * _averageMessageTime;
|
||||
if (openingValue < eyeCoefficients[i] * EYE_OPEN_SCALE) {
|
||||
_eyeStates[i] = EYE_OPEN;
|
||||
|
||||
const float EYELID_MOVEMENT_RATE = 10.0f; // units/second
|
||||
const float EYE_OPEN_SCALE = 0.2f;
|
||||
if (_eyeStates[i] == EYE_CLOSING) {
|
||||
// Close eyelid until it's fully closed
|
||||
float closingValue = _lastEyeCoefficients[i] + EYELID_MOVEMENT_RATE * _averageMessageTime;
|
||||
if (closingValue >= 1.0) {
|
||||
_eyeStates[i] = EYE_CLOSED;
|
||||
eyeCoefficients[i] = 1.0;
|
||||
} else {
|
||||
eyeCoefficients[i] = closingValue;
|
||||
}
|
||||
} else if (_eyeStates[i] == EYE_OPENING) {
|
||||
// Open eyelid until it meets the current adjusted value
|
||||
float openingValue = _lastEyeCoefficients[i] - EYELID_MOVEMENT_RATE * _averageMessageTime;
|
||||
if (openingValue < eyeCoefficients[i] * EYE_OPEN_SCALE) {
|
||||
_eyeStates[i] = EYE_OPEN;
|
||||
eyeCoefficients[i] = eyeCoefficients[i] * EYE_OPEN_SCALE;
|
||||
} else {
|
||||
eyeCoefficients[i] = openingValue;
|
||||
}
|
||||
} else if (_eyeStates[i] == EYE_OPEN) {
|
||||
// Reduce eyelid movement
|
||||
eyeCoefficients[i] = eyeCoefficients[i] * EYE_OPEN_SCALE;
|
||||
} else {
|
||||
eyeCoefficients[i] = openingValue;
|
||||
} else if (_eyeStates[i] == EYE_CLOSED) {
|
||||
// Keep eyelid fully closed
|
||||
eyeCoefficients[i] = 1.0;
|
||||
}
|
||||
} else if (_eyeStates[i] == EYE_OPEN) {
|
||||
// Reduce eyelid movement
|
||||
eyeCoefficients[i] = eyeCoefficients[i] * EYE_OPEN_SCALE;
|
||||
} else if (_eyeStates[i] == EYE_CLOSED) {
|
||||
// Keep eyelid fully closed
|
||||
eyeCoefficients[i] = 1.0;
|
||||
}
|
||||
|
||||
if (_eyeStates[0] == EYE_OPEN && _eyeStates[1] == EYE_OPEN) {
|
||||
// Couple eyelids
|
||||
eyeCoefficients[0] = eyeCoefficients[1] = (eyeCoefficients[0] + eyeCoefficients[0]) / 2.0f;
|
||||
}
|
||||
|
||||
_lastEyeCoefficients[0] = eyeCoefficients[0];
|
||||
_lastEyeCoefficients[1] = eyeCoefficients[1];
|
||||
} else {
|
||||
_eyeStates[0] = EYE_UNCONTROLLED;
|
||||
_eyeStates[1] = EYE_UNCONTROLLED;
|
||||
|
||||
eyeCoefficients[0] = _filteredEyeBlinks[0];
|
||||
eyeCoefficients[1] = _filteredEyeBlinks[1];
|
||||
}
|
||||
if (_eyeStates[0] == EYE_OPEN && _eyeStates[1] == EYE_OPEN) {
|
||||
// Couple eyelids
|
||||
eyeCoefficients[0] = eyeCoefficients[1] = (eyeCoefficients[0] + eyeCoefficients[0]) / 2.0f;
|
||||
}
|
||||
_lastEyeCoefficients[0] = eyeCoefficients[0];
|
||||
_lastEyeCoefficients[1] = eyeCoefficients[1];
|
||||
|
||||
// Use EyeBlink values to control both EyeBlink and EyeOpen
|
||||
if (eyeCoefficients[0] > 0) {
|
||||
|
@ -544,6 +560,10 @@ void DdeFaceTracker::decodePacket(const QByteArray& buffer) {
|
|||
}
|
||||
}
|
||||
|
||||
void DdeFaceTracker::setEyeClosingThreshold(float eyeClosingThreshold) {
|
||||
_eyeClosingThreshold.set(eyeClosingThreshold);
|
||||
}
|
||||
|
||||
static const int CALIBRATION_BILLBOARD_WIDTH = 240;
|
||||
static const int CALIBRATION_BILLBOARD_HEIGHT = 180;
|
||||
static const int CALIBRATION_BILLBOARD_TOP_MARGIN = 60;
|
||||
|
|
|
@ -50,6 +50,9 @@ public:
|
|||
float getMouthSmileLeft() const { return getBlendshapeCoefficient(_mouthSmileLeftIndex); }
|
||||
float getMouthSmileRight() const { return getBlendshapeCoefficient(_mouthSmileRightIndex); }
|
||||
|
||||
float getEyeClosingThreshold() { return _eyeClosingThreshold.get(); }
|
||||
void setEyeClosingThreshold(float eyeClosingThreshold);
|
||||
|
||||
public slots:
|
||||
void setEnabled(bool enabled);
|
||||
void calibrate();
|
||||
|
@ -89,8 +92,7 @@ private:
|
|||
int _rightBlinkIndex;
|
||||
int _leftEyeOpenIndex;
|
||||
int _rightEyeOpenIndex;
|
||||
|
||||
// Brows
|
||||
|
||||
int _browDownLeftIndex;
|
||||
int _browDownRightIndex;
|
||||
int _browUpCenterIndex;
|
||||
|
@ -114,6 +116,7 @@ private:
|
|||
float _filteredBrowUp;
|
||||
|
||||
enum EyeState {
|
||||
EYE_UNCONTROLLED,
|
||||
EYE_OPEN,
|
||||
EYE_CLOSING,
|
||||
EYE_CLOSED,
|
||||
|
@ -123,6 +126,8 @@ private:
|
|||
float _lastEyeBlinks[2];
|
||||
float _filteredEyeBlinks[2];
|
||||
float _lastEyeCoefficients[2];
|
||||
Setting::Handle<float> _eyeClosingThreshold;
|
||||
|
||||
QVector<float> _coefficientAverages;
|
||||
|
||||
bool _isCalibrating;
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
|
||||
#include <AudioClient.h>
|
||||
#include <avatar/AvatarManager.h>
|
||||
#include <devices/DdeFaceTracker.h>
|
||||
#include <devices/Faceshift.h>
|
||||
#include <devices/SixenseManager.h>
|
||||
#include <NetworkingConstants.h>
|
||||
|
@ -135,6 +136,10 @@ void PreferencesDialog::loadPreferences() {
|
|||
ui.pupilDilationSlider->setValue(myAvatar->getHead()->getPupilDilation() *
|
||||
ui.pupilDilationSlider->maximum());
|
||||
|
||||
auto dde = DependencyManager::get<DdeFaceTracker>();
|
||||
ui.ddeEyeClosingThresholdSlider->setValue(dde->getEyeClosingThreshold() *
|
||||
ui.ddeEyeClosingThresholdSlider->maximum());
|
||||
|
||||
auto faceshift = DependencyManager::get<Faceshift>();
|
||||
ui.faceshiftEyeDeflectionSider->setValue(faceshift->getEyeDeflection() *
|
||||
ui.faceshiftEyeDeflectionSider->maximum());
|
||||
|
@ -222,6 +227,10 @@ void PreferencesDialog::savePreferences() {
|
|||
|
||||
qApp->setFieldOfView(ui.fieldOfViewSpin->value());
|
||||
|
||||
auto dde = DependencyManager::get<DdeFaceTracker>();
|
||||
dde->setEyeClosingThreshold(ui.ddeEyeClosingThresholdSlider->value() /
|
||||
(float)ui.ddeEyeClosingThresholdSlider->maximum());
|
||||
|
||||
auto faceshift = DependencyManager::get<Faceshift>();
|
||||
faceshift->setEyeDeflection(ui.faceshiftEyeDeflectionSider->value() /
|
||||
(float)ui.faceshiftEyeDeflectionSider->maximum());
|
||||
|
|
|
@ -1256,7 +1256,7 @@
|
|||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Pupil dillation</string>
|
||||
<string>Pupil dilation</string>
|
||||
</property>
|
||||
<property name="indent">
|
||||
<number>0</number>
|
||||
|
@ -1310,6 +1310,82 @@
|
|||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_28">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>7</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>7</number>
|
||||
</property>
|
||||
<item alignment="Qt::AlignLeft">
|
||||
<widget class="QLabel" name="label_7">
|
||||
<property name="font">
|
||||
<font>
|
||||
<family>Arial</family>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Camera binary eyelid threshold</string>
|
||||
</property>
|
||||
<property name="indent">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>ddeEyeClosingThresholdSlider</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_8">
|
||||
<property name="font">
|
||||
<font>
|
||||
<family>Arial</family>
|
||||
</font>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSlider" name="ddeEyeClosingThresholdSlider">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>130</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<family>Arial</family>
|
||||
</font>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_6">
|
||||
<property name="spacing">
|
||||
|
|
|
@ -36,6 +36,7 @@
|
|||
#include "RenderableSphereEntityItem.h"
|
||||
#include "RenderableTextEntityItem.h"
|
||||
#include "RenderableZoneEntityItem.h"
|
||||
#include "RenderableLineEntityItem.h"
|
||||
#include "EntitiesRendererLogging.h"
|
||||
|
||||
EntityTreeRenderer::EntityTreeRenderer(bool wantScripts, AbstractViewStateInterface* viewState,
|
||||
|
@ -59,6 +60,7 @@ EntityTreeRenderer::EntityTreeRenderer(bool wantScripts, AbstractViewStateInterf
|
|||
REGISTER_ENTITY_TYPE_WITH_FACTORY(Text, RenderableTextEntityItem::factory)
|
||||
REGISTER_ENTITY_TYPE_WITH_FACTORY(ParticleEffect, RenderableParticleEffectEntityItem::factory)
|
||||
REGISTER_ENTITY_TYPE_WITH_FACTORY(Zone, RenderableZoneEntityItem::factory)
|
||||
REGISTER_ENTITY_TYPE_WITH_FACTORY(Line, RenderableLineEntityItem::factory)
|
||||
|
||||
_currentHoverOverEntityID = EntityItemID::createInvalidEntityID(); // makes it the unknown ID
|
||||
_currentClickingOnEntityID = EntityItemID::createInvalidEntityID(); // makes it the unknown ID
|
||||
|
|
41
libraries/entities-renderer/src/RenderableLineEntityItem.cpp
Normal file
41
libraries/entities-renderer/src/RenderableLineEntityItem.cpp
Normal file
|
@ -0,0 +1,41 @@
|
|||
//
|
||||
// RenderableLineEntityItem.cpp
|
||||
// libraries/entities-renderer/src/
|
||||
//
|
||||
// Created by Seth Alves on 5/11/15.
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <glm/gtx/quaternion.hpp>
|
||||
|
||||
#include <gpu/GPUConfig.h>
|
||||
|
||||
#include <DeferredLightingEffect.h>
|
||||
#include <PerfStat.h>
|
||||
|
||||
#include "RenderableLineEntityItem.h"
|
||||
|
||||
EntityItem* RenderableLineEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) {
|
||||
return new RenderableLineEntityItem(entityID, properties);
|
||||
}
|
||||
|
||||
void RenderableLineEntityItem::render(RenderArgs* args) {
|
||||
PerformanceTimer perfTimer("RenderableLineEntityItem::render");
|
||||
assert(getType() == EntityTypes::Line);
|
||||
glm::vec3 position = getPosition();
|
||||
glm::vec3 dimensions = getDimensions();
|
||||
glm::quat rotation = getRotation();
|
||||
glm::vec4 lineColor(toGlm(getXColor()), getLocalRenderAlpha());
|
||||
glPushMatrix();
|
||||
glTranslatef(position.x, position.y, position.z);
|
||||
glm::vec3 axis = glm::axis(rotation);
|
||||
glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z);
|
||||
glm::vec3 p1 = {0.0f, 0.0f, 0.0f};
|
||||
glm::vec3& p2 = dimensions;
|
||||
DependencyManager::get<DeferredLightingEffect>()->renderLine(p1, p2, lineColor, lineColor);
|
||||
glPopMatrix();
|
||||
RenderableDebugableEntityItem::render(this, args);
|
||||
};
|
29
libraries/entities-renderer/src/RenderableLineEntityItem.h
Normal file
29
libraries/entities-renderer/src/RenderableLineEntityItem.h
Normal file
|
@ -0,0 +1,29 @@
|
|||
//
|
||||
// RenderableLineEntityItem.h
|
||||
// libraries/entities-renderer/src/
|
||||
//
|
||||
// Created by Seth Alves on 5/11/15.
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_RenderableLineEntityItem_h
|
||||
#define hifi_RenderableLineEntityItem_h
|
||||
|
||||
#include <LineEntityItem.h>
|
||||
#include "RenderableDebugableEntityItem.h"
|
||||
|
||||
class RenderableLineEntityItem : public LineEntityItem {
|
||||
public:
|
||||
static EntityItem* factory(const EntityItemID& entityID, const EntityItemProperties& properties);
|
||||
|
||||
RenderableLineEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties) :
|
||||
LineEntityItem(entityItemID, properties) { }
|
||||
|
||||
virtual void render(RenderArgs* args);
|
||||
};
|
||||
|
||||
|
||||
#endif // hifi_RenderableLineEntityItem_h
|
|
@ -52,6 +52,7 @@ class EntityItemProperties {
|
|||
friend class TextEntityItem; // TODO: consider removing this friend relationship and use public methods
|
||||
friend class ParticleEffectEntityItem; // TODO: consider removing this friend relationship and use public methods
|
||||
friend class ZoneEntityItem; // TODO: consider removing this friend relationship and use public methods
|
||||
friend class LineEntityItem; // TODO: consider removing this friend relationship and use public methods
|
||||
public:
|
||||
EntityItemProperties();
|
||||
virtual ~EntityItemProperties();
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
#include "SphereEntityItem.h"
|
||||
#include "TextEntityItem.h"
|
||||
#include "ZoneEntityItem.h"
|
||||
#include "LineEntityItem.h"
|
||||
|
||||
QMap<EntityTypes::EntityType, QString> EntityTypes::_typeToNameMap;
|
||||
QMap<QString, EntityTypes::EntityType> EntityTypes::_nameToTypeMap;
|
||||
|
@ -41,6 +42,7 @@ REGISTER_ENTITY_TYPE(Light)
|
|||
REGISTER_ENTITY_TYPE(Text)
|
||||
REGISTER_ENTITY_TYPE(ParticleEffect)
|
||||
REGISTER_ENTITY_TYPE(Zone)
|
||||
REGISTER_ENTITY_TYPE(Line)
|
||||
|
||||
const QString& EntityTypes::getEntityTypeName(EntityType entityType) {
|
||||
QMap<EntityType, QString>::iterator matchedTypeName = _typeToNameMap.find(entityType);
|
||||
|
|
|
@ -35,9 +35,10 @@ public:
|
|||
Sphere,
|
||||
Light,
|
||||
Text,
|
||||
ParticleEffect,
|
||||
Zone,
|
||||
LAST = Zone
|
||||
ParticleEffect,
|
||||
Zone,
|
||||
Line,
|
||||
LAST = Line
|
||||
} EntityType;
|
||||
|
||||
static const QString& getEntityTypeName(EntityType entityType);
|
||||
|
|
108
libraries/entities/src/LineEntityItem.cpp
Normal file
108
libraries/entities/src/LineEntityItem.cpp
Normal file
|
@ -0,0 +1,108 @@
|
|||
//
|
||||
// LineEntityItem.cpp
|
||||
// libraries/entities/src
|
||||
//
|
||||
// Created by Seth Alves on 5/11/15.
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#include <ByteCountCoding.h>
|
||||
|
||||
#include "LineEntityItem.h"
|
||||
#include "EntityTree.h"
|
||||
#include "EntitiesLogging.h"
|
||||
#include "EntityTreeElement.h"
|
||||
|
||||
|
||||
EntityItem* LineEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) {
|
||||
EntityItem* result = new LineEntityItem(entityID, properties);
|
||||
return result;
|
||||
}
|
||||
|
||||
LineEntityItem::LineEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties) :
|
||||
EntityItem(entityItemID)
|
||||
{
|
||||
_type = EntityTypes::Line;
|
||||
_created = properties.getCreated();
|
||||
setProperties(properties);
|
||||
}
|
||||
|
||||
EntityItemProperties LineEntityItem::getProperties() const {
|
||||
EntityItemProperties properties = EntityItem::getProperties(); // get the properties from our base class
|
||||
|
||||
properties._color = getXColor();
|
||||
properties._colorChanged = false;
|
||||
|
||||
properties._glowLevel = getGlowLevel();
|
||||
properties._glowLevelChanged = false;
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
bool LineEntityItem::setProperties(const EntityItemProperties& properties) {
|
||||
bool somethingChanged = false;
|
||||
somethingChanged = EntityItem::setProperties(properties); // set the properties in our base class
|
||||
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(color, setColor);
|
||||
|
||||
if (somethingChanged) {
|
||||
bool wantDebug = false;
|
||||
if (wantDebug) {
|
||||
uint64_t now = usecTimestampNow();
|
||||
int elapsed = now - getLastEdited();
|
||||
qCDebug(entities) << "LineEntityItem::setProperties() AFTER update... edited AGO=" << elapsed <<
|
||||
"now=" << now << " getLastEdited()=" << getLastEdited();
|
||||
}
|
||||
setLastEdited(properties._lastEdited);
|
||||
}
|
||||
return somethingChanged;
|
||||
}
|
||||
|
||||
int LineEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead,
|
||||
ReadBitstreamToTreeParams& args,
|
||||
EntityPropertyFlags& propertyFlags, bool overwriteLocalData) {
|
||||
|
||||
int bytesRead = 0;
|
||||
const unsigned char* dataAt = data;
|
||||
|
||||
READ_ENTITY_PROPERTY_COLOR(PROP_COLOR, _color);
|
||||
|
||||
return bytesRead;
|
||||
}
|
||||
|
||||
|
||||
// TODO: eventually only include properties changed since the params.lastViewFrustumSent time
|
||||
EntityPropertyFlags LineEntityItem::getEntityProperties(EncodeBitstreamParams& params) const {
|
||||
EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params);
|
||||
requestedProperties += PROP_COLOR;
|
||||
return requestedProperties;
|
||||
}
|
||||
|
||||
void LineEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params,
|
||||
EntityTreeElementExtraEncodeData* modelTreeElementExtraEncodeData,
|
||||
EntityPropertyFlags& requestedProperties,
|
||||
EntityPropertyFlags& propertyFlags,
|
||||
EntityPropertyFlags& propertiesDidntFit,
|
||||
int& propertyCount,
|
||||
OctreeElement::AppendState& appendState) const {
|
||||
|
||||
bool successPropertyFits = true;
|
||||
|
||||
APPEND_ENTITY_PROPERTY(PROP_COLOR, appendColor, getColor());
|
||||
}
|
||||
|
||||
void LineEntityItem::debugDump() const {
|
||||
quint64 now = usecTimestampNow();
|
||||
qCDebug(entities) << " LINE EntityItem id:" << getEntityItemID() << "---------------------------------------------";
|
||||
qCDebug(entities) << " color:" << _color[0] << "," << _color[1] << "," << _color[2];
|
||||
qCDebug(entities) << " position:" << debugTreeVector(_position);
|
||||
qCDebug(entities) << " dimensions:" << debugTreeVector(_dimensions);
|
||||
qCDebug(entities) << " getLastEdited:" << debugTime(getLastEdited(), now);
|
||||
}
|
||||
|
62
libraries/entities/src/LineEntityItem.h
Normal file
62
libraries/entities/src/LineEntityItem.h
Normal file
|
@ -0,0 +1,62 @@
|
|||
//
|
||||
// LineEntityItem.h
|
||||
// libraries/entities/src
|
||||
//
|
||||
// Created by Seth Alves on 5/11/15.
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_LineEntityItem_h
|
||||
#define hifi_LineEntityItem_h
|
||||
|
||||
#include "EntityItem.h"
|
||||
|
||||
class LineEntityItem : public EntityItem {
|
||||
public:
|
||||
static EntityItem* factory(const EntityItemID& entityID, const EntityItemProperties& properties);
|
||||
|
||||
LineEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties);
|
||||
|
||||
ALLOW_INSTANTIATION // This class can be instantiated
|
||||
|
||||
// methods for getting/setting all properties of an entity
|
||||
virtual EntityItemProperties getProperties() const;
|
||||
virtual bool setProperties(const EntityItemProperties& properties);
|
||||
|
||||
// TODO: eventually only include properties changed since the params.lastViewFrustumSent time
|
||||
virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const;
|
||||
|
||||
virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params,
|
||||
EntityTreeElementExtraEncodeData* modelTreeElementExtraEncodeData,
|
||||
EntityPropertyFlags& requestedProperties,
|
||||
EntityPropertyFlags& propertyFlags,
|
||||
EntityPropertyFlags& propertiesDidntFit,
|
||||
int& propertyCount,
|
||||
OctreeElement::AppendState& appendState) const;
|
||||
|
||||
virtual int readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead,
|
||||
ReadBitstreamToTreeParams& args,
|
||||
EntityPropertyFlags& propertyFlags, bool overwriteLocalData);
|
||||
|
||||
const rgbColor& getColor() const { return _color; }
|
||||
xColor getXColor() const { xColor color = { _color[RED_INDEX], _color[GREEN_INDEX], _color[BLUE_INDEX] }; return color; }
|
||||
|
||||
void setColor(const rgbColor& value) { memcpy(_color, value, sizeof(_color)); }
|
||||
void setColor(const xColor& value) {
|
||||
_color[RED_INDEX] = value.red;
|
||||
_color[GREEN_INDEX] = value.green;
|
||||
_color[BLUE_INDEX] = value.blue;
|
||||
}
|
||||
|
||||
virtual ShapeType getShapeType() const { return SHAPE_TYPE_LINE; }
|
||||
|
||||
virtual void debugDump() const;
|
||||
|
||||
protected:
|
||||
rgbColor _color;
|
||||
};
|
||||
|
||||
#endif // hifi_LineEntityItem_h
|
|
@ -43,11 +43,11 @@ bool AddressManager::isConnected() {
|
|||
|
||||
const QUrl AddressManager::currentAddress() const {
|
||||
QUrl hifiURL;
|
||||
|
||||
|
||||
hifiURL.setScheme(HIFI_URL_SCHEME);
|
||||
hifiURL.setHost(_rootPlaceName);
|
||||
hifiURL.setPath(currentPath());
|
||||
|
||||
|
||||
return hifiURL;
|
||||
}
|
||||
|
||||
|
@ -64,10 +64,10 @@ void AddressManager::storeCurrentAddress() {
|
|||
}
|
||||
|
||||
const QString AddressManager::currentPath(bool withOrientation) const {
|
||||
|
||||
|
||||
if (_positionGetter) {
|
||||
QString pathString = "/" + createByteArray(_positionGetter());
|
||||
|
||||
|
||||
if (withOrientation) {
|
||||
if (_orientationGetter) {
|
||||
QString orientationString = createByteArray(_orientationGetter());
|
||||
|
@ -76,9 +76,9 @@ const QString AddressManager::currentPath(bool withOrientation) const {
|
|||
qCDebug(networking) << "Cannot add orientation to path without a getter for position."
|
||||
<< "Call AddressManager::setOrientationGetter to pass a function that will return a glm::quat";
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
return pathString;
|
||||
} else {
|
||||
qCDebug(networking) << "Cannot create address path without a getter for position."
|
||||
|
@ -90,29 +90,29 @@ const QString AddressManager::currentPath(bool withOrientation) const {
|
|||
const JSONCallbackParameters& AddressManager::apiCallbackParameters() {
|
||||
static bool hasSetupParameters = false;
|
||||
static JSONCallbackParameters callbackParams;
|
||||
|
||||
|
||||
if (!hasSetupParameters) {
|
||||
callbackParams.jsonCallbackReceiver = this;
|
||||
callbackParams.jsonCallbackMethod = "handleAPIResponse";
|
||||
callbackParams.errorCallbackReceiver = this;
|
||||
callbackParams.errorCallbackMethod = "handleAPIError";
|
||||
}
|
||||
|
||||
|
||||
return callbackParams;
|
||||
}
|
||||
|
||||
bool AddressManager::handleUrl(const QUrl& lookupUrl) {
|
||||
if (lookupUrl.scheme() == HIFI_URL_SCHEME) {
|
||||
|
||||
|
||||
qCDebug(networking) << "Trying to go to URL" << lookupUrl.toString();
|
||||
|
||||
|
||||
// there are 4 possible lookup strings
|
||||
|
||||
|
||||
// 1. global place name (name of domain or place) - example: sanfrancisco
|
||||
// 2. user name (prepended with @) - example: @philip
|
||||
// 3. location string (posX,posY,posZ/eulerX,eulerY,eulerZ)
|
||||
// 4. domain network address (IP or dns resolvable hostname)
|
||||
|
||||
|
||||
// use our regex'ed helpers to figure out what we're supposed to do with this
|
||||
if (!handleUsername(lookupUrl.authority())) {
|
||||
// we're assuming this is either a network address or global place name
|
||||
|
@ -120,24 +120,23 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl) {
|
|||
if (handleNetworkAddress(lookupUrl.host()
|
||||
+ (lookupUrl.port() == -1 ? "" : ":" + QString::number(lookupUrl.port())))) {
|
||||
// we may have a path that defines a relative viewpoint - if so we should jump to that now
|
||||
handleRelativeViewpoint(lookupUrl.path());
|
||||
handlePath(lookupUrl.path());
|
||||
} else {
|
||||
// wasn't an address - lookup the place name
|
||||
// we may have a path that defines a relative viewpoint - pass that through the lookup so we can go to it after
|
||||
attemptPlaceNameLookup(lookupUrl.host(), lookupUrl.path());
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
} else if (lookupUrl.toString().startsWith('/')) {
|
||||
qCDebug(networking) << "Going to relative path" << lookupUrl.path();
|
||||
|
||||
|
||||
// if this is a relative path then handle it as a relative viewpoint
|
||||
handleRelativeViewpoint(lookupUrl.path());
|
||||
handlePath(lookupUrl.path());
|
||||
emit lookupResultsFinished();
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -146,16 +145,16 @@ void AddressManager::handleLookupString(const QString& lookupString) {
|
|||
// make this a valid hifi URL and handle it off to handleUrl
|
||||
QString sanitizedString = lookupString.trimmed();
|
||||
QUrl lookupURL;
|
||||
|
||||
|
||||
if (!lookupString.startsWith('/')) {
|
||||
const QRegExp HIFI_SCHEME_REGEX = QRegExp(HIFI_URL_SCHEME + ":\\/{1,2}", Qt::CaseInsensitive);
|
||||
sanitizedString = sanitizedString.remove(HIFI_SCHEME_REGEX);
|
||||
|
||||
|
||||
lookupURL = QUrl(HIFI_URL_SCHEME + "://" + sanitizedString);
|
||||
} else {
|
||||
lookupURL = QUrl(lookupString);
|
||||
}
|
||||
|
||||
|
||||
handleUrl(lookupURL);
|
||||
}
|
||||
}
|
||||
|
@ -163,101 +162,103 @@ void AddressManager::handleLookupString(const QString& lookupString) {
|
|||
void AddressManager::handleAPIResponse(QNetworkReply& requestReply) {
|
||||
QJsonObject responseObject = QJsonDocument::fromJson(requestReply.readAll()).object();
|
||||
QJsonObject dataObject = responseObject["data"].toObject();
|
||||
|
||||
|
||||
goToAddressFromObject(dataObject.toVariantMap(), requestReply);
|
||||
|
||||
|
||||
emit lookupResultsFinished();
|
||||
}
|
||||
|
||||
const char OVERRIDE_PATH_KEY[] = "override_path";
|
||||
|
||||
void AddressManager::goToAddressFromObject(const QVariantMap& dataObject, const QNetworkReply& reply) {
|
||||
|
||||
|
||||
const QString DATA_OBJECT_PLACE_KEY = "place";
|
||||
const QString DATA_OBJECT_USER_LOCATION_KEY = "location";
|
||||
|
||||
|
||||
QVariantMap locationMap;
|
||||
if (dataObject.contains(DATA_OBJECT_PLACE_KEY)) {
|
||||
locationMap = dataObject[DATA_OBJECT_PLACE_KEY].toMap();
|
||||
} else {
|
||||
locationMap = dataObject[DATA_OBJECT_USER_LOCATION_KEY].toMap();
|
||||
}
|
||||
|
||||
|
||||
if (!locationMap.isEmpty()) {
|
||||
const QString LOCATION_API_ROOT_KEY = "root";
|
||||
const QString LOCATION_API_DOMAIN_KEY = "domain";
|
||||
const QString LOCATION_API_ONLINE_KEY = "online";
|
||||
|
||||
|
||||
if (!locationMap.contains(LOCATION_API_ONLINE_KEY)
|
||||
|| locationMap[LOCATION_API_ONLINE_KEY].toBool()) {
|
||||
|
||||
|
||||
QVariantMap rootMap = locationMap[LOCATION_API_ROOT_KEY].toMap();
|
||||
if (rootMap.isEmpty()) {
|
||||
rootMap = locationMap;
|
||||
}
|
||||
|
||||
|
||||
QVariantMap domainObject = rootMap[LOCATION_API_DOMAIN_KEY].toMap();
|
||||
|
||||
|
||||
if (!domainObject.isEmpty()) {
|
||||
const QString DOMAIN_NETWORK_ADDRESS_KEY = "network_address";
|
||||
const QString DOMAIN_NETWORK_PORT_KEY = "network_port";
|
||||
const QString DOMAIN_ICE_SERVER_ADDRESS_KEY = "ice_server_address";
|
||||
|
||||
|
||||
if (domainObject.contains(DOMAIN_NETWORK_ADDRESS_KEY)) {
|
||||
QString domainHostname = domainObject[DOMAIN_NETWORK_ADDRESS_KEY].toString();
|
||||
|
||||
|
||||
quint16 domainPort = domainObject.contains(DOMAIN_NETWORK_PORT_KEY)
|
||||
? domainObject[DOMAIN_NETWORK_PORT_KEY].toUInt()
|
||||
: DEFAULT_DOMAIN_SERVER_PORT;
|
||||
|
||||
|
||||
qCDebug(networking) << "Possible domain change required to connect to" << domainHostname
|
||||
<< "on" << domainPort;
|
||||
emit possibleDomainChangeRequired(domainHostname, domainPort);
|
||||
} else {
|
||||
QString iceServerAddress = domainObject[DOMAIN_ICE_SERVER_ADDRESS_KEY].toString();
|
||||
|
||||
|
||||
const QString DOMAIN_ID_KEY = "id";
|
||||
QString domainIDString = domainObject[DOMAIN_ID_KEY].toString();
|
||||
QUuid domainID(domainIDString);
|
||||
|
||||
|
||||
qCDebug(networking) << "Possible domain change required to connect to domain with ID" << domainID
|
||||
<< "via ice-server at" << iceServerAddress;
|
||||
|
||||
|
||||
emit possibleDomainChangeRequiredViaICEForID(iceServerAddress, domainID);
|
||||
}
|
||||
|
||||
|
||||
// set our current root place id to the ID that came back
|
||||
const QString PLACE_ID_KEY = "id";
|
||||
_rootPlaceID = rootMap[PLACE_ID_KEY].toUuid();
|
||||
|
||||
|
||||
// set our current root place name to the name that came back
|
||||
const QString PLACE_NAME_KEY = "name";
|
||||
QString newRootPlaceName = rootMap[PLACE_NAME_KEY].toString();
|
||||
setRootPlaceName(newRootPlaceName);
|
||||
|
||||
|
||||
// check if we had a path to override the path returned
|
||||
QString overridePath = reply.property(OVERRIDE_PATH_KEY).toString();
|
||||
|
||||
|
||||
if (!overridePath.isEmpty()) {
|
||||
if (!handleRelativeViewpoint(overridePath)){
|
||||
qCDebug(networking) << "User entered path could not be handled as a viewpoint - " << overridePath;
|
||||
}
|
||||
handlePath(overridePath);
|
||||
} else {
|
||||
// take the path that came back
|
||||
const QString PLACE_PATH_KEY = "path";
|
||||
QString returnedPath = locationMap[PLACE_PATH_KEY].toString();
|
||||
|
||||
|
||||
bool shouldFaceViewpoint = locationMap.contains(LOCATION_API_ONLINE_KEY);
|
||||
|
||||
|
||||
if (!returnedPath.isEmpty()) {
|
||||
// try to parse this returned path as a viewpoint, that's the only thing it could be for now
|
||||
if (!handleRelativeViewpoint(returnedPath, shouldFaceViewpoint)) {
|
||||
qCDebug(networking) << "Received a location path that was could not be handled as a viewpoint -" << returnedPath;
|
||||
if (!handleViewpoint(returnedPath, shouldFaceViewpoint)) {
|
||||
qCDebug(networking) << "Received a location path that was could not be handled as a viewpoint -"
|
||||
<< returnedPath;
|
||||
}
|
||||
} else {
|
||||
// we didn't override the path or get one back - ask the DS for the viewpoint of its index path
|
||||
// which we will jump to if it exists
|
||||
emit pathChangeRequired(INDEX_PATH);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
} else {
|
||||
qCDebug(networking) << "Received an address manager API response with no domain key. Cannot parse.";
|
||||
qCDebug(networking) << locationMap;
|
||||
|
@ -274,7 +275,7 @@ void AddressManager::goToAddressFromObject(const QVariantMap& dataObject, const
|
|||
|
||||
void AddressManager::handleAPIError(QNetworkReply& errorReply) {
|
||||
qCDebug(networking) << "AddressManager API error -" << errorReply.error() << "-" << errorReply.errorString();
|
||||
|
||||
|
||||
if (errorReply.error() == QNetworkReply::ContentNotFoundError) {
|
||||
emit lookupResultIsNotFound();
|
||||
}
|
||||
|
@ -286,12 +287,12 @@ const QString GET_PLACE = "/api/v1/places/%1";
|
|||
void AddressManager::attemptPlaceNameLookup(const QString& lookupString, const QString& overridePath) {
|
||||
// assume this is a place name and see if we can get any info on it
|
||||
QString placeName = QUrl::toPercentEncoding(lookupString);
|
||||
|
||||
|
||||
QVariantMap requestParams;
|
||||
if (!overridePath.isEmpty()) {
|
||||
requestParams.insert(OVERRIDE_PATH_KEY, overridePath);
|
||||
}
|
||||
|
||||
|
||||
AccountManager::getInstance().sendRequest(GET_PLACE.arg(placeName),
|
||||
AccountManagerAuth::None,
|
||||
QNetworkAccessManager::GetOperation,
|
||||
|
@ -302,47 +303,55 @@ void AddressManager::attemptPlaceNameLookup(const QString& lookupString, const Q
|
|||
bool AddressManager::handleNetworkAddress(const QString& lookupString) {
|
||||
const QString IP_ADDRESS_REGEX_STRING = "^((?:(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}"
|
||||
"(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))(?::(\\d{1,5}))?$";
|
||||
|
||||
|
||||
const QString HOSTNAME_REGEX_STRING = "^((?:[A-Z0-9]|[A-Z0-9][A-Z0-9\\-]{0,61}[A-Z0-9])"
|
||||
"(?:\\.(?:[A-Z0-9]|[A-Z0-9][A-Z0-9\\-]{0,61}[A-Z0-9]))+|localhost)(?::(\\d{1,5}))?$";
|
||||
|
||||
|
||||
QRegExp ipAddressRegex(IP_ADDRESS_REGEX_STRING);
|
||||
|
||||
|
||||
if (ipAddressRegex.indexIn(lookupString) != -1) {
|
||||
QString domainIPString = ipAddressRegex.cap(1);
|
||||
|
||||
|
||||
qint16 domainPort = DEFAULT_DOMAIN_SERVER_PORT;
|
||||
if (!ipAddressRegex.cap(2).isEmpty()) {
|
||||
domainPort = (qint16) ipAddressRegex.cap(2).toInt();
|
||||
}
|
||||
|
||||
|
||||
emit lookupResultsFinished();
|
||||
setDomainInfo(domainIPString, domainPort);
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
QRegExp hostnameRegex(HOSTNAME_REGEX_STRING, Qt::CaseInsensitive);
|
||||
|
||||
|
||||
if (hostnameRegex.indexIn(lookupString) != -1) {
|
||||
QString domainHostname = hostnameRegex.cap(1);
|
||||
|
||||
|
||||
quint16 domainPort = DEFAULT_DOMAIN_SERVER_PORT;
|
||||
|
||||
|
||||
if (!hostnameRegex.cap(2).isEmpty()) {
|
||||
domainPort = (qint16) hostnameRegex.cap(2).toInt();
|
||||
}
|
||||
|
||||
|
||||
emit lookupResultsFinished();
|
||||
setDomainInfo(domainHostname, domainPort);
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AddressManager::handleRelativeViewpoint(const QString& lookupString, bool shouldFace) {
|
||||
void AddressManager::handlePath(const QString& path) {
|
||||
if (!handleViewpoint(path)) {
|
||||
qCDebug(networking) << "User entered path could not be handled as a viewpoint - " << path <<
|
||||
"- wll attempt to ask domain-server to resolve.";
|
||||
emit pathChangeRequired(path);
|
||||
}
|
||||
}
|
||||
|
||||
bool AddressManager::handleViewpoint(const QString& viewpointString, bool shouldFace) {
|
||||
const QString FLOAT_REGEX_STRING = "([-+]?[0-9]*\\.?[0-9]+(?:[eE][-+]?[0-9]+)?)";
|
||||
const QString SPACED_COMMA_REGEX_STRING = "\\s*,\\s*";
|
||||
const QString POSITION_REGEX_STRING = QString("\\/") + FLOAT_REGEX_STRING + SPACED_COMMA_REGEX_STRING +
|
||||
|
@ -350,29 +359,29 @@ bool AddressManager::handleRelativeViewpoint(const QString& lookupString, bool s
|
|||
const QString QUAT_REGEX_STRING = QString("\\/") + FLOAT_REGEX_STRING + SPACED_COMMA_REGEX_STRING +
|
||||
FLOAT_REGEX_STRING + SPACED_COMMA_REGEX_STRING + FLOAT_REGEX_STRING + SPACED_COMMA_REGEX_STRING +
|
||||
FLOAT_REGEX_STRING + "\\s*$";
|
||||
|
||||
|
||||
QRegExp positionRegex(POSITION_REGEX_STRING);
|
||||
|
||||
if (positionRegex.indexIn(lookupString) != -1) {
|
||||
|
||||
if (positionRegex.indexIn(viewpointString) != -1) {
|
||||
// we have at least a position, so emit our signal to say we need to change position
|
||||
glm::vec3 newPosition(positionRegex.cap(1).toFloat(),
|
||||
positionRegex.cap(2).toFloat(),
|
||||
positionRegex.cap(3).toFloat());
|
||||
|
||||
|
||||
if (!isNaN(newPosition.x) && !isNaN(newPosition.y) && !isNaN(newPosition.z)) {
|
||||
glm::quat newOrientation;
|
||||
|
||||
|
||||
QRegExp orientationRegex(QUAT_REGEX_STRING);
|
||||
|
||||
|
||||
// we may also have an orientation
|
||||
if (lookupString[positionRegex.matchedLength() - 1] == QChar('/')
|
||||
&& orientationRegex.indexIn(lookupString, positionRegex.matchedLength() - 1) != -1) {
|
||||
|
||||
if (viewpointString[positionRegex.matchedLength() - 1] == QChar('/')
|
||||
&& orientationRegex.indexIn(viewpointString, positionRegex.matchedLength() - 1) != -1) {
|
||||
|
||||
glm::quat newOrientation = glm::normalize(glm::quat(orientationRegex.cap(4).toFloat(),
|
||||
orientationRegex.cap(1).toFloat(),
|
||||
orientationRegex.cap(2).toFloat(),
|
||||
orientationRegex.cap(3).toFloat()));
|
||||
|
||||
|
||||
if (!isNaN(newOrientation.x) && !isNaN(newOrientation.y) && !isNaN(newOrientation.z)
|
||||
&& !isNaN(newOrientation.w)) {
|
||||
emit locationChangeRequired(newPosition, true, newOrientation, shouldFace);
|
||||
|
@ -381,31 +390,31 @@ bool AddressManager::handleRelativeViewpoint(const QString& lookupString, bool s
|
|||
qCDebug(networking) << "Orientation parsed from lookup string is invalid. Will not use for location change.";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
emit locationChangeRequired(newPosition, false, newOrientation, shouldFace);
|
||||
|
||||
|
||||
} else {
|
||||
qCDebug(networking) << "Could not jump to position from lookup string because it has an invalid value.";
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const QString GET_USER_LOCATION = "/api/v1/users/%1/location";
|
||||
|
||||
bool AddressManager::handleUsername(const QString& lookupString) {
|
||||
const QString USERNAME_REGEX_STRING = "^@(\\S+)";
|
||||
|
||||
|
||||
QRegExp usernameRegex(USERNAME_REGEX_STRING);
|
||||
|
||||
|
||||
if (usernameRegex.indexIn(lookupString) != -1) {
|
||||
goToUser(usernameRegex.cap(1));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -420,9 +429,9 @@ void AddressManager::setRootPlaceName(const QString& rootPlaceName) {
|
|||
void AddressManager::setDomainInfo(const QString& hostname, quint16 port) {
|
||||
_rootPlaceName = hostname;
|
||||
_rootPlaceID = QUuid();
|
||||
|
||||
|
||||
qCDebug(networking) << "Possible domain change required to connect to domain at" << hostname << "on" << port;
|
||||
|
||||
|
||||
emit possibleDomainChangeRequired(hostname, port);
|
||||
}
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
|
||||
const QString HIFI_URL_SCHEME = "hifi";
|
||||
const QString DEFAULT_HIFI_ADDRESS = "hifi://entry";
|
||||
const QString INDEX_PATH = "/";
|
||||
|
||||
typedef const glm::vec3& (*PositionGetter)();
|
||||
typedef glm::quat (*OrientationGetter)();
|
||||
|
@ -39,32 +40,34 @@ class AddressManager : public QObject, public Dependency {
|
|||
public:
|
||||
bool isConnected();
|
||||
const QString& getProtocol() { return HIFI_URL_SCHEME; };
|
||||
|
||||
|
||||
const QUrl currentAddress() const;
|
||||
const QString currentPath(bool withOrientation = true) const;
|
||||
|
||||
|
||||
const QUuid& getRootPlaceID() const { return _rootPlaceID; }
|
||||
|
||||
|
||||
const QString& getRootPlaceName() const { return _rootPlaceName; }
|
||||
void setRootPlaceName(const QString& rootPlaceName);
|
||||
|
||||
|
||||
void attemptPlaceNameLookup(const QString& lookupString, const QString& overridePath = QString());
|
||||
|
||||
|
||||
void setPositionGetter(PositionGetter positionGetter) { _positionGetter = positionGetter; }
|
||||
void setOrientationGetter(OrientationGetter orientationGetter) { _orientationGetter = orientationGetter; }
|
||||
|
||||
|
||||
void loadSettings(const QString& lookupString = QString());
|
||||
|
||||
|
||||
public slots:
|
||||
void handleLookupString(const QString& lookupString);
|
||||
|
||||
void goToUser(const QString& username);
|
||||
void goToAddressFromObject(const QVariantMap& addressMap, const QNetworkReply& reply);
|
||||
|
||||
bool goToViewpoint(const QString& viewpointString) { return handleViewpoint(viewpointString); }
|
||||
|
||||
void storeCurrentAddress();
|
||||
|
||||
|
||||
void copyAddress();
|
||||
void copyPath();
|
||||
|
||||
|
||||
signals:
|
||||
void lookupResultsFinished();
|
||||
void lookupResultIsOffline();
|
||||
|
@ -74,6 +77,7 @@ signals:
|
|||
void locationChangeRequired(const glm::vec3& newPosition,
|
||||
bool hasOrientationChange, const glm::quat& newOrientation,
|
||||
bool shouldFaceLocation);
|
||||
void pathChangeRequired(const QString& newPath);
|
||||
void rootPlaceNameChanged(const QString& newRootPlaceName);
|
||||
protected:
|
||||
AddressManager();
|
||||
|
@ -82,15 +86,16 @@ private slots:
|
|||
void handleAPIError(QNetworkReply& errorReply);
|
||||
private:
|
||||
void setDomainInfo(const QString& hostname, quint16 port);
|
||||
|
||||
|
||||
const JSONCallbackParameters& apiCallbackParameters();
|
||||
|
||||
|
||||
bool handleUrl(const QUrl& lookupUrl);
|
||||
|
||||
|
||||
bool handleNetworkAddress(const QString& lookupString);
|
||||
bool handleRelativeViewpoint(const QString& pathSubsection, bool shouldFace = false);
|
||||
void handlePath(const QString& path);
|
||||
bool handleViewpoint(const QString& viewpointString, bool shouldFace = false);
|
||||
bool handleUsername(const QString& lookupString);
|
||||
|
||||
|
||||
QString _rootPlaceName;
|
||||
QUuid _rootPlaceID;
|
||||
PositionGetter _positionGetter;
|
||||
|
|
|
@ -36,19 +36,19 @@ DomainHandler::DomainHandler(QObject* parent) :
|
|||
_settingsObject(),
|
||||
_failedSettingsRequests(0)
|
||||
{
|
||||
|
||||
|
||||
}
|
||||
|
||||
void DomainHandler::clearConnectionInfo() {
|
||||
_uuid = QUuid();
|
||||
|
||||
|
||||
_icePeer = NetworkPeer();
|
||||
|
||||
|
||||
if (requiresICE()) {
|
||||
// if we connected to this domain with ICE, re-set the socket so we reconnect through the ice-server
|
||||
_sockAddr.setAddress(QHostAddress::Null);
|
||||
_sockAddr.clear();
|
||||
}
|
||||
|
||||
|
||||
setIsConnected(false);
|
||||
}
|
||||
|
||||
|
@ -65,12 +65,15 @@ void DomainHandler::softReset() {
|
|||
|
||||
void DomainHandler::hardReset() {
|
||||
softReset();
|
||||
|
||||
|
||||
qCDebug(networking) << "Hard reset in NodeList DomainHandler.";
|
||||
_iceDomainID = QUuid();
|
||||
_iceServerSockAddr = HifiSockAddr();
|
||||
_hostname = QString();
|
||||
_sockAddr.setAddress(QHostAddress::Null);
|
||||
_sockAddr.clear();
|
||||
|
||||
// clear any pending path we may have wanted to ask the previous DS about
|
||||
_pendingPath.clear();
|
||||
}
|
||||
|
||||
void DomainHandler::setSockAddr(const HifiSockAddr& sockAddr, const QString& hostname) {
|
||||
|
@ -80,7 +83,7 @@ void DomainHandler::setSockAddr(const HifiSockAddr& sockAddr, const QString& hos
|
|||
// change the sockAddr
|
||||
_sockAddr = sockAddr;
|
||||
}
|
||||
|
||||
|
||||
// some callers may pass a hostname, this is not to be used for lookup but for DTLS certificate verification
|
||||
_hostname = hostname;
|
||||
}
|
||||
|
@ -93,29 +96,29 @@ void DomainHandler::setUUID(const QUuid& uuid) {
|
|||
}
|
||||
|
||||
void DomainHandler::setHostnameAndPort(const QString& hostname, quint16 port) {
|
||||
|
||||
|
||||
if (hostname != _hostname || _sockAddr.getPort() != port) {
|
||||
// re-set the domain info so that auth information is reloaded
|
||||
hardReset();
|
||||
|
||||
|
||||
if (hostname != _hostname) {
|
||||
// set the new hostname
|
||||
_hostname = hostname;
|
||||
|
||||
|
||||
qCDebug(networking) << "Updated domain hostname to" << _hostname;
|
||||
|
||||
|
||||
// re-set the sock addr to null and fire off a lookup of the IP address for this domain-server's hostname
|
||||
qCDebug(networking, "Looking up DS hostname %s.", _hostname.toLocal8Bit().constData());
|
||||
QHostInfo::lookupHost(_hostname, this, SLOT(completedHostnameLookup(const QHostInfo&)));
|
||||
|
||||
|
||||
UserActivityLogger::getInstance().changedDomain(_hostname);
|
||||
emit hostnameChanged(_hostname);
|
||||
}
|
||||
|
||||
|
||||
if (_sockAddr.getPort() != port) {
|
||||
qCDebug(networking) << "Updated domain port to" << port;
|
||||
}
|
||||
|
||||
|
||||
// grab the port by reading the string after the colon
|
||||
_sockAddr.setPort(port);
|
||||
}
|
||||
|
@ -125,16 +128,16 @@ void DomainHandler::setIceServerHostnameAndID(const QString& iceServerHostname,
|
|||
if (id != _uuid) {
|
||||
// re-set the domain info to connect to new domain
|
||||
hardReset();
|
||||
|
||||
|
||||
_iceDomainID = id;
|
||||
|
||||
|
||||
HifiSockAddr* replaceableSockAddr = &_iceServerSockAddr;
|
||||
replaceableSockAddr->~HifiSockAddr();
|
||||
replaceableSockAddr = new (replaceableSockAddr) HifiSockAddr(iceServerHostname, ICE_SERVER_DEFAULT_PORT);
|
||||
|
||||
|
||||
// refresh our ICE client UUID to something new
|
||||
_iceClientID = QUuid::createUuid();
|
||||
|
||||
|
||||
qCDebug(networking) << "ICE required to connect to domain via ice server at" << iceServerHostname;
|
||||
}
|
||||
}
|
||||
|
@ -142,23 +145,29 @@ void DomainHandler::setIceServerHostnameAndID(const QString& iceServerHostname,
|
|||
void DomainHandler::activateICELocalSocket() {
|
||||
_sockAddr = _icePeer.getLocalSocket();
|
||||
_hostname = _sockAddr.getAddress().toString();
|
||||
emit completedSocketDiscovery();
|
||||
}
|
||||
|
||||
void DomainHandler::activateICEPublicSocket() {
|
||||
_sockAddr = _icePeer.getPublicSocket();
|
||||
_hostname = _sockAddr.getAddress().toString();
|
||||
emit completedSocketDiscovery();
|
||||
}
|
||||
|
||||
void DomainHandler::completedHostnameLookup(const QHostInfo& hostInfo) {
|
||||
for (int i = 0; i < hostInfo.addresses().size(); i++) {
|
||||
if (hostInfo.addresses()[i].protocol() == QAbstractSocket::IPv4Protocol) {
|
||||
_sockAddr.setAddress(hostInfo.addresses()[i]);
|
||||
|
||||
qCDebug(networking, "DS at %s is at %s", _hostname.toLocal8Bit().constData(),
|
||||
_sockAddr.getAddress().toString().toLocal8Bit().constData());
|
||||
|
||||
emit completedSocketDiscovery();
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// if we got here then we failed to lookup the address
|
||||
qCDebug(networking, "Failed domain server lookup");
|
||||
}
|
||||
|
@ -166,10 +175,10 @@ void DomainHandler::completedHostnameLookup(const QHostInfo& hostInfo) {
|
|||
void DomainHandler::setIsConnected(bool isConnected) {
|
||||
if (_isConnected != isConnected) {
|
||||
_isConnected = isConnected;
|
||||
|
||||
|
||||
if (_isConnected) {
|
||||
emit connectedToDomain(_hostname);
|
||||
|
||||
|
||||
// we've connected to new domain - time to ask it for global settings
|
||||
requestDomainSettings();
|
||||
} else {
|
||||
|
@ -195,9 +204,9 @@ void DomainHandler::requestDomainSettings() {
|
|||
settingsJSONURL.setPath("/settings.json");
|
||||
Assignment::Type assignmentType = Assignment::typeForNodeType(DependencyManager::get<NodeList>()->getOwnerType());
|
||||
settingsJSONURL.setQuery(QString("type=%1").arg(assignmentType));
|
||||
|
||||
|
||||
qCDebug(networking) << "Requesting domain-server settings at" << settingsJSONURL.toString();
|
||||
|
||||
|
||||
QNetworkRequest settingsRequest(settingsJSONURL);
|
||||
settingsRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT);
|
||||
QNetworkReply* reply = NetworkAccessManager::getInstance().get(settingsRequest);
|
||||
|
@ -210,23 +219,23 @@ const int MAX_SETTINGS_REQUEST_FAILED_ATTEMPTS = 5;
|
|||
|
||||
void DomainHandler::settingsRequestFinished() {
|
||||
QNetworkReply* settingsReply = reinterpret_cast<QNetworkReply*>(sender());
|
||||
|
||||
|
||||
int replyCode = settingsReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
|
||||
|
||||
if (settingsReply->error() == QNetworkReply::NoError && replyCode != 301 && replyCode != 302) {
|
||||
// parse the JSON to a QJsonObject and save it
|
||||
_settingsObject = QJsonDocument::fromJson(settingsReply->readAll()).object();
|
||||
|
||||
|
||||
qCDebug(networking) << "Received domain settings.";
|
||||
emit settingsReceived(_settingsObject);
|
||||
|
||||
|
||||
// reset failed settings requests to 0, we got them
|
||||
_failedSettingsRequests = 0;
|
||||
} else {
|
||||
// error grabbing the settings - in some cases this means we are stuck
|
||||
// so we should retry until we get it
|
||||
qCDebug(networking) << "Error getting domain settings -" << settingsReply->errorString() << "- retrying";
|
||||
|
||||
|
||||
if (++_failedSettingsRequests >= MAX_SETTINGS_REQUEST_FAILED_ATTEMPTS) {
|
||||
qCDebug(networking) << "Failed to retreive domain-server settings" << MAX_SETTINGS_REQUEST_FAILED_ATTEMPTS
|
||||
<< "times. Re-setting connection to domain.";
|
||||
|
@ -235,7 +244,7 @@ void DomainHandler::settingsRequestFinished() {
|
|||
emit settingsReceiveFail();
|
||||
} else {
|
||||
requestDomainSettings();
|
||||
}
|
||||
}
|
||||
}
|
||||
settingsReply->deleteLater();
|
||||
}
|
||||
|
@ -243,30 +252,30 @@ void DomainHandler::settingsRequestFinished() {
|
|||
void DomainHandler::parseDTLSRequirementPacket(const QByteArray& dtlsRequirementPacket) {
|
||||
// figure out the port that the DS wants us to use for us to talk to them with DTLS
|
||||
int numBytesPacketHeader = numBytesForPacketHeader(dtlsRequirementPacket);
|
||||
|
||||
|
||||
unsigned short dtlsPort = 0;
|
||||
memcpy(&dtlsPort, dtlsRequirementPacket.data() + numBytesPacketHeader, sizeof(dtlsPort));
|
||||
|
||||
|
||||
qCDebug(networking) << "domain-server DTLS port changed to" << dtlsPort << "- Enabling DTLS.";
|
||||
|
||||
|
||||
_sockAddr.setPort(dtlsPort);
|
||||
|
||||
|
||||
// initializeDTLSSession();
|
||||
}
|
||||
|
||||
void DomainHandler::processICEResponsePacket(const QByteArray& icePacket) {
|
||||
QDataStream iceResponseStream(icePacket);
|
||||
iceResponseStream.skipRawData(numBytesForPacketHeader(icePacket));
|
||||
|
||||
|
||||
NetworkPeer packetPeer;
|
||||
iceResponseStream >> packetPeer;
|
||||
|
||||
|
||||
if (packetPeer.getUUID() != _iceDomainID) {
|
||||
qCDebug(networking) << "Received a network peer with ID that does not match current domain. Will not attempt connection.";
|
||||
} else {
|
||||
qCDebug(networking) << "Received network peer object for domain -" << packetPeer;
|
||||
_icePeer = packetPeer;
|
||||
|
||||
|
||||
emit requestICEConnectionAttempt();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,67 +31,79 @@ class DomainHandler : public QObject {
|
|||
Q_OBJECT
|
||||
public:
|
||||
DomainHandler(QObject* parent = 0);
|
||||
|
||||
|
||||
void clearConnectionInfo();
|
||||
void clearSettings();
|
||||
|
||||
|
||||
const QUuid& getUUID() const { return _uuid; }
|
||||
void setUUID(const QUuid& uuid);
|
||||
|
||||
|
||||
const QString& getHostname() const { return _hostname; }
|
||||
|
||||
|
||||
const QHostAddress& getIP() const { return _sockAddr.getAddress(); }
|
||||
void setIPToLocalhost() { _sockAddr.setAddress(QHostAddress(QHostAddress::LocalHost)); }
|
||||
|
||||
|
||||
const HifiSockAddr& getSockAddr() { return _sockAddr; }
|
||||
void setSockAddr(const HifiSockAddr& sockAddr, const QString& hostname);
|
||||
|
||||
|
||||
unsigned short getPort() const { return _sockAddr.getPort(); }
|
||||
void setPort(quint16 port) { _sockAddr.setPort(port); }
|
||||
|
||||
|
||||
const QUuid& getAssignmentUUID() const { return _assignmentUUID; }
|
||||
void setAssignmentUUID(const QUuid& assignmentUUID) { _assignmentUUID = assignmentUUID; }
|
||||
|
||||
|
||||
const QUuid& getICEDomainID() const { return _iceDomainID; }
|
||||
|
||||
|
||||
const QUuid& getICEClientID() const { return _iceClientID; }
|
||||
|
||||
|
||||
bool requiresICE() const { return !_iceServerSockAddr.isNull(); }
|
||||
const HifiSockAddr& getICEServerSockAddr() const { return _iceServerSockAddr; }
|
||||
NetworkPeer& getICEPeer() { return _icePeer; }
|
||||
void activateICELocalSocket();
|
||||
void activateICEPublicSocket();
|
||||
|
||||
|
||||
bool isConnected() const { return _isConnected; }
|
||||
void setIsConnected(bool isConnected);
|
||||
|
||||
|
||||
bool hasSettings() const { return !_settingsObject.isEmpty(); }
|
||||
void requestDomainSettings();
|
||||
const QJsonObject& getSettingsObject() const { return _settingsObject; }
|
||||
|
||||
|
||||
void parseDTLSRequirementPacket(const QByteArray& dtlsRequirementPacket);
|
||||
void processICEResponsePacket(const QByteArray& icePacket);
|
||||
|
||||
|
||||
void setPendingPath(const QString& pendingPath) { _pendingPath = pendingPath; }
|
||||
const QString& getPendingPath() { return _pendingPath; }
|
||||
void clearPendingPath() { _pendingPath.clear(); }
|
||||
|
||||
bool isSocketKnown() const { return !_sockAddr.getAddress().isNull(); }
|
||||
|
||||
void softReset();
|
||||
public slots:
|
||||
void setHostnameAndPort(const QString& hostname, quint16 port = DEFAULT_DOMAIN_SERVER_PORT);
|
||||
void setIceServerHostnameAndID(const QString& iceServerHostname, const QUuid& id);
|
||||
|
||||
|
||||
private slots:
|
||||
void completedHostnameLookup(const QHostInfo& hostInfo);
|
||||
void settingsRequestFinished();
|
||||
signals:
|
||||
void hostnameChanged(const QString& hostname);
|
||||
|
||||
// NOTE: the emission of completedSocketDiscovery does not mean a connection to DS is established
|
||||
// It means that, either from DNS lookup or ICE, we think we have a socket we can talk to DS on
|
||||
void completedSocketDiscovery();
|
||||
|
||||
void connectedToDomain(const QString& hostname);
|
||||
void disconnectedFromDomain();
|
||||
|
||||
void requestICEConnectionAttempt();
|
||||
|
||||
|
||||
void settingsReceived(const QJsonObject& domainSettingsObject);
|
||||
void settingsReceiveFail();
|
||||
|
||||
|
||||
private:
|
||||
void hardReset();
|
||||
|
||||
|
||||
QUuid _uuid;
|
||||
QString _hostname;
|
||||
HifiSockAddr _sockAddr;
|
||||
|
@ -103,6 +115,7 @@ private:
|
|||
bool _isConnected;
|
||||
QJsonObject _settingsObject;
|
||||
int _failedSettingsRequests;
|
||||
QString _pendingPath;
|
||||
};
|
||||
|
||||
#endif // hifi_DomainHandler_h
|
||||
|
|
|
@ -29,26 +29,27 @@ public:
|
|||
HifiSockAddr(const HifiSockAddr& otherSockAddr);
|
||||
HifiSockAddr(const QString& hostname, quint16 hostOrderPort, bool shouldBlockForLookup = false);
|
||||
HifiSockAddr(const sockaddr* sockaddr);
|
||||
|
||||
|
||||
bool isNull() const { return _address.isNull() && _port == 0; }
|
||||
void clear() { _address = QHostAddress::Null; _port = 0;}
|
||||
|
||||
HifiSockAddr& operator=(const HifiSockAddr& rhsSockAddr);
|
||||
void swap(HifiSockAddr& otherSockAddr);
|
||||
|
||||
|
||||
bool operator==(const HifiSockAddr& rhsSockAddr) const;
|
||||
bool operator!=(const HifiSockAddr& rhsSockAddr) const { return !(*this == rhsSockAddr); }
|
||||
|
||||
|
||||
const QHostAddress& getAddress() const { return _address; }
|
||||
QHostAddress* getAddressPointer() { return &_address; }
|
||||
void setAddress(const QHostAddress& address) { _address = address; }
|
||||
|
||||
|
||||
quint16 getPort() const { return _port; }
|
||||
quint16* getPortPointer() { return &_port; }
|
||||
void setPort(quint16 port) { _port = port; }
|
||||
|
||||
|
||||
static int packSockAddr(unsigned char* packetData, const HifiSockAddr& packSockAddr);
|
||||
static int unpackSockAddr(const unsigned char* packetData, HifiSockAddr& unpackDestSockAddr);
|
||||
|
||||
|
||||
friend QDebug operator<<(QDebug debug, const HifiSockAddr& sockAddr);
|
||||
friend QDataStream& operator<<(QDataStream& dataStream, const HifiSockAddr& sockAddr);
|
||||
friend QDataStream& operator>>(QDataStream& dataStream, HifiSockAddr& sockAddr);
|
||||
|
|
|
@ -46,23 +46,30 @@ NodeList::NodeList(char newOwnerType, unsigned short socketListenPort, unsigned
|
|||
firstCall = false;
|
||||
}
|
||||
auto addressManager = DependencyManager::get<AddressManager>();
|
||||
|
||||
|
||||
// handle domain change signals from AddressManager
|
||||
connect(addressManager.data(), &AddressManager::possibleDomainChangeRequired,
|
||||
&_domainHandler, &DomainHandler::setHostnameAndPort);
|
||||
|
||||
|
||||
connect(addressManager.data(), &AddressManager::possibleDomainChangeRequiredViaICEForID,
|
||||
&_domainHandler, &DomainHandler::setIceServerHostnameAndID);
|
||||
|
||||
|
||||
// handle a request for a path change from the AddressManager
|
||||
connect(addressManager.data(), &AddressManager::pathChangeRequired, this, &NodeList::handleDSPathQuery);
|
||||
|
||||
// in case we don't know how to talk to DS when a path change is requested
|
||||
// fire off any pending DS path query when we get socket information
|
||||
connect(&_domainHandler, &DomainHandler::completedSocketDiscovery, this, &NodeList::sendPendingDSPathQuery);
|
||||
|
||||
// clear our NodeList when the domain changes
|
||||
connect(&_domainHandler, &DomainHandler::disconnectedFromDomain, this, &NodeList::reset);
|
||||
|
||||
|
||||
// handle ICE signal from DS so connection is attempted immediately
|
||||
connect(&_domainHandler, &DomainHandler::requestICEConnectionAttempt, this, &NodeList::handleICEConnectionToDomainServer);
|
||||
|
||||
// clear out NodeList when login is finished
|
||||
connect(&AccountManager::getInstance(), &AccountManager::loginComplete , this, &NodeList::reset);
|
||||
|
||||
|
||||
// clear our NodeList when logout is requested
|
||||
connect(&AccountManager::getInstance(), &AccountManager::logoutComplete , this, &NodeList::reset);
|
||||
}
|
||||
|
@ -75,8 +82,8 @@ qint64 NodeList::sendStats(const QJsonObject& statsObject, const HifiSockAddr& d
|
|||
QStringList statsStringList = JSONBreakableMarshal::toStringList(statsObject, "");
|
||||
|
||||
int numBytesWritten = numBytesForPacketHeader;
|
||||
|
||||
// enumerate the resulting strings - pack them and send off packets once we hit MTU size
|
||||
|
||||
// enumerate the resulting strings - pack them and send off packets once we hit MTU size
|
||||
foreach(const QString& statsItem, statsStringList) {
|
||||
QByteArray utf8String = statsItem.toUtf8();
|
||||
utf8String.append('\0');
|
||||
|
@ -85,14 +92,14 @@ qint64 NodeList::sendStats(const QJsonObject& statsObject, const HifiSockAddr& d
|
|||
// send off the current packet since the next string will make us too big
|
||||
statsPacket.resize(numBytesWritten);
|
||||
writeUnverifiedDatagram(statsPacket, destination);
|
||||
|
||||
|
||||
// reset the number of bytes written to the size of our packet header
|
||||
numBytesWritten = numBytesForPacketHeader;
|
||||
}
|
||||
|
||||
|
||||
// write this string into the stats packet
|
||||
statsPacket.replace(numBytesWritten, utf8String.size(), utf8String);
|
||||
|
||||
|
||||
// keep track of the number of bytes we have written
|
||||
numBytesWritten += utf8String.size();
|
||||
}
|
||||
|
@ -102,7 +109,7 @@ qint64 NodeList::sendStats(const QJsonObject& statsObject, const HifiSockAddr& d
|
|||
statsPacket.resize(numBytesWritten);
|
||||
writeUnverifiedDatagram(statsPacket, destination);
|
||||
}
|
||||
|
||||
|
||||
// enumerate the resulting strings, breaking them into MTU sized packets
|
||||
return 0;
|
||||
}
|
||||
|
@ -114,26 +121,26 @@ qint64 NodeList::sendStatsToDomainServer(const QJsonObject& statsObject) {
|
|||
void NodeList::timePingReply(const QByteArray& packet, const SharedNodePointer& sendingNode) {
|
||||
QDataStream packetStream(packet);
|
||||
packetStream.skipRawData(numBytesForPacketHeader(packet));
|
||||
|
||||
|
||||
quint8 pingType;
|
||||
quint64 ourOriginalTime, othersReplyTime;
|
||||
|
||||
|
||||
packetStream >> pingType >> ourOriginalTime >> othersReplyTime;
|
||||
|
||||
|
||||
quint64 now = usecTimestampNow();
|
||||
int pingTime = now - ourOriginalTime;
|
||||
int oneWayFlightTime = pingTime / 2; // half of the ping is our one way flight
|
||||
|
||||
|
||||
// The other node's expected time should be our original time plus the one way flight time
|
||||
// anything other than that is clock skew
|
||||
quint64 othersExprectedReply = ourOriginalTime + oneWayFlightTime;
|
||||
int clockSkew = othersReplyTime - othersExprectedReply;
|
||||
|
||||
|
||||
sendingNode->setPingMs(pingTime / 1000);
|
||||
sendingNode->updateClockSkewUsec(clockSkew);
|
||||
|
||||
const bool wantDebug = false;
|
||||
|
||||
|
||||
if (wantDebug) {
|
||||
qCDebug(networking) << "PING_REPLY from node " << *sendingNode << "\n" <<
|
||||
" now: " << now << "\n" <<
|
||||
|
@ -172,7 +179,7 @@ void NodeList::processNodeData(const HifiSockAddr& senderSockAddr, const QByteAr
|
|||
matchingNode->setLastHeardMicrostamp(usecTimestampNow());
|
||||
QByteArray replyPacket = constructPingReplyPacket(packet);
|
||||
writeDatagram(replyPacket, matchingNode, senderSockAddr);
|
||||
|
||||
|
||||
// If we don't have a symmetric socket for this node and this socket doesn't match
|
||||
// what we have for public and local then set it as the symmetric.
|
||||
// This allows a server on a reachable port to communicate with nodes on symmetric NATs
|
||||
|
@ -182,7 +189,7 @@ void NodeList::processNodeData(const HifiSockAddr& senderSockAddr, const QByteAr
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
break;
|
||||
}
|
||||
case PacketTypePingReply: {
|
||||
|
@ -190,14 +197,14 @@ void NodeList::processNodeData(const HifiSockAddr& senderSockAddr, const QByteAr
|
|||
|
||||
if (sendingNode) {
|
||||
sendingNode->setLastHeardMicrostamp(usecTimestampNow());
|
||||
|
||||
|
||||
// activate the appropriate socket for this node, if not yet updated
|
||||
activateSocketFromNodeCommunication(packet, sendingNode);
|
||||
|
||||
|
||||
// set the ping time for this node for stat collection
|
||||
timePingReply(packet, sendingNode);
|
||||
}
|
||||
|
||||
|
||||
break;
|
||||
}
|
||||
case PacketTypeUnverifiedPing: {
|
||||
|
@ -208,7 +215,7 @@ void NodeList::processNodeData(const HifiSockAddr& senderSockAddr, const QByteAr
|
|||
}
|
||||
case PacketTypeUnverifiedPingReply: {
|
||||
qCDebug(networking) << "Received reply from domain-server on" << senderSockAddr;
|
||||
|
||||
|
||||
// for now we're unsafely assuming this came back from the domain
|
||||
if (senderSockAddr == _domainHandler.getICEPeer().getLocalSocket()) {
|
||||
qCDebug(networking) << "Connecting to domain using local socket";
|
||||
|
@ -226,6 +233,10 @@ void NodeList::processNodeData(const HifiSockAddr& senderSockAddr, const QByteAr
|
|||
processSTUNResponse(packet);
|
||||
break;
|
||||
}
|
||||
case PacketTypeDomainServerPathResponse: {
|
||||
handleDSPathQueryResponse(packet);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
LimitedNodeList::processNodeData(senderSockAddr, packet);
|
||||
break;
|
||||
|
@ -234,17 +245,17 @@ void NodeList::processNodeData(const HifiSockAddr& senderSockAddr, const QByteAr
|
|||
|
||||
void NodeList::reset() {
|
||||
LimitedNodeList::reset();
|
||||
|
||||
|
||||
_numNoReplyDomainCheckIns = 0;
|
||||
|
||||
// refresh the owner UUID to the NULL UUID
|
||||
setSessionUUID(QUuid());
|
||||
|
||||
|
||||
if (sender() != &_domainHandler) {
|
||||
// clear the domain connection information, unless they're the ones that asked us to reset
|
||||
_domainHandler.softReset();
|
||||
}
|
||||
|
||||
|
||||
// if we setup the DTLS socket, also disconnect from the DTLS socket readyRead() so it can handle handshaking
|
||||
if (_dtlsSocket) {
|
||||
disconnect(_dtlsSocket, 0, this, 0);
|
||||
|
@ -267,7 +278,7 @@ void NodeList::sendSTUNRequest() {
|
|||
if (!_hasCompletedInitialSTUNFailure) {
|
||||
qCDebug(networking) << "Sending intial stun request to" << STUN_SERVER_HOSTNAME;
|
||||
}
|
||||
|
||||
|
||||
LimitedNodeList::sendSTUNRequest();
|
||||
|
||||
_stunRequestsSinceSuccess++;
|
||||
|
@ -292,9 +303,9 @@ bool NodeList::processSTUNResponse(const QByteArray& packet) {
|
|||
if (LimitedNodeList::processSTUNResponse(packet)) {
|
||||
// reset the number of failed STUN requests since last success
|
||||
_stunRequestsSinceSuccess = 0;
|
||||
|
||||
|
||||
_hasCompletedInitialSTUNFailure = true;
|
||||
|
||||
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
|
@ -309,23 +320,23 @@ void NodeList::sendDomainServerCheckIn() {
|
|||
} else if (_domainHandler.getIP().isNull() && _domainHandler.requiresICE()) {
|
||||
handleICEConnectionToDomainServer();
|
||||
} else if (!_domainHandler.getIP().isNull()) {
|
||||
|
||||
|
||||
bool isUsingDTLS = false;
|
||||
|
||||
|
||||
PacketType domainPacketType = !_domainHandler.isConnected()
|
||||
? PacketTypeDomainConnectRequest : PacketTypeDomainListRequest;
|
||||
|
||||
|
||||
if (!_domainHandler.isConnected()) {
|
||||
qCDebug(networking) << "Sending connect request to domain-server at" << _domainHandler.getHostname();
|
||||
|
||||
|
||||
// is this our localhost domain-server?
|
||||
// if so we need to make sure we have an up-to-date local port in case it restarted
|
||||
|
||||
|
||||
if (_domainHandler.getSockAddr().getAddress() == QHostAddress::LocalHost
|
||||
|| _domainHandler.getHostname() == "localhost") {
|
||||
|
||||
|
||||
static QSharedMemory* localDSPortSharedMem = NULL;
|
||||
|
||||
|
||||
quint16 domainPort = DEFAULT_DOMAIN_SERVER_PORT;
|
||||
getLocalServerPortFromSharedMemory(DOMAIN_SERVER_LOCAL_PORT_SMEM_KEY,
|
||||
localDSPortSharedMem,
|
||||
|
@ -333,12 +344,12 @@ void NodeList::sendDomainServerCheckIn() {
|
|||
qCDebug(networking) << "Local domain-server port read from shared memory (or default) is" << domainPort;
|
||||
_domainHandler.setPort(domainPort);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
// construct the DS check in packet
|
||||
QUuid packetUUID = _sessionUUID;
|
||||
|
||||
|
||||
if (domainPacketType == PacketTypeDomainConnectRequest) {
|
||||
if (!_domainHandler.getAssignmentUUID().isNull()) {
|
||||
// this is a connect request and we're an assigned node
|
||||
|
@ -351,70 +362,166 @@ void NodeList::sendDomainServerCheckIn() {
|
|||
packetUUID = _domainHandler.getICEClientID();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
QByteArray domainServerPacket = byteArrayWithUUIDPopulatedHeader(domainPacketType, packetUUID);
|
||||
QDataStream packetStream(&domainServerPacket, QIODevice::Append);
|
||||
|
||||
|
||||
// pack our data to send to the domain-server
|
||||
packetStream << _ownerType << _publicSockAddr << _localSockAddr << _nodeTypesOfInterest.toList();
|
||||
|
||||
|
||||
|
||||
|
||||
// if this is a connect request, and we can present a username signature, send it along
|
||||
if (!_domainHandler.isConnected()) {
|
||||
DataServerAccountInfo& accountInfo = AccountManager::getInstance().getAccountInfo();
|
||||
packetStream << accountInfo.getUsername();
|
||||
|
||||
|
||||
const QByteArray& usernameSignature = AccountManager::getInstance().getAccountInfo().getUsernameSignature();
|
||||
|
||||
|
||||
if (!usernameSignature.isEmpty()) {
|
||||
qCDebug(networking) << "Including username signature in domain connect request.";
|
||||
packetStream << usernameSignature;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!isUsingDTLS) {
|
||||
writeUnverifiedDatagram(domainServerPacket, _domainHandler.getSockAddr());
|
||||
}
|
||||
|
||||
|
||||
const int NUM_DOMAIN_SERVER_CHECKINS_PER_STUN_REQUEST = 5;
|
||||
static unsigned int numDomainCheckins = 0;
|
||||
|
||||
|
||||
// send a STUN request every Nth domain server check in so we update our public socket, if required
|
||||
if (numDomainCheckins++ % NUM_DOMAIN_SERVER_CHECKINS_PER_STUN_REQUEST == 0) {
|
||||
sendSTUNRequest();
|
||||
}
|
||||
|
||||
|
||||
if (_numNoReplyDomainCheckIns >= MAX_SILENT_DOMAIN_SERVER_CHECK_INS) {
|
||||
// we haven't heard back from DS in MAX_SILENT_DOMAIN_SERVER_CHECK_INS
|
||||
// so emit our signal that says that
|
||||
emit limitOfSilentDomainCheckInsReached();
|
||||
}
|
||||
|
||||
|
||||
// increment the count of un-replied check-ins
|
||||
_numNoReplyDomainCheckIns++;
|
||||
}
|
||||
}
|
||||
|
||||
void NodeList::handleDSPathQuery(const QString& newPath) {
|
||||
if (_domainHandler.isSocketKnown()) {
|
||||
// if we have a DS socket we assume it will get this packet and send if off right away
|
||||
sendDSPathQuery(newPath);
|
||||
} else {
|
||||
// otherwise we make it pending so that it can be sent once a connection is established
|
||||
_domainHandler.setPendingPath(newPath);
|
||||
}
|
||||
}
|
||||
|
||||
void NodeList::sendPendingDSPathQuery() {
|
||||
|
||||
QString pendingPath = _domainHandler.getPendingPath();
|
||||
|
||||
if (!pendingPath.isEmpty()) {
|
||||
qCDebug(networking) << "Attemping to send pending query to DS for path" << pendingPath;
|
||||
|
||||
// this is a slot triggered if we just established a network link with a DS and want to send a path query
|
||||
sendDSPathQuery(_domainHandler.getPendingPath());
|
||||
|
||||
// clear whatever the pending path was
|
||||
_domainHandler.clearPendingPath();
|
||||
}
|
||||
}
|
||||
|
||||
void NodeList::sendDSPathQuery(const QString& newPath) {
|
||||
// only send a path query if we know who our DS is or is going to be
|
||||
if (_domainHandler.isSocketKnown()) {
|
||||
// construct the path query packet
|
||||
QByteArray pathQueryPacket = byteArrayWithPopulatedHeader(PacketTypeDomainServerPathQuery);
|
||||
|
||||
// get the UTF8 representation of path query
|
||||
QByteArray pathQueryUTF8 = newPath.toUtf8();
|
||||
|
||||
// get the size of the UTF8 representation of the desired path
|
||||
quint16 numPathBytes = pathQueryUTF8.size();
|
||||
|
||||
if (pathQueryPacket.size() + numPathBytes + sizeof(numPathBytes) < MAX_PACKET_SIZE) {
|
||||
// append the size of the path to the query packet
|
||||
pathQueryPacket.append(reinterpret_cast<char*>(&numPathBytes), sizeof(numPathBytes));
|
||||
|
||||
// append the path itself to the query packet
|
||||
pathQueryPacket.append(pathQueryUTF8);
|
||||
|
||||
qCDebug(networking) << "Sending a path query packet for path" << newPath << "to domain-server at"
|
||||
<< _domainHandler.getSockAddr();
|
||||
|
||||
// send off the path query
|
||||
writeUnverifiedDatagram(pathQueryPacket, _domainHandler.getSockAddr());
|
||||
} else {
|
||||
qCDebug(networking) << "Path" << newPath << "would make PacketTypeDomainServerPathQuery packet > MAX_PACKET_SIZE." <<
|
||||
"Will not send query.";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NodeList::handleDSPathQueryResponse(const QByteArray& packet) {
|
||||
// This is a response to a path query we theoretically made.
|
||||
// In the future we may want to check that this was actually from our DS and for a query we actually made.
|
||||
|
||||
int numHeaderBytes = numBytesForPacketHeaderGivenPacketType(PacketTypeDomainServerPathResponse);
|
||||
const char* startPosition = packet.data() + numHeaderBytes;
|
||||
const char* currentPosition = startPosition;
|
||||
|
||||
// figure out how many bytes the path query is
|
||||
qint16 numPathBytes;
|
||||
memcpy(&numPathBytes, currentPosition, sizeof(numPathBytes));
|
||||
currentPosition += sizeof(numPathBytes);
|
||||
|
||||
// make sure it is safe to pull the path
|
||||
if (numPathBytes <= packet.size() - numHeaderBytes - (currentPosition - startPosition)) {
|
||||
// pull the path from the packet
|
||||
QString pathQuery = QString::fromUtf8(currentPosition, numPathBytes);
|
||||
currentPosition += numPathBytes;
|
||||
|
||||
// figure out how many bytes the viewpoint is
|
||||
qint16 numViewpointBytes;
|
||||
memcpy(&numViewpointBytes, currentPosition, sizeof(numViewpointBytes));
|
||||
currentPosition += sizeof(numViewpointBytes);
|
||||
|
||||
// make sure it is safe to pull the viewpoint
|
||||
if (numViewpointBytes <= packet.size() - numHeaderBytes - (currentPosition - startPosition)) {
|
||||
// pull the viewpoint from the packet
|
||||
QString viewpoint = QString::fromUtf8(currentPosition, numViewpointBytes);
|
||||
|
||||
// Hand it off to the AddressManager so it can handle it as a relative viewpoint
|
||||
if (DependencyManager::get<AddressManager>()->goToViewpoint(viewpoint)) {
|
||||
qCDebug(networking) << "Going to viewpoint" << viewpoint << "which was the lookup result for path" << pathQuery;
|
||||
} else {
|
||||
qCDebug(networking) << "Could not go to viewpoint" << viewpoint
|
||||
<< "which was the lookup result for path" << pathQuery;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NodeList::handleICEConnectionToDomainServer() {
|
||||
if (_domainHandler.getICEPeer().isNull()
|
||||
|| _domainHandler.getICEPeer().getConnectionAttempts() >= MAX_ICE_CONNECTION_ATTEMPTS) {
|
||||
|
||||
|
||||
_domainHandler.getICEPeer().resetConnectionAttemps();
|
||||
|
||||
|
||||
LimitedNodeList::sendHeartbeatToIceServer(_domainHandler.getICEServerSockAddr(),
|
||||
_domainHandler.getICEClientID(),
|
||||
_domainHandler.getICEDomainID());
|
||||
} else {
|
||||
qCDebug(networking) << "Sending ping packets to establish connectivity with domain-server with ID"
|
||||
<< uuidStringWithoutCurlyBraces(_domainHandler.getICEDomainID());
|
||||
|
||||
|
||||
// send the ping packet to the local and public sockets for this node
|
||||
QByteArray localPingPacket = constructPingPacket(PingType::Local, false, _domainHandler.getICEClientID());
|
||||
writeUnverifiedDatagram(localPingPacket, _domainHandler.getICEPeer().getLocalSocket());
|
||||
|
||||
|
||||
QByteArray publicPingPacket = constructPingPacket(PingType::Public, false, _domainHandler.getICEClientID());
|
||||
writeUnverifiedDatagram(publicPingPacket, _domainHandler.getICEPeer().getPublicSocket());
|
||||
|
||||
|
||||
_domainHandler.getICEPeer().incrementConnectionAttempts();
|
||||
}
|
||||
}
|
||||
|
@ -422,18 +529,18 @@ void NodeList::handleICEConnectionToDomainServer() {
|
|||
int NodeList::processDomainServerList(const QByteArray& packet) {
|
||||
// this is a packet from the domain server, reset the count of un-replied check-ins
|
||||
_numNoReplyDomainCheckIns = 0;
|
||||
|
||||
|
||||
// if this was the first domain-server list from this domain, we've now connected
|
||||
if (!_domainHandler.isConnected()) {
|
||||
_domainHandler.setUUID(uuidFromPacketHeader(packet));
|
||||
_domainHandler.setIsConnected(true);
|
||||
}
|
||||
|
||||
|
||||
int readNodes = 0;
|
||||
|
||||
QDataStream packetStream(packet);
|
||||
packetStream.skipRawData(numBytesForPacketHeader(packet));
|
||||
|
||||
|
||||
// pull our owner UUID from the packet, it's always the first thing
|
||||
QUuid newUUID;
|
||||
packetStream >> newUUID;
|
||||
|
@ -446,7 +553,7 @@ int NodeList::processDomainServerList(const QByteArray& packet) {
|
|||
bool thisNodeCanRez;
|
||||
packetStream >> thisNodeCanRez;
|
||||
setThisNodeCanRez(thisNodeCanRez);
|
||||
|
||||
|
||||
// pull each node in the packet
|
||||
while(packetStream.device()->pos() < packet.size()) {
|
||||
// setup variables to read into from QDataStream
|
||||
|
@ -466,11 +573,11 @@ int NodeList::processDomainServerList(const QByteArray& packet) {
|
|||
|
||||
SharedNodePointer node = addOrUpdateNode(nodeUUID, nodeType, nodePublicSocket,
|
||||
nodeLocalSocket, canAdjustLocks, canRez);
|
||||
|
||||
|
||||
packetStream >> connectionUUID;
|
||||
node->setConnectionSecret(connectionUUID);
|
||||
}
|
||||
|
||||
|
||||
// ping inactive nodes in conjunction with receipt of list from domain-server
|
||||
// this makes it happen every second and also pings any newly added nodes
|
||||
pingInactiveNodes();
|
||||
|
@ -479,25 +586,25 @@ int NodeList::processDomainServerList(const QByteArray& packet) {
|
|||
}
|
||||
|
||||
void NodeList::sendAssignment(Assignment& assignment) {
|
||||
|
||||
|
||||
PacketType assignmentPacketType = assignment.getCommand() == Assignment::CreateCommand
|
||||
? PacketTypeCreateAssignment
|
||||
: PacketTypeRequestAssignment;
|
||||
|
||||
|
||||
QByteArray packet = byteArrayWithPopulatedHeader(assignmentPacketType);
|
||||
QDataStream packetStream(&packet, QIODevice::Append);
|
||||
|
||||
|
||||
packetStream << assignment;
|
||||
|
||||
_nodeSocket.writeDatagram(packet, _assignmentServerSocket.getAddress(), _assignmentServerSocket.getPort());
|
||||
}
|
||||
|
||||
void NodeList::pingPunchForInactiveNode(const SharedNodePointer& node) {
|
||||
|
||||
|
||||
// send the ping packet to the local and public sockets for this node
|
||||
QByteArray localPingPacket = constructPingPacket(PingType::Local);
|
||||
writeDatagram(localPingPacket, node, node->getLocalSocket());
|
||||
|
||||
|
||||
QByteArray publicPingPacket = constructPingPacket(PingType::Public);
|
||||
writeDatagram(publicPingPacket, node, node->getPublicSocket());
|
||||
|
||||
|
@ -520,10 +627,10 @@ void NodeList::activateSocketFromNodeCommunication(const QByteArray& packet, con
|
|||
// deconstruct this ping packet to see if it is a public or local reply
|
||||
QDataStream packetStream(packet);
|
||||
packetStream.skipRawData(numBytesForPacketHeader(packet));
|
||||
|
||||
|
||||
quint8 pingType;
|
||||
packetStream >> pingType;
|
||||
|
||||
|
||||
// if this is a local or public ping then we can activate a socket
|
||||
// we do nothing with agnostic pings, those are simply for timing
|
||||
if (pingType == PingType::Local && sendingNode->getActiveSocket() != &sendingNode->getLocalSocket()) {
|
||||
|
|
|
@ -43,7 +43,7 @@ class Assignment;
|
|||
class NodeList : public LimitedNodeList {
|
||||
Q_OBJECT
|
||||
SINGLETON_DEPENDENCY
|
||||
|
||||
|
||||
public:
|
||||
NodeType_t getOwnerType() const { return _ownerType; }
|
||||
void setOwnerType(NodeType_t ownerType) { _ownerType = ownerType; }
|
||||
|
@ -53,42 +53,49 @@ public:
|
|||
|
||||
int getNumNoReplyDomainCheckIns() const { return _numNoReplyDomainCheckIns; }
|
||||
DomainHandler& getDomainHandler() { return _domainHandler; }
|
||||
|
||||
|
||||
const NodeSet& getNodeInterestSet() const { return _nodeTypesOfInterest; }
|
||||
void addNodeTypeToInterestSet(NodeType_t nodeTypeToAdd);
|
||||
void addSetOfNodeTypesToNodeInterestSet(const NodeSet& setOfNodeTypes);
|
||||
void resetNodeInterestSet() { _nodeTypesOfInterest.clear(); }
|
||||
|
||||
void processNodeData(const HifiSockAddr& senderSockAddr, const QByteArray& packet);
|
||||
|
||||
|
||||
int processDomainServerList(const QByteArray& packet);
|
||||
|
||||
void setAssignmentServerSocket(const HifiSockAddr& serverSocket) { _assignmentServerSocket = serverSocket; }
|
||||
void sendAssignment(Assignment& assignment);
|
||||
|
||||
|
||||
void pingPunchForInactiveNode(const SharedNodePointer& node);
|
||||
public slots:
|
||||
void reset();
|
||||
void sendDomainServerCheckIn();
|
||||
void pingInactiveNodes();
|
||||
void handleDSPathQuery(const QString& newPath);
|
||||
signals:
|
||||
void limitOfSilentDomainCheckInsReached();
|
||||
private slots:
|
||||
void sendPendingDSPathQuery();
|
||||
private:
|
||||
NodeList() : LimitedNodeList(0, 0) { assert(false); } // Not implemented, needed for DependencyManager templates compile
|
||||
NodeList(char ownerType, unsigned short socketListenPort = 0, unsigned short dtlsListenPort = 0);
|
||||
NodeList(NodeList const&); // Don't implement, needed to avoid copies of singleton
|
||||
void operator=(NodeList const&); // Don't implement, needed to avoid copies of singleton
|
||||
|
||||
|
||||
void sendSTUNRequest();
|
||||
bool processSTUNResponse(const QByteArray& packet);
|
||||
|
||||
void handleICEConnectionToDomainServer();
|
||||
|
||||
|
||||
void processDomainServerAuthRequest(const QByteArray& packet);
|
||||
void requestAuthForDomainServer();
|
||||
void activateSocketFromNodeCommunication(const QByteArray& packet, const SharedNodePointer& sendingNode);
|
||||
void timePingReply(const QByteArray& packet, const SharedNodePointer& sendingNode);
|
||||
|
||||
|
||||
void handleDSPathQueryResponse(const QByteArray& packet);
|
||||
|
||||
void sendDSPathQuery(const QString& newPath);
|
||||
|
||||
NodeType_t _ownerType;
|
||||
NodeSet _nodeTypesOfInterest;
|
||||
DomainHandler _domainHandler;
|
||||
|
@ -96,7 +103,7 @@ private:
|
|||
HifiSockAddr _assignmentServerSocket;
|
||||
bool _hasCompletedInitialSTUNFailure;
|
||||
unsigned int _stunRequestsSinceSuccess;
|
||||
|
||||
|
||||
friend class Application;
|
||||
};
|
||||
|
||||
|
|
|
@ -72,7 +72,7 @@ PacketVersion versionForPacketType(PacketType packetType) {
|
|||
return 1;
|
||||
case PacketTypeEntityAddOrEdit:
|
||||
case PacketTypeEntityData:
|
||||
return VERSION_ENTITIES_PARTICLE_ENTITIES_HAVE_TEXTURES;
|
||||
return VERSION_ENTITIES_HAVE_LINE_TYPE;
|
||||
case PacketTypeEntityErase:
|
||||
return 2;
|
||||
case PacketTypeAudioStreamStats:
|
||||
|
|
|
@ -47,8 +47,8 @@ enum PacketType {
|
|||
PacketTypeMuteEnvironment,
|
||||
PacketTypeAudioStreamStats,
|
||||
PacketTypeDataServerConfirm, // 20
|
||||
UNUSED_1,
|
||||
UNUSED_2,
|
||||
PacketTypeDomainServerPathQuery,
|
||||
PacketTypeDomainServerPathResponse,
|
||||
UNUSED_3,
|
||||
UNUSED_4,
|
||||
UNUSED_5, // 25
|
||||
|
@ -96,7 +96,8 @@ const QSet<PacketType> NON_VERIFIED_PACKETS = QSet<PacketType>()
|
|||
<< PacketTypeNodeJsonStats << PacketTypeEntityQuery
|
||||
<< PacketTypeOctreeDataNack << PacketTypeEntityEditNack
|
||||
<< PacketTypeIceServerHeartbeat << PacketTypeIceServerHeartbeatResponse
|
||||
<< PacketTypeUnverifiedPing << PacketTypeUnverifiedPingReply << PacketTypeStopNode;
|
||||
<< PacketTypeUnverifiedPing << PacketTypeUnverifiedPingReply << PacketTypeStopNode
|
||||
<< PacketTypeDomainServerPathQuery << PacketTypeDomainServerPathResponse;
|
||||
|
||||
const QSet<PacketType> SEQUENCE_NUMBERED_PACKETS = QSet<PacketType>()
|
||||
<< PacketTypeAvatarData;
|
||||
|
@ -122,7 +123,7 @@ int numSequenceNumberBytesForType(PacketType packetType);
|
|||
|
||||
int numBytesForPacketHeader(const QByteArray& packet);
|
||||
int numBytesForPacketHeader(const char* packet);
|
||||
int numBytesForArithmeticCodedPacketType(PacketType packetType);
|
||||
int numBytesForArithmeticCodedPacketType(PacketType packetType);
|
||||
int numBytesForPacketHeaderGivenPacketType(PacketType packetType);
|
||||
|
||||
QUuid uuidFromPacketHeader(const QByteArray& packet);
|
||||
|
@ -138,9 +139,9 @@ QByteArray hashForPacketAndConnectionUUID(const QByteArray& packet, const QUuid&
|
|||
|
||||
PacketSequenceNumber sequenceNumberFromHeader(const QByteArray& packet, PacketType packetType = PacketTypeUnknown);
|
||||
|
||||
void replaceHashInPacket(QByteArray& packet, const QUuid& connectionUUID, PacketType packetType = PacketTypeUnknown);
|
||||
void replaceHashInPacket(QByteArray& packet, const QUuid& connectionUUID, PacketType packetType = PacketTypeUnknown);
|
||||
|
||||
void replaceSequenceNumberInPacket(QByteArray& packet, PacketSequenceNumber sequenceNumber,
|
||||
void replaceSequenceNumberInPacket(QByteArray& packet, PacketSequenceNumber sequenceNumber,
|
||||
PacketType packetType = PacketTypeUnknown);
|
||||
|
||||
void replaceHashAndSequenceNumberInPacket(QByteArray& packet, const QUuid& connectionUUID, PacketSequenceNumber sequenceNumber,
|
||||
|
@ -174,5 +175,6 @@ const PacketVersion VERSION_ENTITIES_ZONE_ENTITIES_HAVE_ATMOSPHERE = 20;
|
|||
const PacketVersion VERSION_ENTITIES_ZONE_ENTITIES_HAVE_SKYBOX = 21;
|
||||
const PacketVersion VERSION_ENTITIES_ZONE_ENTITIES_STAGE_HAS_AUTOMATIC_HOURDAY = 22;
|
||||
const PacketVersion VERSION_ENTITIES_PARTICLE_ENTITIES_HAVE_TEXTURES = 23;
|
||||
const PacketVersion VERSION_ENTITIES_HAVE_LINE_TYPE = 24;
|
||||
|
||||
#endif // hifi_PacketHeaders_h
|
||||
|
|
|
@ -129,6 +129,14 @@ void DeferredLightingEffect::renderWireCube(float size, const glm::vec4& color)
|
|||
releaseSimpleProgram();
|
||||
}
|
||||
|
||||
void DeferredLightingEffect::renderLine(const glm::vec3& p1, const glm::vec3& p2,
|
||||
const glm::vec4& color1, const glm::vec4& color2) {
|
||||
bindSimpleProgram();
|
||||
DependencyManager::get<GeometryCache>()->renderLine(p1, p2, color1, color2);
|
||||
releaseSimpleProgram();
|
||||
}
|
||||
|
||||
|
||||
void DeferredLightingEffect::renderSolidCone(float base, float height, int slices, int stacks) {
|
||||
bindSimpleProgram();
|
||||
DependencyManager::get<GeometryCache>()->renderCone(base, height, slices, stacks);
|
||||
|
@ -288,21 +296,21 @@ void DeferredLightingEffect::render() {
|
|||
program->setUniformValue(locations->ambientSphere + i, *(((QVector4D*) &sh) + i));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (useSkyboxCubemap) {
|
||||
glActiveTexture(GL_TEXTURE5);
|
||||
glBindTexture(GL_TEXTURE_CUBE_MAP, gpu::GLBackend::getTextureID(_skybox->getCubemap()));
|
||||
}
|
||||
|
||||
if (locations->lightBufferUnit >= 0) {
|
||||
gpu::Batch batch;
|
||||
batch.setUniformBuffer(locations->lightBufferUnit, globalLight->getSchemaBuffer());
|
||||
gpu::GLBackend::renderBatch(batch);
|
||||
}
|
||||
|
||||
if (locations->lightBufferUnit >= 0) {
|
||||
gpu::Batch batch;
|
||||
batch.setUniformBuffer(locations->lightBufferUnit, globalLight->getSchemaBuffer());
|
||||
gpu::GLBackend::renderBatch(batch);
|
||||
}
|
||||
|
||||
if (_atmosphere && (locations->atmosphereBufferUnit >= 0)) {
|
||||
gpu::Batch batch;
|
||||
batch.setUniformBuffer(locations->atmosphereBufferUnit, _atmosphere->getDataBuffer());
|
||||
gpu::Batch batch;
|
||||
batch.setUniformBuffer(locations->atmosphereBufferUnit, _atmosphere->getDataBuffer());
|
||||
gpu::GLBackend::renderBatch(batch);
|
||||
}
|
||||
glUniformMatrix4fv(locations->invViewMat, 1, false, reinterpret_cast< const GLfloat* >(&invViewMat));
|
||||
|
@ -366,11 +374,11 @@ void DeferredLightingEffect::render() {
|
|||
|
||||
for (auto lightID : _pointLights) {
|
||||
auto light = _allocatedLights[lightID];
|
||||
|
||||
if (_pointLightLocations.lightBufferUnit >= 0) {
|
||||
gpu::Batch batch;
|
||||
batch.setUniformBuffer(_pointLightLocations.lightBufferUnit, light->getSchemaBuffer());
|
||||
gpu::GLBackend::renderBatch(batch);
|
||||
|
||||
if (_pointLightLocations.lightBufferUnit >= 0) {
|
||||
gpu::Batch batch;
|
||||
batch.setUniformBuffer(_pointLightLocations.lightBufferUnit, light->getSchemaBuffer());
|
||||
gpu::GLBackend::renderBatch(batch);
|
||||
}
|
||||
glUniformMatrix4fv(_pointLightLocations.invViewMat, 1, false, reinterpret_cast< const GLfloat* >(&invViewMat));
|
||||
|
||||
|
@ -412,10 +420,10 @@ void DeferredLightingEffect::render() {
|
|||
for (auto lightID : _spotLights) {
|
||||
auto light = _allocatedLights[lightID];
|
||||
|
||||
if (_spotLightLocations.lightBufferUnit >= 0) {
|
||||
gpu::Batch batch;
|
||||
batch.setUniformBuffer(_spotLightLocations.lightBufferUnit, light->getSchemaBuffer());
|
||||
gpu::GLBackend::renderBatch(batch);
|
||||
if (_spotLightLocations.lightBufferUnit >= 0) {
|
||||
gpu::Batch batch;
|
||||
batch.setUniformBuffer(_spotLightLocations.lightBufferUnit, light->getSchemaBuffer());
|
||||
gpu::GLBackend::renderBatch(batch);
|
||||
}
|
||||
glUniformMatrix4fv(_spotLightLocations.invViewMat, 1, false, reinterpret_cast< const GLfloat* >(&invViewMat));
|
||||
|
||||
|
@ -535,42 +543,42 @@ void DeferredLightingEffect::loadLightProgram(const char* fragSource, bool limit
|
|||
locations.ambientSphere = program.uniformLocation("ambientSphere.L00");
|
||||
locations.invViewMat = program.uniformLocation("invViewMat");
|
||||
|
||||
GLint loc = -1;
|
||||
|
||||
#if (GPU_FEATURE_PROFILE == GPU_CORE)
|
||||
const GLint LIGHT_GPU_SLOT = 3;
|
||||
loc = glGetUniformBlockIndex(program.programId(), "lightBuffer");
|
||||
if (loc >= 0) {
|
||||
glUniformBlockBinding(program.programId(), loc, LIGHT_GPU_SLOT);
|
||||
locations.lightBufferUnit = LIGHT_GPU_SLOT;
|
||||
} else {
|
||||
locations.lightBufferUnit = -1;
|
||||
}
|
||||
#else
|
||||
loc = program.uniformLocation("lightBuffer");
|
||||
if (loc >= 0) {
|
||||
locations.lightBufferUnit = loc;
|
||||
} else {
|
||||
locations.lightBufferUnit = -1;
|
||||
}
|
||||
GLint loc = -1;
|
||||
|
||||
#if (GPU_FEATURE_PROFILE == GPU_CORE)
|
||||
const GLint LIGHT_GPU_SLOT = 3;
|
||||
loc = glGetUniformBlockIndex(program.programId(), "lightBuffer");
|
||||
if (loc >= 0) {
|
||||
glUniformBlockBinding(program.programId(), loc, LIGHT_GPU_SLOT);
|
||||
locations.lightBufferUnit = LIGHT_GPU_SLOT;
|
||||
} else {
|
||||
locations.lightBufferUnit = -1;
|
||||
}
|
||||
#else
|
||||
loc = program.uniformLocation("lightBuffer");
|
||||
if (loc >= 0) {
|
||||
locations.lightBufferUnit = loc;
|
||||
} else {
|
||||
locations.lightBufferUnit = -1;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if (GPU_FEATURE_PROFILE == GPU_CORE)
|
||||
const GLint ATMOSPHERE_GPU_SLOT = 4;
|
||||
loc = glGetUniformBlockIndex(program.programId(), "atmosphereBufferUnit");
|
||||
if (loc >= 0) {
|
||||
glUniformBlockBinding(program.programId(), loc, ATMOSPHERE_GPU_SLOT);
|
||||
locations.atmosphereBufferUnit = ATMOSPHERE_GPU_SLOT;
|
||||
} else {
|
||||
locations.atmosphereBufferUnit = -1;
|
||||
}
|
||||
#else
|
||||
loc = program.uniformLocation("atmosphereBufferUnit");
|
||||
if (loc >= 0) {
|
||||
locations.atmosphereBufferUnit = loc;
|
||||
} else {
|
||||
locations.atmosphereBufferUnit = -1;
|
||||
}
|
||||
#if (GPU_FEATURE_PROFILE == GPU_CORE)
|
||||
const GLint ATMOSPHERE_GPU_SLOT = 4;
|
||||
loc = glGetUniformBlockIndex(program.programId(), "atmosphereBufferUnit");
|
||||
if (loc >= 0) {
|
||||
glUniformBlockBinding(program.programId(), loc, ATMOSPHERE_GPU_SLOT);
|
||||
locations.atmosphereBufferUnit = ATMOSPHERE_GPU_SLOT;
|
||||
} else {
|
||||
locations.atmosphereBufferUnit = -1;
|
||||
}
|
||||
#else
|
||||
loc = program.uniformLocation("atmosphereBufferUnit");
|
||||
if (loc >= 0) {
|
||||
locations.atmosphereBufferUnit = loc;
|
||||
} else {
|
||||
locations.atmosphereBufferUnit = -1;
|
||||
}
|
||||
#endif
|
||||
|
||||
program.release();
|
||||
|
@ -597,4 +605,4 @@ void DeferredLightingEffect::setGlobalLight(const glm::vec3& direction, const gl
|
|||
|
||||
void DeferredLightingEffect::setGlobalSkybox(const model::SkyboxPointer& skybox) {
|
||||
_skybox = skybox;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -54,6 +54,10 @@ public:
|
|||
//// Renders a wireframe cube with the simple program.
|
||||
void renderWireCube(float size, const glm::vec4& color);
|
||||
|
||||
//// Renders a line with the simple program.
|
||||
void renderLine(const glm::vec3& p1, const glm::vec3& p2,
|
||||
const glm::vec4& color1, const glm::vec4& color2);
|
||||
|
||||
//// Renders a solid cone with the simple program.
|
||||
void renderSolidCone(float base, float height, int slices, int stacks);
|
||||
|
||||
|
|
|
@ -72,7 +72,7 @@ QVariantMap HifiConfigVariantMap::mergeCLParametersWithJSONConfig(const QStringL
|
|||
int configIndex = argumentList.indexOf(CONFIG_FILE_OPTION);
|
||||
|
||||
QString configFilePath;
|
||||
|
||||
|
||||
if (configIndex != -1) {
|
||||
// we have a config file - try and read it
|
||||
configFilePath = argumentList[configIndex + 1];
|
||||
|
@ -82,8 +82,8 @@ QVariantMap HifiConfigVariantMap::mergeCLParametersWithJSONConfig(const QStringL
|
|||
QCoreApplication::organizationName(),
|
||||
QCoreApplication::applicationName());
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
return mergedMap;
|
||||
}
|
||||
|
@ -94,23 +94,23 @@ HifiConfigVariantMap::HifiConfigVariantMap() :
|
|||
_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
|
||||
const QString USER_CONFIG_FILE_OPTION = "--user-config";
|
||||
|
||||
|
||||
int userConfigIndex = argumentList.indexOf(USER_CONFIG_FILE_OPTION);
|
||||
if (userConfigIndex != -1) {
|
||||
_userConfigFilename = argumentList[userConfigIndex + 1];
|
||||
|
@ -119,26 +119,26 @@ void HifiConfigVariantMap::loadMasterAndUserConfig(const QStringList& argumentLi
|
|||
QCoreApplication::organizationName(),
|
||||
QCoreApplication::applicationName());
|
||||
}
|
||||
|
||||
|
||||
loadMapFromJSONFile(_userConfig, _userConfigFilename);
|
||||
|
||||
|
||||
// the merged config is initially matched to the master config
|
||||
_mergedConfig = _masterConfig;
|
||||
|
||||
|
||||
// then we merge in anything missing from the user config
|
||||
addMissingValuesToExistingMap(_mergedConfig, _userConfig);
|
||||
}
|
||||
|
||||
void HifiConfigVariantMap::loadMapFromJSONFile(QVariantMap& existingMap, const QString& filename) {
|
||||
QFile configFile(filename);
|
||||
|
||||
|
||||
if (configFile.exists()) {
|
||||
qCDebug(shared) << "Reading JSON config file at" << filename;
|
||||
configFile.open(QIODevice::ReadOnly);
|
||||
|
||||
|
||||
QJsonDocument configDocument = QJsonDocument::fromJson(configFile.readAll());
|
||||
existingMap = configDocument.toVariant().toMap();
|
||||
|
||||
|
||||
} else {
|
||||
qCDebug(shared) << "Could not find JSON config file at" << filename;
|
||||
}
|
||||
|
@ -148,7 +148,7 @@ void HifiConfigVariantMap::addMissingValuesToExistingMap(QVariantMap& existingMa
|
|||
foreach(const QString& key, newMap.keys()) {
|
||||
if (existingMap.contains(key)) {
|
||||
// if this is just a regular value, we're done - we don't ovveride
|
||||
|
||||
|
||||
if (newMap[key].canConvert(QMetaType::QVariantMap) && existingMap[key].canConvert(QMetaType::QVariantMap)) {
|
||||
// there's a variant map below and the existing map has one too, so we need to keep recursing
|
||||
addMissingValuesToExistingMap(*static_cast<QVariantMap*>(existingMap[key].data()), newMap[key].toMap());
|
||||
|
@ -159,11 +159,11 @@ void HifiConfigVariantMap::addMissingValuesToExistingMap(QVariantMap& existingMa
|
|||
}
|
||||
}
|
||||
|
||||
const QVariant* valueForKeyPath(QVariantMap& variantMap, const QString& keyPath) {
|
||||
QVariant* valueForKeyPath(QVariantMap& variantMap, const QString& keyPath) {
|
||||
int dotIndex = keyPath.indexOf('.');
|
||||
|
||||
|
||||
QString firstKey = (dotIndex == -1) ? keyPath : keyPath.mid(0, dotIndex);
|
||||
|
||||
|
||||
if (variantMap.contains(firstKey)) {
|
||||
if (dotIndex == -1) {
|
||||
return &variantMap[firstKey];
|
||||
|
@ -171,6 +171,6 @@ const QVariant* valueForKeyPath(QVariantMap& variantMap, const QString& keyPath)
|
|||
return valueForKeyPath(*static_cast<QVariantMap*>(variantMap[firstKey].data()), keyPath.mid(dotIndex + 1));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
|
|
@ -17,26 +17,26 @@
|
|||
class HifiConfigVariantMap {
|
||||
public:
|
||||
static QVariantMap mergeCLParametersWithJSONConfig(const QStringList& argumentList);
|
||||
|
||||
|
||||
HifiConfigVariantMap();
|
||||
void loadMasterAndUserConfig(const QStringList& argumentList);
|
||||
|
||||
|
||||
const QVariantMap& getMasterConfig() const { return _masterConfig; }
|
||||
QVariantMap& getUserConfig() { return _userConfig; }
|
||||
QVariantMap& getMergedConfig() { return _mergedConfig; }
|
||||
|
||||
|
||||
const QString& getUserConfigFilename() const { return _userConfigFilename; }
|
||||
private:
|
||||
QString _userConfigFilename;
|
||||
|
||||
|
||||
QVariantMap _masterConfig;
|
||||
QVariantMap _userConfig;
|
||||
QVariantMap _mergedConfig;
|
||||
|
||||
|
||||
void loadMapFromJSONFile(QVariantMap& existingMap, const QString& filename);
|
||||
void addMissingValuesToExistingMap(QVariantMap& existingMap, const QVariantMap& newMap);
|
||||
};
|
||||
|
||||
const QVariant* valueForKeyPath(QVariantMap& variantMap, const QString& keyPath);
|
||||
QVariant* valueForKeyPath(QVariantMap& variantMap, const QString& keyPath);
|
||||
|
||||
#endif // hifi_HifiConfigVariantMap_h
|
||||
|
|
|
@ -31,7 +31,8 @@ enum ShapeType {
|
|||
SHAPE_TYPE_CAPSULE_Z,
|
||||
SHAPE_TYPE_CYLINDER_X,
|
||||
SHAPE_TYPE_CYLINDER_Y,
|
||||
SHAPE_TYPE_CYLINDER_Z
|
||||
SHAPE_TYPE_CYLINDER_Z,
|
||||
SHAPE_TYPE_LINE
|
||||
};
|
||||
|
||||
class ShapeInfo {
|
||||
|
|
|
@ -51,15 +51,16 @@ void InfoView::show(const QString& path, bool firstOrChangedOnly) {
|
|||
}
|
||||
if (firstOrChangedOnly) {
|
||||
const QString lastVersion = infoVersion.get();
|
||||
const QString version = fetchVersion(url);
|
||||
// If we have version information stored
|
||||
if (lastVersion != QString::null) {
|
||||
// Check to see the document version. If it's valid and matches
|
||||
// the stored version, we're done, so exit
|
||||
const QString version = fetchVersion(url);
|
||||
if (version == QString::null || version == lastVersion) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
infoVersion.set(version);
|
||||
}
|
||||
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
||||
QString infoViewName(NAME + "_" + path);
|
||||
|
|
Loading…
Reference in a new issue