This commit is contained in:
Philip Rosedale 2014-09-26 15:55:41 -07:00
commit c276523afd
48 changed files with 1354 additions and 515 deletions

View file

@ -643,7 +643,7 @@ void AudioMixer::run() {
QJsonObject audioGroupObject = settingsObject[AUDIO_GROUP_KEY].toObject();
// check the payload to see if we have asked for dynamicJitterBuffer support
const QString DYNAMIC_JITTER_BUFFER_JSON_KEY = "A-dynamic-jitter-buffer";
const QString DYNAMIC_JITTER_BUFFER_JSON_KEY = "dynamic-jitter-buffer";
_streamSettings._dynamicJitterBuffers = audioGroupObject[DYNAMIC_JITTER_BUFFER_JSON_KEY].toBool();
if (_streamSettings._dynamicJitterBuffers) {
qDebug() << "Enable dynamic jitter buffers.";
@ -652,21 +652,21 @@ void AudioMixer::run() {
}
bool ok;
const QString DESIRED_JITTER_BUFFER_FRAMES_KEY = "B-static-desired-jitter-buffer-frames";
const QString DESIRED_JITTER_BUFFER_FRAMES_KEY = "static-desired-jitter-buffer-frames";
_streamSettings._staticDesiredJitterBufferFrames = audioGroupObject[DESIRED_JITTER_BUFFER_FRAMES_KEY].toString().toInt(&ok);
if (!ok) {
_streamSettings._staticDesiredJitterBufferFrames = DEFAULT_STATIC_DESIRED_JITTER_BUFFER_FRAMES;
}
qDebug() << "Static desired jitter buffer frames:" << _streamSettings._staticDesiredJitterBufferFrames;
const QString MAX_FRAMES_OVER_DESIRED_JSON_KEY = "C-max-frames-over-desired";
const QString MAX_FRAMES_OVER_DESIRED_JSON_KEY = "max-frames-over-desired";
_streamSettings._maxFramesOverDesired = audioGroupObject[MAX_FRAMES_OVER_DESIRED_JSON_KEY].toString().toInt(&ok);
if (!ok) {
_streamSettings._maxFramesOverDesired = DEFAULT_MAX_FRAMES_OVER_DESIRED;
}
qDebug() << "Max frames over desired:" << _streamSettings._maxFramesOverDesired;
const QString USE_STDEV_FOR_DESIRED_CALC_JSON_KEY = "D-use-stdev-for-desired-calc";
const QString USE_STDEV_FOR_DESIRED_CALC_JSON_KEY = "use-stdev-for-desired-calc";
_streamSettings._useStDevForJitterCalc = audioGroupObject[USE_STDEV_FOR_DESIRED_CALC_JSON_KEY].toBool();
if (_streamSettings._useStDevForJitterCalc) {
qDebug() << "Using Philip's stdev method for jitter calc if dynamic jitter buffers enabled";
@ -674,28 +674,28 @@ void AudioMixer::run() {
qDebug() << "Using Fred's max-gap method for jitter calc if dynamic jitter buffers enabled";
}
const QString WINDOW_STARVE_THRESHOLD_JSON_KEY = "E-window-starve-threshold";
const QString WINDOW_STARVE_THRESHOLD_JSON_KEY = "window-starve-threshold";
_streamSettings._windowStarveThreshold = audioGroupObject[WINDOW_STARVE_THRESHOLD_JSON_KEY].toString().toInt(&ok);
if (!ok) {
_streamSettings._windowStarveThreshold = DEFAULT_WINDOW_STARVE_THRESHOLD;
}
qDebug() << "Window A starve threshold:" << _streamSettings._windowStarveThreshold;
const QString WINDOW_SECONDS_FOR_DESIRED_CALC_ON_TOO_MANY_STARVES_JSON_KEY = "F-window-seconds-for-desired-calc-on-too-many-starves";
const QString WINDOW_SECONDS_FOR_DESIRED_CALC_ON_TOO_MANY_STARVES_JSON_KEY = "window-seconds-for-desired-calc-on-too-many-starves";
_streamSettings._windowSecondsForDesiredCalcOnTooManyStarves = audioGroupObject[WINDOW_SECONDS_FOR_DESIRED_CALC_ON_TOO_MANY_STARVES_JSON_KEY].toString().toInt(&ok);
if (!ok) {
_streamSettings._windowSecondsForDesiredCalcOnTooManyStarves = DEFAULT_WINDOW_SECONDS_FOR_DESIRED_CALC_ON_TOO_MANY_STARVES;
}
qDebug() << "Window A length:" << _streamSettings._windowSecondsForDesiredCalcOnTooManyStarves << "seconds";
const QString WINDOW_SECONDS_FOR_DESIRED_REDUCTION_JSON_KEY = "G-window-seconds-for-desired-reduction";
const QString WINDOW_SECONDS_FOR_DESIRED_REDUCTION_JSON_KEY = "window-seconds-for-desired-reduction";
_streamSettings._windowSecondsForDesiredReduction = audioGroupObject[WINDOW_SECONDS_FOR_DESIRED_REDUCTION_JSON_KEY].toString().toInt(&ok);
if (!ok) {
_streamSettings._windowSecondsForDesiredReduction = DEFAULT_WINDOW_SECONDS_FOR_DESIRED_REDUCTION;
}
qDebug() << "Window B length:" << _streamSettings._windowSecondsForDesiredReduction << "seconds";
const QString REPETITION_WITH_FADE_JSON_KEY = "H-repetition-with-fade";
const QString REPETITION_WITH_FADE_JSON_KEY = "repetition-with-fade";
_streamSettings._repetitionWithFade = audioGroupObject[REPETITION_WITH_FADE_JSON_KEY].toBool();
if (_streamSettings._repetitionWithFade) {
qDebug() << "Repetition with fade enabled";
@ -703,13 +703,13 @@ void AudioMixer::run() {
qDebug() << "Repetition with fade disabled";
}
const QString PRINT_STREAM_STATS_JSON_KEY = "I-print-stream-stats";
const QString PRINT_STREAM_STATS_JSON_KEY = "print-stream-stats";
_printStreamStats = audioGroupObject[PRINT_STREAM_STATS_JSON_KEY].toBool();
if (_printStreamStats) {
qDebug() << "Stream stats will be printed to stdout";
}
const QString FILTER_KEY = "J-enable-filter";
const QString FILTER_KEY = "enable-filter";
if (audioGroupObject[FILTER_KEY].isBool()) {
_enableFilter = audioGroupObject[FILTER_KEY].toBool();
}
@ -717,7 +717,7 @@ void AudioMixer::run() {
qDebug() << "Filter enabled";
}
const QString UNATTENUATED_ZONE_KEY = "Z-unattenuated-zone";
const QString UNATTENUATED_ZONE_KEY = "unattenuated-zone";
QString unattenuatedZoneString = audioGroupObject[UNATTENUATED_ZONE_KEY].toString();
if (!unattenuatedZoneString.isEmpty()) {

View file

@ -3,14 +3,37 @@ set(TARGET_NAME domain-server)
# setup the project and link required Qt modules
setup_hifi_project(Network)
# remove and then copy the files for the webserver
add_custom_command(TARGET ${TARGET_NAME} POST_BUILD
COMMAND "${CMAKE_COMMAND}" -E remove_directory
$<TARGET_FILE_DIR:${TARGET_NAME}>/resources/web)
add_custom_command(TARGET ${TARGET_NAME} POST_BUILD
COMMAND "${CMAKE_COMMAND}" -E copy_directory
"${PROJECT_SOURCE_DIR}/resources/web"
$<TARGET_FILE_DIR:${TARGET_NAME}>/resources/web)
# remove current resources dir
add_custom_command(
TARGET ${TARGET_NAME} POST_BUILD
COMMAND "${CMAKE_COMMAND}" -E remove_directory
$<TARGET_FILE_DIR:${TARGET_NAME}>/resources
)
# copy all files in resources, including web
add_custom_command(
TARGET ${TARGET_NAME} POST_BUILD
COMMAND "${CMAKE_COMMAND}" -E copy_directory
"${PROJECT_SOURCE_DIR}/resources"
$<TARGET_FILE_DIR:${TARGET_NAME}>/resources
)
if (NOT WIN32)
# remove the web directory so we can make it a symlink
add_custom_command(
TARGET ${TARGET_NAME} POST_BUILD
COMMAND "${CMAKE_COMMAND}" -E remove_directory
$<TARGET_FILE_DIR:${TARGET_NAME}>/resources/web
)
# make the web directory a symlink
add_custom_command(
TARGET ${TARGET_NAME} POST_BUILD
COMMAND "${CMAKE_COMMAND}" -E create_symlink
"${PROJECT_SOURCE_DIR}/resources/web"
$<TARGET_FILE_DIR:${TARGET_NAME}>/resources/web
)
endif ()
# link the shared hifi libraries
link_hifi_libraries(embedded-webserver networking shared)

View file

@ -0,0 +1,128 @@
[
{
"name": "metaverse",
"label": "Metaverse Registration",
"settings": [
{
"name": "access-token",
"label": "Access Token",
"help": "This is an access token generated on the <a href='https://data.highfidelity.io/tokens'>My Tokens</a> page of your High Fidelity account.<br/>Generate a token with the 'domains' scope and paste it here.<br/>This is required to associate this domain-server with a domain in your account."
},
{
"name": "id",
"label": "Domain ID",
"help": "This is your High Fidelity domain ID. If you do not want your domain to be registered in the High Fidelity metaverse you can leave this blank."
}
]
},
{
"name": "security",
"label": "Security",
"settings": [
{
"name": "http-username",
"label": "HTTP Username",
"help": "Username used for basic HTTP authentication."
},
{
"name": "http-password",
"label": "HTTP Password",
"type": "password",
"help": "Password used for basic HTTP authentication. Leave this blank if you do not want to change it.",
"value-hidden": true
}
]
},
{
"name": "audio",
"label": "Audio",
"assignment-types": [0],
"settings": [
{
"name": "enable-filter",
"type": "checkbox",
"label": "Enable Positional Filter",
"help": "positional audio stream uses lowpass filter",
"default": true
},
{
"name": "unattenuated-zone",
"label": "Unattenuated Zone",
"help": "Boxes for source and listener (corner x, corner y, corner z, size x, size y, size z, corner x, corner y, corner z, size x, size y, size z)",
"placeholder": "no zone"
},
{
"name": "dynamic-jitter-buffer",
"type": "checkbox",
"label": "Dynamic Jitter Buffers",
"help": "dynamically buffer client audio based on perceived jitter in packet receipt timing",
"default": false,
"advanced": true
},
{
"name": "static-desired-jitter-buffer-frames",
"label": "Static Desired Jitter Buffer Frames",
"help": "If dynamic jitter buffers is disabled, this determines the target number of frames maintained by the AudioMixer's jitter buffers",
"placeholder": "1",
"default": "1",
"advanced": true
},
{
"name": "max-frames-over-desired",
"label": "Max Frames Over Desired",
"help": "The highest number of frames an AudioMixer's ringbuffer can exceed the desired jitter buffer frames by",
"placeholder": "10",
"default": "10",
"advanced": true
},
{
"name": "use-stdev-for-desired-calc",
"type": "checkbox",
"label": "Use Stdev for Desired Jitter Frames Calc:",
"help": "use Philip's method (stdev of timegaps) to calculate desired jitter frames (otherwise Fred's max timegap method is used)",
"default": false,
"advanced": true
},
{
"name": "window-starve-threshold",
"label": "Window Starve Threshold",
"help": "If this many starves occur in an N-second window (N is the number in the next field), then the desired jitter frames will be re-evaluated using Window A.",
"placeholder": "3",
"default": "3",
"advanced": true
},
{
"name": "window-seconds-for-desired-calc-on-too-many-starves",
"label": "Timegaps Window (A) Seconds:",
"help": "Window A contains a history of timegaps. Its max timegap is used to re-evaluate the desired jitter frames when too many starves occur within it.",
"placeholder": "50",
"default": "50",
"advanced": true
},
{
"name": "window-seconds-for-desired-reduction",
"label": "Timegaps Window (B) Seconds:",
"help": "Window B contains a history of timegaps. Its max timegap is used as a ceiling for the desired jitter frames value.",
"placeholder": "10",
"default": "10",
"advanced": true
},
{
"name": "repetition-with-fade",
"type": "checkbox",
"label": "Repetition with Fade:",
"help": "dropped frames and mixing during starves repeat the last frame, eventually fading to silence",
"default": false,
"advanced": true
},
{
"name": "I-print-stream-stats",
"type": "checkbox",
"label": "Print Stream Stats:",
"help": "audio upstream and downstream stats of each agent printed to audio-mixer stdout",
"default": false,
"advanced": true
}
]
}
]

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,8 +1,12 @@
#nodes-lead, #settings-lead {
body {
position: relative;
}
.table-lead {
color: #66CCCC;
}
#nodes-lead .lead-line, #settings-lead .lead-line {
.table-lead .lead-line {
background-color: #66CCCC;
}
@ -46,4 +50,23 @@ span.port {
.stale {
color: red;
}
.advanced-setting {
display: none;
}
#setup-sidebar.affix {
position: fixed;
top: 15px;
}
#setup-sidebar button {
width: 100%;
margin-top: 10px;
}
#small-save-button {
width: 100%;
margin-bottom: 15px;
}

View file

@ -1,4 +1,4 @@
</div>
<script src='/js/jquery-2.0.3.min.js'></script>
<script src='/js/jquery-2.1.1.min.js'></script>
<script src='/js/bootstrap.min.js'></script>
<script src='/js/domain-server.js'></script>

View file

@ -25,16 +25,15 @@
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
<li><a href="/">Nodes</a></li>
<li><a href="/settings/">Settings</a></li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Assignments <span class="caret"></span></a>
<ul class="dropdown-menu" role="menu">
<li><a href="/assignment">New Assignment</a></li>
</ul>
</li>
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Assignments <span class="caret"></span></a>
<ul class="dropdown-menu" role="menu">
<li><a href="/assignment">New Assignment</a></li>
</ul>
</li>
<li><a href="/settings/">Settings</a></li>
</ul>
</div>
</div><!-- /.container-fluid -->
</nav>
<div class="container">
<div class="container-fluid">

View file

@ -1,63 +1,78 @@
<!--#include file="header.html"-->
<div id="nodes-lead" class="table-lead"><h3>Nodes</h3><div class="lead-line"></div></div>
<div style="clear:both;"></div>
<button type="button" class="btn btn-danger" id="kill-all-btn">
<span class="glyphicon glyphicon-fire"></span> Kill all Nodes
</button>
<table id="nodes-table" class="table table-striped">
<thead>
<tr>
<th>Type</th>
<th>UUID</th>
<th>Pool</th>
<th>Username</th>
<th>Public</th>
<th>Local</th>
<th>Uptime (s)</th>
<th>Pending Credits</th>
<th>Kill?</th>
</tr>
</thead>
<tbody>
<script id="nodes-template" type="text/template">
<% _.each(nodes, function(node, node_index){ %>
<div class="col-md-10 col-md-offset-1">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Nodes</h3>
</div>
<div class="panel-body">
<table id="nodes-table" class="table table-striped">
<thead>
<tr>
<td><%- node.type %></td>
<td><a href="stats/?uuid=<%- node.uuid %>"><%- node.uuid %></a></td>
<td><%- node.pool %></td>
<td><%- node.username %></td>
<td><%- node.public.ip %><span class='port'>:<%- node.public.port %></span></td>
<td><%- node.local.ip %><span class='port'>:<%- node.local.port %></span></td>
<td><%- ((Date.now() - node.wake_timestamp) / 1000).toLocaleString() %></td>
<td><%- (typeof node.pending_credits == 'number' ? node.pending_credits.toLocaleString() : 'N/A') %></td>
<td><span class='glyphicon glyphicon-remove' data-uuid="<%- node.uuid %>"></span></td>
<th>Type</th>
<th>UUID</th>
<th>Pool</th>
<th>Username</th>
<th>Public</th>
<th>Local</th>
<th>Uptime (s)</th>
<th>Pending Credits</th>
<th>Kill?</th>
</tr>
<% }); %>
</script>
</table>
</thead>
<tbody>
<script id="nodes-template" type="text/template">
<% _.each(nodes, function(node, node_index){ %>
<tr>
<td><%- node.type %></td>
<td><a href="stats/?uuid=<%- node.uuid %>"><%- node.uuid %></a></td>
<td><%- node.pool %></td>
<td><%- node.username %></td>
<td><%- node.public.ip %><span class='port'>:<%- node.public.port %></span></td>
<td><%- node.local.ip %><span class='port'>:<%- node.local.port %></span></td>
<td><%- ((Date.now() - node.wake_timestamp) / 1000).toLocaleString() %></td>
<td><%- (typeof node.pending_credits == 'number' ? node.pending_credits.toLocaleString() : 'N/A') %></td>
<td><span class='glyphicon glyphicon-remove' data-uuid="<%- node.uuid %>"></span></td>
</tr>
<% }); %>
</script>
</table>
</div>
<div class="panel-footer">
<button type="button" class="btn btn-danger" id="kill-all-btn">
<span class="glyphicon glyphicon-remove-circle"></span> Kill all Nodes
</button>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Queued Assignments</h3>
</div>
<div class="panel-body">
<table id="assignments-table" class="table table-striped">
<thead>
<tr>
<th>Type</th>
<th>UUID</th>
<th>Pool</th>
</tr>
</thead>
<tbody>
<script id="queued-template" type="text/template">
<% _.each(queued, function(assignment, uuid){ %>
<tr>
<td><%- assignment.type %></td>
<td><%- uuid %></td>
<td><%- assignment.pool %></td>
</tr>
<% }); %>
</script>
</tbody>
</table>
<div>
</div>
</div>
<div id="queued-lead" class="table-lead"><h3>Queued Assignments</h3><div class="lead-line"></div></div>
<table id="assignments-table" class="table table-striped">
<thead>
<tr>
<th>Type</th>
<th>UUID</th>
<th>Pool</th>
</tr>
</thead>
<tbody>
<script id="queued-template" type="text/template">
<% _.each(queued, function(assignment, uuid){ %>
<tr>
<td><%- assignment.type %></td>
<td><%- uuid %></td>
<td><%- assignment.pool %></td>
</tr>
<% }); %>
</script>
</tbody>
</table>
<!--#include file="footer.html"-->
<script src='js/underscore-min.js'></script>
<script src='js/tables.js'></script>
<script src='js/underscore-1.5.0.min.js'></script>
<!--#include file="page-end.html"-->

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,34 +1,128 @@
var Settings = {};
var Settings = {
showAdvanced: false
};
var viewHelpers = {
getFormGroup: function(groupName, setting, values, isAdvanced) {
setting_id = groupName + "_" + setting.name
form_group = "<div class='form-group" + (isAdvanced ? " advanced-setting" : "") + "'>"
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 = ""
}
if (setting.type === 'checkbox') {
form_group += "<label class='control-label'>" + setting.label + "</label>"
form_group += "<div class='checkbox'>"
form_group += "<label for='" + setting_id + "'>"
form_group += "<input type='checkbox' id='" + setting_id + "' " + (setting_value ? "checked" : "") + "/>"
form_group += " " + setting.help + "</label>";
form_group += "</div>"
} else {
input_type = _.has(setting, 'type') ? setting.type : "text"
form_group += "<label for='" + setting_id + "' class='control-label'>" + setting.label + "</label>";
form_group += "<input type='" + input_type + "' class='form-control' id='" + setting_id +
"' placeholder='" + (_.has(setting, 'placeholder') ? setting.placeholder : "") +
"' value='" + setting_value + "'/>"
form_group += "<span class='help-block'>" + setting.help + "</span>"
}
form_group += "</div>"
return form_group
}
}
$(document).ready(function(){
var source = $('#settings-template').html();
Settings.template = _.template(source);
/*
* Clamped-width.
* Usage:
* <div data-clampedwidth=".myParent">This long content will force clamped width</div>
*
* Author: LV
*/
reloadSettings();
});
$('[data-clampedwidth]').each(function () {
var elem = $(this);
var parentPanel = elem.data('clampedwidth');
var resizeFn = function () {
var sideBarNavWidth = $(parentPanel).width() - parseInt(elem.css('paddingLeft')) - parseInt(elem.css('paddingRight')) - parseInt(elem.css('marginLeft')) - parseInt(elem.css('marginRight')) - parseInt(elem.css('borderLeftWidth')) - parseInt(elem.css('borderRightWidth'));
elem.css('width', sideBarNavWidth);
};
resizeFn();
$(window).resize(resizeFn);
})
$('#settings-form').on('change', 'input', 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 = $('.advanced-setting')
if (Settings.showAdvanced) {
advancedSelector.show()
$(this).html("Hide advanced")
} else {
advancedSelector.hide()
$(this).html("Show advanced")
}
$(this).blur()
})
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()
})
function reloadSettings() {
$.getJSON('/settings.json', function(data){
$('#settings').html(Settings.template(data));
_.extend(data, viewHelpers)
$('.nav-stacked').html(Settings.sidebarTemplate(data))
$('#panels').html(Settings.panelsTemplate(data))
Settings.initialValues = form2js('settings-form', "_", false, cleanupFormValues, true);
});
}
var SETTINGS_ERROR_MESSAGE = "There was a problem saving domain settings. Please try again!";
$('#settings').on('click', 'button', function(e){
$('body').on('click', '.save-button', function(e){
// disable any inputs not changed
$("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);
var formJSON = form2js('settings-form', "_", false, cleanupFormValues, true);
// 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),
@ -36,12 +130,11 @@ $('#settings').on('click', 'button', function(e){
type: 'POST'
}).done(function(data){
if (data.status == "success") {
showAlertMessage("Domain settings saved.", true);
showRestartModal();
} else {
showAlertMessage(SETTINGS_ERROR_MESSAGE, false);
reloadSettings();
}
reloadSettings();
}).fail(function(){
showAlertMessage(SETTINGS_ERROR_MESSAGE, false);
reloadSettings();
@ -50,10 +143,54 @@ $('#settings').on('click', 'button', function(e){
return false;
});
$('#settings').on('change', 'input', function(){
// this input was changed, add the changed data attribute to it
$(this).attr('data-changed', true);
});
function badgeSidebarForDifferences(changedInput) {
// figure out which group this input is in
var panelParentID = changedInput.closest('.panel').attr('id')
// get a JSON representation of that section
var rootJSON = form2js(panelParentID, "_", false, cleanupFormValues, true);
var panelJSON = rootJSON[panelParentID]
var badgeValue = 0
for (var setting in panelJSON) {
if (panelJSON[setting] != Settings.initialValues[panelParentID][ 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 showRestartModal() {
$('#restart-modal').modal({
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);
}
}, 1000);
}
function cleanupFormValues(node) {
if (node.type && node.type === 'checkbox') {

View file

@ -0,0 +1,33 @@
$(document).ready(function(){
/*
* 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');
var resizeFn = function () {
var sideBarNavWidth = $(parentPanel).width() - parseInt(elem.css('paddingLeft')) - parseInt(elem.css('paddingRight')) - parseInt(elem.css('marginLeft')) - parseInt(elem.css('marginRight')) - parseInt(elem.css('borderLeftWidth')) - parseInt(elem.css('borderRightWidth'));
elem.css('width', sideBarNavWidth);
};
resizeFn();
$(window).resize(resizeFn);
});
var listSource = $('#list-group-template').html();
var listTemplate = _.template(listSource);
reloadSettings();
function reloadSettings() {
$.getJSON('describe-setup.json', function(data){
$('.list-group').html(listTemplate(data));
});
}
});

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,74 +0,0 @@
{
"audio": {
"label": "Audio",
"assignment-types": [0],
"settings": {
"A-dynamic-jitter-buffer": {
"type": "checkbox",
"label": "Dynamic Jitter Buffers",
"help": "Dynamically buffer client audio based on perceived jitter in packet receipt timing",
"default": false
},
"B-static-desired-jitter-buffer-frames": {
"label": "Static Desired Jitter Buffer Frames",
"help": "If dynamic jitter buffers is disabled, this determines the target number of frames maintained by the AudioMixer's jitter buffers",
"placeholder": "1",
"default": "1"
},
"C-max-frames-over-desired": {
"label": "Max Frames Over Desired",
"help": "The highest number of frames an AudioMixer's ringbuffer can exceed the desired jitter buffer frames by",
"placeholder": "10",
"default": "10"
},
"D-use-stdev-for-desired-calc": {
"type": "checkbox",
"label": "Use Stdev for Desired Jitter Frames Calc:",
"help": "If checked, Philip's method (stdev of timegaps) is used to calculate desired jitter frames. Otherwise, Fred's method (max timegap) is used",
"default": false
},
"E-window-starve-threshold": {
"label": "Window Starve Threshold",
"help": "If this many starves occur in an N-second window (N is the number in the next field), then the desired jitter frames will be re-evaluated using Window A.",
"placeholder": "3",
"default": "3"
},
"F-window-seconds-for-desired-calc-on-too-many-starves": {
"label": "Timegaps Window (A) Seconds:",
"help": "Window A contains a history of timegaps. Its max timegap is used to re-evaluate the desired jitter frames when too many starves occur within it.",
"placeholder": "50",
"default": "50"
},
"G-window-seconds-for-desired-reduction": {
"label": "Timegaps Window (B) Seconds:",
"help": "Window B contains a history of timegaps. Its max timegap is used as a ceiling for the desired jitter frames value.",
"placeholder": "10",
"default": "10"
},
"H-repetition-with-fade": {
"type": "checkbox",
"label": "Repetition with Fade:",
"help": "If enabled, dropped frames and mixing during starves will repeat the last frame, eventually fading to silence",
"default": false
},
"I-print-stream-stats": {
"type": "checkbox",
"label": "Print Stream Stats:",
"help": "If enabled, audio upstream and downstream stats of each agent will be printed each second to stdout",
"default": false
},
"Z-unattenuated-zone": {
"label": "Unattenuated Zone",
"help": "Boxes for source and listener (corner x, corner y, corner z, size x, size y, size z, corner x, corner y, corner z, size x, size y, size z)",
"placeholder": "no zone",
"default": ""
},
"J-enable-filter": {
"type": "checkbox",
"label": "Enable Positional Filter",
"help": "If enabled, positional audio stream uses lowpass filter",
"default": true
}
}
}
}

View file

@ -1,52 +1,78 @@
<!--#include virtual="header.html"-->
<div id="settings-lead" class="table-lead"><h3>Settings</h3><div class="lead-line"></div></div>
<div style="clear: both;"></div>
<div class="alert" style="display:none;"></div>
<form class="form-horizontal" id="settings-form" role="form">
<div class="col-md-10 col-md-offset-1">
<div class="col-md-12">
<div class="alert" style="display:none;"></div>
</div>
<script id="settings-template" type="text/template">
<% _.each(descriptions, function(group, group_key){ %>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title"><%- group.label %></h3>
</div>
<div class="panel-body">
<% _.each(group.settings, function(setting, setting_key){ %>
<div class="form-group">
<% var setting_id = group_key + "." + setting_key %>
<label for="<%- setting_id %>" class="col-sm-2 control-label"><%- setting.label %></label>
<div class="col-sm-10">
<% if (setting.type === "checkbox") { %>
<% var checked_box = _.has(values, group_key) ? values[group_key][setting_key] : setting.default %>
<input type="checkbox" id="<%- setting_id %>" <%- checked_box ? "checked" : "" %>>
<% } else { %>
<% if (setting.input_addon) { %>
<div class="input-group">
<div class="input-group-addon"><%- setting.input_addon %></div>
<% } %>
<input type="text" class="form-control" id="<%- setting_id %>"
placeholder="<%- setting.placeholder %>"
value="<%- (values[group_key] || {})[setting_key] %>">
<% if (setting.input_addon) { %>
</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){ %>
<li>
<a href="#<%-group.name %>" 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" 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){ %>
<div class="panel panel-default" id="<%- group.name %>">
<div class="panel-heading">
<h3 class="panel-title"><%- group.label %></h3>
</div>
<div class="panel-body">
<% split_settings = _.partition(group.settings, function(value, index) { return !value.advanced }) %>
<% _.each(split_settings[0], function(setting) { %>
<%= getFormGroup(group.name, setting, values, false) %>
<% }); %>
<% _.each(split_settings[1], function(setting) { %>
<%= getFormGroup(group.name, setting, values, true) %>
<% }); %>
</div>
<p class="help-block col-sm-offset-2 col-sm-10"><%- setting.help %></p>
</div>
<% }); %>
</div>
</div>
<% }); %>
<button type="submit" class="btn btn-default">Save</button>
</script>
</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>
</div>
<div class="modal fade" id="restart-modal">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">domain-server is restarting</h4>
</div>
<div class="modal-body">
<h5>This page will automatically refresh in <span id="refresh-time">3 seconds</span>.</h5>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->
<div id="settings"></div>
</form>
<!--#include virtual="footer.html"-->
<script src='/js/underscore-min.js'></script>
<script src='/js/settings.js'></script>
<script src='/js/form2js.min.js'></script>
<script src='/js/underscore-1.5.0.min.js'></script>
<!--#include virtual="page-end.html"-->

View file

@ -30,6 +30,8 @@
#include "DomainServer.h"
int const DomainServer::EXIT_CODE_REBOOT = 234923;
DomainServer::DomainServer(int argc, char* argv[]) :
QCoreApplication(argc, argv),
_shutdownEventListener(this),
@ -48,22 +50,21 @@ DomainServer::DomainServer(int argc, char* argv[]) :
_cookieSessionHash(),
_settingsManager()
{
LogUtils::init();
setOrganizationName("High Fidelity");
setOrganizationDomain("highfidelity.io");
setApplicationName("domain-server");
QSettings::setDefaultFormat(QSettings::IniFormat);
_settingsManager.loadSettingsMap(arguments());
installNativeEventFilter(&_shutdownEventListener);
connect(&_shutdownEventListener, SIGNAL(receivedCloseEvent()), SLOT(quit()));
qRegisterMetaType<DomainServerWebSessionData>("DomainServerWebSessionData");
qRegisterMetaTypeStreamOperators<DomainServerWebSessionData>("DomainServerWebSessionData");
_argumentVariantMap = HifiConfigVariantMap::mergeCLParametersWithJSONConfig(arguments());
if (optionallyReadX509KeyAndCertificate() && optionallySetupOAuth() && optionallySetupAssignmentPayment()) {
// we either read a certificate and private key or were not passed one
// and completed login or did not need to
@ -78,13 +79,18 @@ DomainServer::DomainServer(int argc, char* argv[]) :
}
}
void DomainServer::restart() {
qDebug() << "domain-server is restarting.";
exit(DomainServer::EXIT_CODE_REBOOT);
}
bool DomainServer::optionallyReadX509KeyAndCertificate() {
const QString X509_CERTIFICATE_OPTION = "cert";
const QString X509_PRIVATE_KEY_OPTION = "key";
const QString X509_KEY_PASSPHRASE_ENV = "DOMAIN_SERVER_KEY_PASSPHRASE";
QString certPath = _argumentVariantMap.value(X509_CERTIFICATE_OPTION).toString();
QString keyPath = _argumentVariantMap.value(X509_PRIVATE_KEY_OPTION).toString();
QString certPath = _settingsManager.getSettingsMap().value(X509_CERTIFICATE_OPTION).toString();
QString keyPath = _settingsManager.getSettingsMap().value(X509_PRIVATE_KEY_OPTION).toString();
if (!certPath.isEmpty() && !keyPath.isEmpty()) {
// the user wants to use DTLS to encrypt communication with nodes
@ -143,10 +149,11 @@ bool DomainServer::optionallySetupOAuth() {
const QString OAUTH_CLIENT_SECRET_ENV = "DOMAIN_SERVER_CLIENT_SECRET";
const QString REDIRECT_HOSTNAME_OPTION = "hostname";
_oauthProviderURL = QUrl(_argumentVariantMap.value(OAUTH_PROVIDER_URL_OPTION).toString());
_oauthClientID = _argumentVariantMap.value(OAUTH_CLIENT_ID_OPTION).toString();
const QVariantMap& settingsMap = _settingsManager.getSettingsMap();
_oauthProviderURL = QUrl(settingsMap.value(OAUTH_PROVIDER_URL_OPTION).toString());
_oauthClientID = settingsMap.value(OAUTH_CLIENT_ID_OPTION).toString();
_oauthClientSecret = QProcessEnvironment::systemEnvironment().value(OAUTH_CLIENT_SECRET_ENV);
_hostname = _argumentVariantMap.value(REDIRECT_HOSTNAME_OPTION).toString();
_hostname = settingsMap.value(REDIRECT_HOSTNAME_OPTION).toString();
if (!_oauthClientID.isEmpty()) {
if (_oauthProviderURL.isEmpty()
@ -171,9 +178,11 @@ void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) {
const QString CUSTOM_PORT_OPTION = "port";
unsigned short domainServerPort = DEFAULT_DOMAIN_SERVER_PORT;
QVariantMap& settingsMap = _settingsManager.getSettingsMap();
if (_argumentVariantMap.contains(CUSTOM_PORT_OPTION)) {
domainServerPort = (unsigned short) _argumentVariantMap.value(CUSTOM_PORT_OPTION).toUInt();
if (settingsMap.contains(CUSTOM_PORT_OPTION)) {
domainServerPort = (unsigned short) settingsMap.value(CUSTOM_PORT_OPTION).toUInt();
}
unsigned short domainServerDTLSPort = 0;
@ -183,8 +192,8 @@ void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) {
const QString CUSTOM_DTLS_PORT_OPTION = "dtls-port";
if (_argumentVariantMap.contains(CUSTOM_DTLS_PORT_OPTION)) {
domainServerDTLSPort = (unsigned short) _argumentVariantMap.value(CUSTOM_DTLS_PORT_OPTION).toUInt();
if (settingsMap.contains(CUSTOM_DTLS_PORT_OPTION)) {
domainServerDTLSPort = (unsigned short) settingsMap.value(CUSTOM_DTLS_PORT_OPTION).toUInt();
}
}
@ -197,7 +206,11 @@ void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) {
// set our LimitedNodeList UUID to match the UUID from our config
// nodes will currently use this to add resources to data-web that relate to our domain
nodeList->setSessionUUID(_argumentVariantMap.value(DOMAIN_CONFIG_ID_KEY).toString());
const QString METAVERSE_DOMAIN_ID_KEY_PATH = "metaverse.id";
const QVariant* idValueVariant = valueForKeyPath(settingsMap, METAVERSE_DOMAIN_ID_KEY_PATH);
if (idValueVariant) {
nodeList->setSessionUUID(idValueVariant->toString());
}
connect(nodeList, &LimitedNodeList::nodeAdded, this, &DomainServer::nodeAdded);
connect(nodeList, &LimitedNodeList::nodeKilled, this, &DomainServer::nodeKilled);
@ -260,9 +273,10 @@ bool DomainServer::hasOAuthProviderAndAuthInformation() {
bool DomainServer::optionallySetupAssignmentPayment() {
const QString PAY_FOR_ASSIGNMENTS_OPTION = "pay-for-assignments";
const QVariantMap& settingsMap = _settingsManager.getSettingsMap();
if (_argumentVariantMap.contains(PAY_FOR_ASSIGNMENTS_OPTION) &&
_argumentVariantMap.value(PAY_FOR_ASSIGNMENTS_OPTION).toBool() &&
if (settingsMap.contains(PAY_FOR_ASSIGNMENTS_OPTION) &&
settingsMap.value(PAY_FOR_ASSIGNMENTS_OPTION).toBool() &&
hasOAuthProviderAndAuthInformation()) {
qDebug() << "Assignments will be paid for via" << qPrintable(_oauthProviderURL.toString());
@ -288,8 +302,10 @@ bool DomainServer::optionallySetupAssignmentPayment() {
void DomainServer::setupDynamicIPAddressUpdating() {
const QString ENABLE_DYNAMIC_IP_UPDATING_OPTION = "update-ip";
if (_argumentVariantMap.contains(ENABLE_DYNAMIC_IP_UPDATING_OPTION) &&
_argumentVariantMap.value(ENABLE_DYNAMIC_IP_UPDATING_OPTION).toBool() &&
const QVariantMap& settingsMap = _settingsManager.getSettingsMap();
if (settingsMap.contains(ENABLE_DYNAMIC_IP_UPDATING_OPTION) &&
settingsMap.value(ENABLE_DYNAMIC_IP_UPDATING_OPTION).toBool() &&
hasOAuthProviderAndAuthInformation()) {
LimitedNodeList* nodeList = LimitedNodeList::getInstance();
@ -338,9 +354,11 @@ void DomainServer::parseAssignmentConfigs(QSet<Assignment::Type>& excludedTypes)
// check for configs from the command line, these take precedence
const QString ASSIGNMENT_CONFIG_REGEX_STRING = "config-([\\d]+)";
QRegExp assignmentConfigRegex(ASSIGNMENT_CONFIG_REGEX_STRING);
const QVariantMap& settingsMap = _settingsManager.getSettingsMap();
// scan for assignment config keys
QStringList variantMapKeys = _argumentVariantMap.keys();
QStringList variantMapKeys = settingsMap.keys();
int configIndex = variantMapKeys.indexOf(assignmentConfigRegex);
while (configIndex != -1) {
@ -348,7 +366,7 @@ void DomainServer::parseAssignmentConfigs(QSet<Assignment::Type>& excludedTypes)
Assignment::Type assignmentType = (Assignment::Type) assignmentConfigRegex.cap(1).toInt();
if (assignmentType < Assignment::AllTypes && !excludedTypes.contains(assignmentType)) {
QVariant mapValue = _argumentVariantMap[variantMapKeys[configIndex]];
QVariant mapValue = settingsMap[variantMapKeys[configIndex]];
QJsonArray assignmentArray;
if (mapValue.type() == QVariant::String) {
@ -513,7 +531,7 @@ void DomainServer::handleConnectRequest(const QByteArray& packet, const HifiSock
QString connectedUsername;
if (!isAssignment && !_oauthProviderURL.isEmpty() && _argumentVariantMap.contains(ALLOWED_ROLES_CONFIG_KEY)) {
if (!isAssignment && !_oauthProviderURL.isEmpty() && _settingsManager.getSettingsMap().contains(ALLOWED_ROLES_CONFIG_KEY)) {
// this is an Agent, and we require authentication so we can compare the user's roles to our list of allowed ones
if (_sessionAuthenticationHash.contains(packetUUID)) {
connectedUsername = _sessionAuthenticationHash.take(packetUUID);
@ -1388,12 +1406,15 @@ bool DomainServer::isAuthenticatedRequest(HTTPConnection* connection, const QUrl
const QByteArray HTTP_COOKIE_HEADER_KEY = "Cookie";
const QString ADMIN_USERS_CONFIG_KEY = "admin-users";
const QString ADMIN_ROLES_CONFIG_KEY = "admin-roles";
const QString BASIC_AUTH_CONFIG_KEY = "basic-auth";
const QString BASIC_AUTH_USERNAME_KEY_PATH = "security.http-username";
const QString BASIC_AUTH_PASSWORD_KEY_PATH = "security.http-password";
const QByteArray UNAUTHENTICATED_BODY = "You do not have permission to access this domain-server.";
QVariantMap& settingsMap = _settingsManager.getSettingsMap();
if (!_oauthProviderURL.isEmpty()
&& (_argumentVariantMap.contains(ADMIN_USERS_CONFIG_KEY) || _argumentVariantMap.contains(ADMIN_ROLES_CONFIG_KEY))) {
&& (settingsMap.contains(ADMIN_USERS_CONFIG_KEY) || settingsMap.contains(ADMIN_ROLES_CONFIG_KEY))) {
QString cookieString = connection->requestHeaders().value(HTTP_COOKIE_HEADER_KEY);
const QString COOKIE_UUID_REGEX_STRING = HIFI_SESSION_COOKIE_KEY + "=([\\d\\w-]+)($|;)";
@ -1404,7 +1425,7 @@ bool DomainServer::isAuthenticatedRequest(HTTPConnection* connection, const QUrl
cookieUUID = cookieUUIDRegex.cap(1);
}
if (_argumentVariantMap.contains(BASIC_AUTH_CONFIG_KEY)) {
if (valueForKeyPath(settingsMap, BASIC_AUTH_USERNAME_KEY_PATH)) {
qDebug() << "Config file contains web admin settings for OAuth and basic HTTP authentication."
<< "These cannot be combined - using OAuth for authentication.";
}
@ -1414,13 +1435,13 @@ bool DomainServer::isAuthenticatedRequest(HTTPConnection* connection, const QUrl
DomainServerWebSessionData sessionData = _cookieSessionHash.value(cookieUUID);
QString profileUsername = sessionData.getUsername();
if (_argumentVariantMap.value(ADMIN_USERS_CONFIG_KEY).toJsonValue().toArray().contains(profileUsername)) {
if (settingsMap.value(ADMIN_USERS_CONFIG_KEY).toJsonValue().toArray().contains(profileUsername)) {
// this is an authenticated user
return true;
}
// loop the roles of this user and see if they are in the admin-roles array
QJsonArray adminRolesArray = _argumentVariantMap.value(ADMIN_ROLES_CONFIG_KEY).toJsonValue().toArray();
QJsonArray adminRolesArray = settingsMap.value(ADMIN_ROLES_CONFIG_KEY).toJsonValue().toArray();
if (!adminRolesArray.isEmpty()) {
foreach(const QString& userRole, sessionData.getRoles()) {
@ -1455,7 +1476,7 @@ bool DomainServer::isAuthenticatedRequest(HTTPConnection* connection, const QUrl
// we don't know about this user yet, so they are not yet authenticated
return false;
}
} else if (_argumentVariantMap.contains(BASIC_AUTH_CONFIG_KEY)) {
} else if (valueForKeyPath(settingsMap, BASIC_AUTH_USERNAME_KEY_PATH)) {
// config file contains username and password combinations for basic auth
const QByteArray BASIC_AUTH_HEADER_KEY = "Authorization";
@ -1470,21 +1491,16 @@ bool DomainServer::isAuthenticatedRequest(HTTPConnection* connection, const QUrl
if (!credentialString.isEmpty()) {
QStringList credentialList = credentialString.split(':');
if (credentialList.size() == 2) {
QString username = credentialList[0];
QString password = credentialList[1];
QString headerUsername = credentialList[0];
QString headerPassword = credentialList[1];
// we've pulled a username and password - now check if there is a match in our basic auth hash
QJsonObject basicAuthObject = _argumentVariantMap.value(BASIC_AUTH_CONFIG_KEY).toJsonValue().toObject();
QString settingsUsername = valueForKeyPath(settingsMap, BASIC_AUTH_USERNAME_KEY_PATH)->toString();
const QVariant* settingsPasswordVariant = valueForKeyPath(settingsMap, BASIC_AUTH_PASSWORD_KEY_PATH);
QString settingsPassword = settingsPasswordVariant ? settingsPasswordVariant->toString() : "";
if (basicAuthObject.contains(username)) {
const QString BASIC_AUTH_USER_PASSWORD_KEY = "password";
QJsonObject userObject = basicAuthObject.value(username).toObject();
if (userObject.contains(BASIC_AUTH_USER_PASSWORD_KEY)
&& userObject.value(BASIC_AUTH_USER_PASSWORD_KEY).toString() == password) {
// this is username / password match - let this user in
return true;
}
if (settingsUsername == headerUsername && headerPassword == settingsPassword) {
return true;
}
}
}
@ -1557,7 +1573,8 @@ void DomainServer::handleProfileRequestFinished() {
// pull the user roles from the response
QJsonArray userRolesArray = profileJSON.object()["data"].toObject()["user"].toObject()["roles"].toArray();
QJsonArray allowedRolesArray = _argumentVariantMap.value(ALLOWED_ROLES_CONFIG_KEY).toJsonValue().toArray();
QJsonArray allowedRolesArray = _settingsManager.getSettingsMap()
.value(ALLOWED_ROLES_CONFIG_KEY).toJsonValue().toArray();
QString connectableUsername;
QString profileUsername = profileJSON.object()["data"].toObject()["user"].toObject()["username"].toString();

View file

@ -35,16 +35,17 @@
typedef QSharedPointer<Assignment> SharedAssignmentPointer;
typedef QMultiHash<QUuid, WalletTransaction*> TransactionHash;
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 handleHTTPSRequest(HTTPSConnection* connection, const QUrl& url);
void exit(int retCode = 0);
public slots:
/// Called by NodeList to inform us a node has been added
void nodeAdded(SharedNodePointer node);
@ -53,6 +54,8 @@ public slots:
void transactionJSONCallback(const QJsonObject& data);
void restart();
private slots:
void loginFailed();
void readAvailableDatagrams();
@ -115,8 +118,6 @@ private:
QHash<QUuid, PendingAssignedNodeData*> _pendingAssignedNodes;
TransactionHash _pendingAssignmentCredits;
QVariantMap _argumentVariantMap;
bool _isUsingDTLS;
QUrl _oauthProviderURL;

View file

@ -10,45 +10,44 @@
//
#include <QtCore/QCoreApplication>
#include <QtCore/QDir>
#include <QtCore/QFile>
#include <QtCore/QJsonArray>
#include <QtCore/QJsonObject>
#include <QtCore/QStandardPaths>
#include <QtCore/QUrl>
#include <QtCore/QUrlQuery>
#include <Assignment.h>
#include <HifiConfigVariantMap.h>
#include <HTTPConnection.h>
#include "DomainServerSettingsManager.h"
const QString SETTINGS_DESCRIPTION_RELATIVE_PATH = "/resources/web/settings/describe.json";
const QString SETTINGS_JSON_FILE_RELATIVE_PATH = "/resources/settings.json";
const QString SETTINGS_DESCRIPTION_RELATIVE_PATH = "/resources/describe-settings.json";
DomainServerSettingsManager::DomainServerSettingsManager() :
_descriptionObject(),
_descriptionArray(),
_settingsMap()
{
// load the description object from the settings description
QFile descriptionFile(QCoreApplication::applicationDirPath() + SETTINGS_DESCRIPTION_RELATIVE_PATH);
descriptionFile.open(QIODevice::ReadOnly);
_descriptionObject = QJsonDocument::fromJson(descriptionFile.readAll()).object();
// load the existing config file to get the current values
QFile configFile(QCoreApplication::applicationDirPath() + SETTINGS_JSON_FILE_RELATIVE_PATH);
if (configFile.exists()) {
configFile.open(QIODevice::ReadOnly);
_settingsMap = QJsonDocument::fromJson(configFile.readAll()).toVariant().toMap();
}
_descriptionArray = QJsonDocument::fromJson(descriptionFile.readAll()).array();
}
const QString DESCRIPTION_SETTINGS_KEY = "settings";
const QString SETTING_DEFAULT_KEY = "default";
void DomainServerSettingsManager::loadSettingsMap(const QStringList& argumentList) {
_settingsMap = HifiConfigVariantMap::mergeMasterConfigWithUserConfig(argumentList);
// figure out where we are supposed to persist our settings to
_settingsFilepath = HifiConfigVariantMap::userConfigFilepath(argumentList);
}
const QString SETTINGS_PATH = "/settings.json";
bool DomainServerSettingsManager::handlePublicHTTPRequest(HTTPConnection* connection, const QUrl &url) {
if (connection->requestOperation() == QNetworkAccessManager::GetOperation && url.path() == "/settings.json") {
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
@ -56,77 +55,28 @@ bool DomainServerSettingsManager::handlePublicHTTPRequest(HTTPConnection* connec
QUrlQuery settingsQuery(url);
QString typeValue = settingsQuery.queryItemValue(SETTINGS_TYPE_QUERY_KEY);
QJsonObject responseObject;
if (typeValue.isEmpty()) {
// combine the description object and our current settings map
responseObject["descriptions"] = _descriptionObject;
responseObject["values"] = QJsonDocument::fromVariant(_settingsMap).object();
if (!typeValue.isEmpty()) {
QJsonObject responseObject = responseObjectForType(typeValue);
connection->respond(HTTPConnection::StatusCode200, QJsonDocument(responseObject).toJson(), "application/json");
return true;
} else {
// convert the string type value to a QJsonValue
QJsonValue queryType = 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 QString& group, _descriptionObject.keys()) {
QJsonObject groupObject = _descriptionObject[group].toObject();
QJsonObject groupSettingsObject = groupObject[DESCRIPTION_SETTINGS_KEY].toObject();
QJsonObject groupResponseObject;
foreach(const QString& settingKey, groupSettingsObject.keys()) {
QJsonObject settingObject = groupSettingsObject[settingKey].toObject();
QJsonArray affectedTypesArray = settingObject[AFFECTED_TYPES_JSON_KEY].toArray();
if (affectedTypesArray.isEmpty()) {
affectedTypesArray = groupObject[AFFECTED_TYPES_JSON_KEY].toArray();
}
if (affectedTypesArray.contains(queryType)) {
// this is a setting we should include in the responseObject
// we need to check if the settings map has a value for this setting
QVariant variantValue;
QVariant settingsMapGroupValue = _settingsMap.value(group);
if (!settingsMapGroupValue.isNull()) {
variantValue = settingsMapGroupValue.toMap().value(settingKey);
}
if (variantValue.isNull()) {
// no value for this setting, pass the default
groupResponseObject[settingKey] = settingObject[SETTING_DEFAULT_KEY];
} else {
groupResponseObject[settingKey] = QJsonValue::fromVariant(variantValue);
}
}
}
if (!groupResponseObject.isEmpty()) {
// set this group's object to the constructed object
responseObject[group] = groupResponseObject;
}
}
return false;
}
connection->respond(HTTPConnection::StatusCode200, QJsonDocument(responseObject).toJson(), "application/json");
return true;
}
return false;
}
bool DomainServerSettingsManager::handleAuthenticatedHTTPRequest(HTTPConnection *connection, const QUrl &url) {
if (connection->requestOperation() == QNetworkAccessManager::PostOperation && url.path() == "/settings.json") {
if (connection->requestOperation() == QNetworkAccessManager::PostOperation && url.path() == SETTINGS_PATH) {
// this is a POST operation to change one or more settings
QJsonDocument postedDocument = QJsonDocument::fromJson(connection->requestContent());
QJsonObject postedObject = postedDocument.object();
// we recurse one level deep below each group for the appropriate setting
recurseJSONObjectAndOverwriteSettings(postedObject, _settingsMap, _descriptionObject);
recurseJSONObjectAndOverwriteSettings(postedObject, _settingsMap, _descriptionArray);
// store whatever the current _settingsMap is to file
persistToFile();
@ -135,56 +85,153 @@ bool DomainServerSettingsManager::handleAuthenticatedHTTPRequest(HTTPConnection
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";
QJsonObject rootObject;
rootObject[SETTINGS_RESPONSE_DESCRIPTION_KEY] = _descriptionArray;
rootObject[SETTINGS_RESPONSE_VALUE_KEY] = responseObjectForType("", true);
connection->respond(HTTPConnection::StatusCode200, QJsonDocument(rootObject).toJson(), "application/json");
}
return false;
}
const QString DESCRIPTION_SETTINGS_KEY = "settings";
const QString SETTING_DEFAULT_KEY = "default";
const QString DESCRIPTION_NAME_KEY = "name";
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 = _settingsMap.value(groupObject[DESCRIPTION_NAME_KEY].toString());
if (!settingsMapGroupValue.isNull()) {
variantValue = settingsMapGroupValue.toMap().value(settingName);
}
if (variantValue.isNull()) {
// no value for this setting, pass the default
if (settingObject.contains(SETTING_DEFAULT_KEY)) {
groupResponseObject[settingName] = 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("");
}
} else {
groupResponseObject[settingName] = QJsonValue::fromVariant(variantValue);
}
}
}
}
if (!groupResponseObject.isEmpty()) {
// set this group's object to the constructed object
responseObject[groupKey] = groupResponseObject;
}
}
}
return responseObject;
}
const QString SETTING_DESCRIPTION_TYPE_KEY = "type";
void DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject,
QVariantMap& settingsVariant,
QJsonObject descriptionObject) {
QJsonArray descriptionArray) {
foreach(const QString& key, postedObject.keys()) {
QJsonValue rootValue = postedObject[key];
// we don't continue if this key is not present in our descriptionObject
if (descriptionObject.contains(key)) {
if (rootValue.isString()) {
if (rootValue.toString().isEmpty()) {
// this is an empty value, clear it in settings variant so the default is sent
settingsVariant.remove(key);
} else {
if (descriptionObject[key].toObject().contains(SETTING_DESCRIPTION_TYPE_KEY)) {
// for now this means that this is a double, so set it as a double
settingsVariant[key] = rootValue.toString().toDouble();
} else {
settingsVariant[key] = rootValue.toString();
}
}
} else if (rootValue.isBool()) {
settingsVariant[key] = rootValue.toBool();
} else if (rootValue.isObject()) {
// there's a JSON Object to explore, so attempt to recurse into it
QJsonObject nextDescriptionObject = descriptionObject[key].toObject();
if (nextDescriptionObject.contains(DESCRIPTION_SETTINGS_KEY)) {
if (!settingsVariant.contains(key)) {
// we don't have a map below this key yet, so set it up now
settingsVariant[key] = QVariantMap();
}
QVariantMap& thisMap = *reinterpret_cast<QVariantMap*>(settingsVariant[key].data());
recurseJSONObjectAndOverwriteSettings(rootValue.toObject(),
thisMap,
nextDescriptionObject[DESCRIPTION_SETTINGS_KEY].toObject());
if (thisMap.isEmpty()) {
// we've cleared all of the settings below this value, so remove this one too
foreach(const QJsonValue& groupValue, descriptionArray) {
if (groupValue.toObject()[DESCRIPTION_NAME_KEY].toString() == key) {
QJsonObject groupObject = groupValue.toObject();
if (rootValue.isString()) {
if (rootValue.toString().isEmpty()) {
// this is an empty value, clear it in settings variant so the default is sent
settingsVariant.remove(key);
} else {
QString settingType = groupObject[SETTING_DESCRIPTION_TYPE_KEY].toString();
const QString INPUT_DOUBLE_TYPE = "double";
// make sure the resulting json value has the right type
if (settingType == INPUT_DOUBLE_TYPE) {
settingsVariant[key] = rootValue.toString().toDouble();
} else {
settingsVariant[key] = rootValue.toString();
}
}
} else if (rootValue.isBool()) {
settingsVariant[key] = rootValue.toBool();
} else if (rootValue.isObject()) {
// there's a JSON Object to explore, so attempt to recurse into it
QJsonObject nextDescriptionObject = groupObject;
if (nextDescriptionObject.contains(DESCRIPTION_SETTINGS_KEY)) {
if (!settingsVariant.contains(key)) {
// we don't have a map below this key yet, so set it up now
settingsVariant[key] = QVariantMap();
}
QVariantMap& thisMap = *reinterpret_cast<QVariantMap*>(settingsVariant[key].data());
recurseJSONObjectAndOverwriteSettings(rootValue.toObject(),
thisMap,
nextDescriptionObject[DESCRIPTION_SETTINGS_KEY].toArray());
if (thisMap.isEmpty()) {
// we've cleared all of the settings below this value, so remove this one too
settingsVariant.remove(key);
}
}
}
}
@ -197,7 +244,15 @@ QByteArray DomainServerSettingsManager::getJSONSettingsMap() const {
}
void DomainServerSettingsManager::persistToFile() {
QFile settingsFile(QCoreApplication::applicationDirPath() + SETTINGS_JSON_FILE_RELATIVE_PATH);
// make sure we have the dir the settings file is supposed to live in
QFileInfo settingsFileInfo(_settingsFilepath);
if (!settingsFileInfo.dir().exists()) {
settingsFileInfo.dir().mkpath(".");
}
QFile settingsFile(_settingsFilepath);
if (settingsFile.open(QIODevice::WriteOnly)) {
settingsFile.write(getJSONSettingsMap());

View file

@ -12,6 +12,7 @@
#ifndef hifi_DomainServerSettingsManager_h
#define hifi_DomainServerSettingsManager_h
#include <QtCore/QJsonArray>
#include <QtCore/QJsonDocument>
#include <HTTPManager.h>
@ -23,14 +24,19 @@ public:
bool handlePublicHTTPRequest(HTTPConnection* connection, const QUrl& url);
bool handleAuthenticatedHTTPRequest(HTTPConnection* connection, const QUrl& url);
void loadSettingsMap(const QStringList& argumentList);
QByteArray getJSONSettingsMap() const;
QVariantMap& getSettingsMap() { return _settingsMap; }
private:
QJsonObject responseObjectForType(const QString& typeValue, bool isAuthenticated = false);
void recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject, QVariantMap& settingsVariant,
QJsonObject descriptionObject);
QJsonArray descriptionArray);
void persistToFile();
QJsonObject _descriptionObject;
QJsonArray _descriptionArray;
QVariantMap _settingsMap;
QString _settingsFilepath;
};
#endif // hifi_DomainServerSettingsManager_h

View file

@ -28,8 +28,16 @@ int main(int argc, char* argv[]) {
#endif
qInstallMessageHandler(Logging::verboseMessageHandler);
DomainServer domainServer(argc, argv);
return domainServer.exec();
int currentExitCode = 0;
// use a do-while to handle domain-server restart
do {
DomainServer domainServer(argc, argv);
currentExitCode = domainServer.exec();
} while (currentExitCode == DomainServer::EXIT_CODE_REBOOT);
return currentExitCode;
}

View file

@ -2907,8 +2907,6 @@ function handeMenuEvent(menuItem) {
index++;
array.push({ label: "Linear Damping:", value: properties.damping.toFixed(decimals) });
index++;
array.push({ label: "Mass:", value: properties.mass.toFixed(decimals) });
index++;
array.push({ label: "Angular Pitch:", value: properties.angularVelocity.x.toFixed(decimals) });
index++;
array.push({ label: "Angular Yaw:", value: properties.angularVelocity.y.toFixed(decimals) });
@ -2925,6 +2923,15 @@ function handeMenuEvent(menuItem) {
array.push({ label: "Gravity Z:", value: properties.gravity.z.toFixed(decimals) });
index++;
array.push({ label: "Collisions:", type: "header" });
index++;
array.push({ label: "Mass:", value: properties.mass.toFixed(decimals) });
index++;
array.push({ label: "Ignore for Collisions:", value: properties.ignoreForCollisions });
index++;
array.push({ label: "Collisions Will Move:", value: properties.collisionsWillMove });
index++;
array.push({ label: "Lifetime:", value: properties.lifetime.toFixed(decimals) });
index++;
@ -3055,7 +3062,6 @@ Window.nonBlockingFormClosed.connect(function() {
properties.velocity.y = array[index++].value;
properties.velocity.z = array[index++].value;
properties.damping = array[index++].value;
properties.mass = array[index++].value;
properties.angularVelocity.x = array[index++].value;
properties.angularVelocity.y = array[index++].value;
@ -3065,6 +3071,12 @@ Window.nonBlockingFormClosed.connect(function() {
properties.gravity.x = array[index++].value;
properties.gravity.y = array[index++].value;
properties.gravity.z = array[index++].value;
index++; // skip header
properties.mass = array[index++].value;
properties.ignoreForCollisions = array[index++].value;
properties.collisionsWillMove = array[index++].value;
properties.lifetime = array[index++].value;
properties.visible = array[index++].value;

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 49 49" enable-background="new 0 0 49 49" xml:space="preserve">
<g>
<g>
<path fill="#333" d="M49,46c0,1.7-1.3,3-3,3H3c-1.7,0-3-1.3-3-3V3c0-1.7,1.3-3,3-3h43c1.7,0,3,1.3,3,3V46z"/>
</g>
<g>
<g id="Your_Icon_11_">
<g>
<polygon fill="#E7EEEE" points="23.6,19.9 15.1,19.9 15.1,27.8 23.6,27.8 23.6,33.8 33,23.9 23.6,14 "/>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 759 B

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 49 49" enable-background="new 0 0 49 49" xml:space="preserve">
<g>
<g>
<path fill="#0E7077" d="M49,46c0,1.7-1.3,3-3,3H3c-1.7,0-3-1.3-3-3V3c0-1.7,1.3-3,3-3h43c1.7,0,3,1.3,3,3V46z"/>
</g>
<g>
<g id="Your_Icon_11_">
<g>
<polygon fill="#E7EEEE" points="23.6,19.9 15.1,19.9 15.1,27.8 23.6,27.8 23.6,33.8 33,23.9 23.6,14 "/>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 762 B

View file

@ -680,7 +680,7 @@ void Application::paintGL() {
if (whichCamera.getMode() == CAMERA_MODE_MIRROR) {
OculusManager::display(whichCamera.getRotation(), whichCamera.getPosition(), whichCamera);
} else {
OculusManager::display(_myAvatar->getWorldAlignedOrientation(), whichCamera.getPosition(), whichCamera);
OculusManager::display(_myAvatar->getWorldAlignedOrientation(), _myAvatar->getDefaultEyePosition(), whichCamera);
}
} else if (TV3DManager::isConnected()) {
@ -921,12 +921,11 @@ void Application::keyPressEvent(QKeyEvent* event) {
case Qt::Key_Return:
case Qt::Key_Enter:
if (isMeta) {
Menu::getInstance()->triggerOption(MenuOption::AddressBar);
} else {
Menu::getInstance()->triggerOption(MenuOption::Chat);
}
Menu::getInstance()->triggerOption(MenuOption::AddressBar);
break;
case Qt::Key_Backslash:
Menu::getInstance()->triggerOption(MenuOption::Chat);
break;
case Qt::Key_N:

View file

@ -176,7 +176,7 @@ Menu::Menu() :
SLOT(toggleLocationList()));
addActionToQMenuAndActionHash(fileMenu,
MenuOption::AddressBar,
Qt::CTRL | Qt::Key_Enter,
Qt::Key_Enter,
this,
SLOT(toggleAddressBar()));
@ -1156,22 +1156,13 @@ void Menu::changePrivateKey() {
}
void Menu::toggleAddressBar() {
QInputDialog addressBarDialog(Application::getInstance()->getWindow());
addressBarDialog.setWindowTitle("Address Bar");
addressBarDialog.setWindowFlags(Qt::Sheet);
addressBarDialog.setLabelText("place, domain, @user, example.com, /position/orientation");
addressBarDialog.resize(addressBarDialog.parentWidget()->size().width() * DIALOG_RATIO_OF_WINDOW,
addressBarDialog.size().height());
int dialogReturn = addressBarDialog.exec();
if (dialogReturn == QDialog::Accepted && !addressBarDialog.textValue().isEmpty()) {
// let the AddressManger figure out what to do with this
AddressManager::getInstance().handleLookupString(addressBarDialog.textValue());
if (!_addressBarDialog) {
_addressBarDialog = new AddressBarDialog();
}
sendFakeEnterEvent();
if (!_addressBarDialog->isVisible()) {
_addressBarDialog->show();
}
}
void Menu::displayAddressOfflineMessage() {

View file

@ -27,6 +27,7 @@
#include "SpeechRecognizer.h"
#endif
#include "ui/AddressBarDialog.h"
#include "ui/ChatWindow.h"
#include "ui/DataWebDialog.h"
#include "ui/JSConsole.h"
@ -298,6 +299,7 @@ private:
QPointer<PreferencesDialog> _preferencesDialog;
QPointer<AttachmentsDialog> _attachmentsDialog;
QPointer<AnimationsDialog> _animationsDialog;
QPointer<AddressBarDialog> _addressBarDialog;
QPointer<LoginDialog> _loginDialog;
bool _hasLoginDialogDisplayed;
QAction* _chatAction;

View file

@ -505,10 +505,11 @@ void Avatar::renderBody(RenderMode renderMode, bool postLighting, float glowLeve
{
Glower glower(glowLevel);
if ((_shouldRenderBillboard || !(_skeletonModel.isRenderable() && getHead()->getFaceModel().isRenderable())) &&
(postLighting || renderMode == SHADOW_RENDER_MODE)) {
// render the billboard until both models are loaded
renderBillboard();
if (_shouldRenderBillboard || !(_skeletonModel.isRenderable() && getHead()->getFaceModel().isRenderable())) {
if (postLighting || renderMode == SHADOW_RENDER_MODE) {
// render the billboard until both models are loaded
renderBillboard();
}
return;
}

View file

@ -1010,6 +1010,10 @@ glm::vec3 MyAvatar::getUprightHeadPosition() const {
return _position + getWorldAlignedOrientation() * glm::vec3(0.0f, getPelvisToHeadLength(), 0.0f);
}
glm::vec3 MyAvatar::getDefaultEyePosition() const {
return _position + getWorldAlignedOrientation() * _skeletonModel.getDefaultEyeModelPosition();
}
const float SCRIPT_PRIORITY = DEFAULT_PRIORITY + 1.0f;
const float RECORDER_PRIORITY = SCRIPT_PRIORITY + 1.0f;

View file

@ -66,6 +66,7 @@ public:
const glm::vec3& getMouseRayDirection() const { return _mouseRayDirection; }
glm::vec3 getGravity() const { return _gravity; }
glm::vec3 getUprightHeadPosition() const;
glm::vec3 getDefaultEyePosition() const;
bool getShouldRenderLocally() const { return _shouldRender; }
const QList<AnimationHandlePointer>& getAnimationHandles() const { return _animationHandles; }

View file

@ -27,7 +27,8 @@ SkeletonModel::SkeletonModel(Avatar* owningAvatar, QObject* parent) :
_owningAvatar(owningAvatar),
_boundingShape(),
_boundingShapeLocalOffset(0.0f),
_ragdoll(NULL) {
_ragdoll(NULL),
_defaultEyeModelPosition(glm::vec3(0.f, 0.f, 0.f)) {
}
SkeletonModel::~SkeletonModel() {
@ -470,23 +471,23 @@ bool SkeletonModel::getNeckParentRotationFromDefaultOrientation(glm::quat& neckP
return success;
}
bool SkeletonModel::getEyePositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition) const {
bool SkeletonModel::getEyeModelPositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition) const {
if (!isActive()) {
return false;
}
}
const FBXGeometry& geometry = _geometry->getFBXGeometry();
if (getJointPositionInWorldFrame(geometry.leftEyeJointIndex, firstEyePosition) &&
getJointPositionInWorldFrame(geometry.rightEyeJointIndex, secondEyePosition)) {
if (getJointPosition(geometry.leftEyeJointIndex, firstEyePosition) &&
getJointPosition(geometry.rightEyeJointIndex, secondEyePosition)) {
return true;
}
// no eye joints; try to estimate based on head/neck joints
glm::vec3 neckPosition, headPosition;
if (getJointPositionInWorldFrame(geometry.neckJointIndex, neckPosition) &&
getJointPositionInWorldFrame(geometry.headJointIndex, headPosition)) {
if (getJointPosition(geometry.neckJointIndex, neckPosition) &&
getJointPosition(geometry.headJointIndex, headPosition)) {
const float EYE_PROPORTION = 0.6f;
glm::vec3 baseEyePosition = glm::mix(neckPosition, headPosition, EYE_PROPORTION);
glm::quat headRotation;
getJointRotationInWorldFrame(geometry.headJointIndex, headRotation);
getJointRotation(geometry.headJointIndex, headRotation);
const float EYES_FORWARD = 0.25f;
const float EYE_SEPARATION = 0.1f;
float headHeight = glm::distance(neckPosition, headPosition);
@ -497,6 +498,15 @@ bool SkeletonModel::getEyePositions(glm::vec3& firstEyePosition, glm::vec3& seco
return false;
}
bool SkeletonModel::getEyePositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition) const {
if (getEyeModelPositions(firstEyePosition, secondEyePosition)) {
firstEyePosition = _translation + _rotation * firstEyePosition;
secondEyePosition = _translation + _rotation * secondEyePosition;
return true;
}
return false;
}
void SkeletonModel::renderRagdoll() {
if (!_ragdoll) {
return;
@ -656,6 +666,20 @@ void SkeletonModel::buildShapes() {
// This method moves the shapes to their default positions in Model frame.
computeBoundingShape(geometry);
int headJointIndex = _geometry->getFBXGeometry().headJointIndex;
if (0 <= headJointIndex && headJointIndex < _jointStates.size()) {
glm::vec3 leftEyePosition, rightEyePosition;
getEyeModelPositions(leftEyePosition, rightEyePosition);
glm::vec3 midEyePosition = (leftEyePosition + rightEyePosition) / 2.f;
int rootJointIndex = _geometry->getFBXGeometry().rootJointIndex;
glm::vec3 rootModelPosition;
getJointPosition(rootJointIndex, rootModelPosition);
_defaultEyeModelPosition = midEyePosition - rootModelPosition;
_defaultEyeModelPosition.z = -_defaultEyeModelPosition.z;
}
// While the shapes are in their default position we disable collisions between
// joints that are currently colliding.
disableCurrentSelfCollisions();

View file

@ -97,6 +97,10 @@ public:
/// \return whether or not both eye meshes were found
bool getEyePositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition) const;
/// Gets the default position of the mid eye point in model frame coordinates.
/// \return whether or not the head was found.
glm::vec3 getDefaultEyeModelPosition() const { return _defaultEyeModelPosition; }
virtual void updateVisibleJointStates();
SkeletonRagdoll* buildRagdoll();
@ -140,12 +144,16 @@ private:
/// \param position position of joint in model-frame
/// \param rotation rotation of joint in model-frame
void setHandPosition(int jointIndex, const glm::vec3& position, const glm::quat& rotation);
bool getEyeModelPositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition) const;
Avatar* _owningAvatar;
CapsuleShape _boundingShape;
glm::vec3 _boundingShapeLocalOffset;
SkeletonRagdoll* _ragdoll;
glm::vec3 _defaultEyeModelPosition;
};
#endif // hifi_SkeletonModel_h

View file

@ -602,6 +602,14 @@ bool Model::getJointRotationInWorldFrame(int jointIndex, glm::quat& rotation) co
return true;
}
bool Model::getJointRotation(int jointIndex, glm::quat& rotation) const {
if (jointIndex == -1 || jointIndex >= _jointStates.size()) {
return false;
}
rotation = _jointStates[jointIndex].getRotation();
return true;
}
bool Model::getJointCombinedRotation(int jointIndex, glm::quat& rotation) const {
if (jointIndex == -1 || jointIndex >= _jointStates.size()) {
return false;

View file

@ -148,6 +148,11 @@ public:
/// \return true if joint exists
bool getJointPosition(int jointIndex, glm::vec3& position) const;
/// \param jointIndex index of joint in model structure
/// \param rotation[out] rotation of joint in model-frame
/// \return true if joint exists
bool getJointRotation(int jointIndex, glm::quat& rotation) const;
QStringList getJointNames() const;
AnimationHandlePointer createAnimationHandle();

View file

@ -0,0 +1,131 @@
//
// AddressBarDialog.cpp
// interface/src/ui
//
// Created by Stojce Slavkovski on 9/22/14.
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "AddressBarDialog.h"
#include "AddressManager.h"
#include "Application.h"
const QString ADDRESSBAR_GO_BUTTON_ICON = "images/address-bar-submit.svg";
const QString ADDRESSBAR_GO_BUTTON_ACTIVE_ICON = "images/address-bar-submit-active.svg";
AddressBarDialog::AddressBarDialog() :
FramelessDialog(Application::getInstance()->getWindow(), 0, FramelessDialog::POSITION_TOP)
{
setAttribute(Qt::WA_DeleteOnClose, false);
setupUI();
}
void AddressBarDialog::setupUI() {
const QString DIALOG_STYLESHEET = "font-family: Helvetica, Arial, sans-serif;";
const QString ADDRESSBAR_PLACEHOLDER = "Go to: domain, @user, #location";
const QString ADDRESSBAR_STYLESHEET = "padding: 0 10px;";
const QString ADDRESSBAR_FONT_FAMILY = "Helvetica,Arial,sans-serif";
const int ADDRESSBAR_FONT_SIZE = 20;
const int ADDRESSBAR_MIN_WIDTH = 200;
const int ADDRESSBAR_MAX_WIDTH = 615;
const int ADDRESSBAR_HEIGHT = 54;
const int ADDRESSBAR_STRETCH = 60;
const int BUTTON_SPACER_SIZE = 10;
const int DEFAULT_SPACER_SIZE = 20;
const int ADDRESS_LAYOUT_RIGHT_MARGIN = 10;
const int GO_BUTTON_SIZE = 55;
const int CLOSE_BUTTON_SIZE = 16;
const QString CLOSE_BUTTON_ICON = "styles/close.svg";
const int DIALOG_HEIGHT = 100;
const int DIALOG_INITIAL_WIDTH = 560;
setModal(true);
setWindowModality(Qt::WindowModal);
setHideOnBlur(false);
QSizePolicy sizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
setSizePolicy(sizePolicy);
setMinimumSize(QSize(DIALOG_INITIAL_WIDTH, DIALOG_HEIGHT));
setStyleSheet(DIALOG_STYLESHEET);
_verticalLayout = new QVBoxLayout(this);
_addressLayout = new QHBoxLayout();
_addressLayout->setContentsMargins(0, 0, ADDRESS_LAYOUT_RIGHT_MARGIN, 0);
_leftSpacer = new QSpacerItem(DEFAULT_SPACER_SIZE,
DEFAULT_SPACER_SIZE,
QSizePolicy::MinimumExpanding,
QSizePolicy::Minimum);
_addressLayout->addItem(_leftSpacer);
_addressLineEdit = new QLineEdit(this);
_addressLineEdit->setPlaceholderText(ADDRESSBAR_PLACEHOLDER);
QSizePolicy sizePolicyLineEdit(QSizePolicy::Preferred, QSizePolicy::Fixed);
sizePolicyLineEdit.setHorizontalStretch(ADDRESSBAR_STRETCH);
_addressLineEdit->setSizePolicy(sizePolicyLineEdit);
_addressLineEdit->setMinimumSize(QSize(ADDRESSBAR_MIN_WIDTH, ADDRESSBAR_HEIGHT));
_addressLineEdit->setMaximumSize(QSize(ADDRESSBAR_MAX_WIDTH, ADDRESSBAR_HEIGHT));
QFont font(ADDRESSBAR_FONT_FAMILY, ADDRESSBAR_FONT_SIZE);
_addressLineEdit->setFont(font);
_addressLineEdit->setStyleSheet(ADDRESSBAR_STYLESHEET);
_addressLayout->addWidget(_addressLineEdit);
_buttonSpacer = new QSpacerItem(BUTTON_SPACER_SIZE, BUTTON_SPACER_SIZE, QSizePolicy::Fixed, QSizePolicy::Minimum);
_addressLayout->addItem(_buttonSpacer);
_goButton = new QPushButton(this);
_goButton->setSizePolicy(sizePolicy);
_goButton->setMinimumSize(QSize(GO_BUTTON_SIZE, GO_BUTTON_SIZE));
_goButton->setMaximumSize(QSize(GO_BUTTON_SIZE, GO_BUTTON_SIZE));
_goButton->setIcon(QIcon(Application::resourcesPath() + ADDRESSBAR_GO_BUTTON_ICON));
_goButton->setIconSize(QSize(GO_BUTTON_SIZE, GO_BUTTON_SIZE));
_goButton->setDefault(true);
_goButton->setFlat(true);
_addressLayout->addWidget(_goButton);
_rightSpacer = new QSpacerItem(DEFAULT_SPACER_SIZE,
DEFAULT_SPACER_SIZE,
QSizePolicy::MinimumExpanding,
QSizePolicy::Minimum);
_addressLayout->addItem(_rightSpacer);
_closeButton = new QPushButton(this);
_closeButton->setSizePolicy(sizePolicy);
_closeButton->setMinimumSize(QSize(CLOSE_BUTTON_SIZE, CLOSE_BUTTON_SIZE));
_closeButton->setMaximumSize(QSize(CLOSE_BUTTON_SIZE, CLOSE_BUTTON_SIZE));
QIcon icon(Application::resourcesPath() + CLOSE_BUTTON_ICON);
_closeButton->setIcon(icon);
_closeButton->setFlat(true);
_addressLayout->addWidget(_closeButton, 0, Qt::AlignRight);
_verticalLayout->addLayout(_addressLayout);
connect(_goButton, &QPushButton::clicked, this, &AddressBarDialog::accept);
connect(_closeButton, &QPushButton::clicked, this, &QDialog::close);
}
void AddressBarDialog::showEvent(QShowEvent* event) {
_goButton->setIcon(QIcon(Application::resourcesPath() + ADDRESSBAR_GO_BUTTON_ICON));
_addressLineEdit->setText(QString());
FramelessDialog::showEvent(event);
}
void AddressBarDialog::accept() {
if (!_addressLineEdit->text().isEmpty()) {
_goButton->setIcon(QIcon(Application::resourcesPath() + ADDRESSBAR_GO_BUTTON_ACTIVE_ICON));
AddressManager& addressManager = AddressManager::getInstance();
connect(&addressManager, &AddressManager::lookupResultsFinished, this, &QDialog::hide);
addressManager.handleLookupString(_addressLineEdit->text());
}
}

View file

@ -0,0 +1,46 @@
//
// AddressBarDialog.h
// interface/src/ui
//
// Created by Stojce Slavkovski on 9/22/14.
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_AddressBarDialog_h
#define hifi_AddressBarDialog_h
#include "FramelessDialog.h"
#include <QLineEdit>
#include <QHBoxLayout>
#include <QSpacerItem>
#include <QVBoxLayout>
class AddressBarDialog : public FramelessDialog {
Q_OBJECT
public:
AddressBarDialog();
private:
void setupUI();
void showEvent(QShowEvent* event);
QVBoxLayout *_verticalLayout;
QHBoxLayout *_addressLayout;
QSpacerItem *_leftSpacer;
QSpacerItem *_rightSpacer;
QSpacerItem *_buttonSpacer;
QPushButton *_goButton;
QPushButton *_closeButton;
QLineEdit *_addressLineEdit;
private slots:
void accept();
};
#endif // hifi_AddressBarDialog_h

View file

@ -78,6 +78,11 @@ void EntityCollisionSystem::emitGlobalEntityCollisionWithEntity(EntityItem* enti
}
void EntityCollisionSystem::updateCollisionWithVoxels(EntityItem* entity) {
if (entity->getIgnoreForCollisions() || !entity->getCollisionsWillMove()) {
return; // bail early if this entity is to be ignored or wont move
}
glm::vec3 center = entity->getPosition() * (float)(TREE_SCALE);
float radius = entity->getRadius() * (float)(TREE_SCALE);
const float ELASTICITY = 0.4f;
@ -105,82 +110,110 @@ void EntityCollisionSystem::updateCollisionWithVoxels(EntityItem* entity) {
}
void EntityCollisionSystem::updateCollisionWithEntities(EntityItem* entityA) {
if (entityA->getIgnoreForCollisions()) {
return; // bail early if this entity is to be ignored...
}
glm::vec3 penetration;
EntityItem* entityB = NULL;
const float MAX_COLLISIONS_PER_ENTITY = 32;
const int MAX_COLLISIONS_PER_ENTITY = 32;
CollisionList collisions(MAX_COLLISIONS_PER_ENTITY);
bool shapeCollisionsAccurate = false;
bool shapeCollisions = _entities->findShapeCollisions(&entityA->getCollisionShapeInMeters(),
collisions, Octree::NoLock, &shapeCollisionsAccurate);
if (shapeCollisions) {
for(int i = 0; i < collisions.size(); i++) {
CollisionInfo* collision = collisions[i];
penetration = collision->_penetration;
entityB = static_cast<EntityItem*>(collision->_extraData);
// TODO: how to handle multiple collisions?
break;
}
}
if (shapeCollisions) {
// NOTE: 'penetration' is the depth that 'entityA' overlaps 'entityB'. It points from A into B.
glm::vec3 penetrationInTreeUnits = penetration / (float)(TREE_SCALE);
// NOTE: 'penetration' is the depth that 'entityA' overlaps 'entityB'. It points from A into B.
glm::vec3 penetrationInTreeUnits = penetration / (float)(TREE_SCALE);
// Even if the Entities overlap... when the Entities are already moving appart
// we don't want to count this as a collision.
glm::vec3 relativeVelocity = entityA->getVelocity() - entityB->getVelocity();
// Even if the Entities overlap... when the Entities are already moving appart
// we don't want to count this as a collision.
glm::vec3 relativeVelocity = entityA->getVelocity() - entityB->getVelocity();
bool fullyEnclosedCollision = glm::length(penetrationInTreeUnits) > entityA->getLargestDimension();
bool movingTowardEachOther = glm::dot(relativeVelocity, penetrationInTreeUnits) > 0.0f;
bool doCollisions = movingTowardEachOther; // don't do collisions if the entities are moving away from each other
bool wantToMoveA = entityA->getCollisionsWillMove();
bool wantToMoveB = entityB->getCollisionsWillMove();
bool movingTowardEachOther = glm::dot(relativeVelocity, penetrationInTreeUnits) > 0.0f;
if (doCollisions) {
quint64 now = usecTimestampNow();
// only do collisions if the entities are moving toward each other and one or the other
// of the entities are movable from collisions
bool doCollisions = !fullyEnclosedCollision && movingTowardEachOther && (wantToMoveA || wantToMoveB);
if (doCollisions) {
quint64 now = usecTimestampNow();
CollisionInfo collision;
collision._penetration = penetration;
// for now the contactPoint is the average between the the two paricle centers
collision._contactPoint = (0.5f * (float)TREE_SCALE) * (entityA->getPosition() + entityB->getPosition());
emitGlobalEntityCollisionWithEntity(entityA, entityB, collision);
CollisionInfo collision;
collision._penetration = penetration;
// for now the contactPoint is the average between the the two paricle centers
collision._contactPoint = (0.5f * (float)TREE_SCALE) * (entityA->getPosition() + entityB->getPosition());
emitGlobalEntityCollisionWithEntity(entityA, entityB, collision);
glm::vec3 axis = glm::normalize(penetration);
glm::vec3 axialVelocity = glm::dot(relativeVelocity, axis) * axis;
glm::vec3 axis = glm::normalize(penetration);
glm::vec3 axialVelocity = glm::dot(relativeVelocity, axis) * axis;
float massA = entityA->getMass();
float massB = entityB->getMass();
float totalMass = massA + massB;
float massA = entityA->getMass();
float massB = entityB->getMass();
float totalMass = massA + massB;
float massRatioA = (2.0f * massB / totalMass);
float massRatioB = (2.0f * massA / totalMass);
// handle Entity A
glm::vec3 newVelocityA = entityA->getVelocity() - axialVelocity * (2.0f * massB / totalMass);
glm::vec3 newPositionA = entityA->getPosition() - 0.5f * penetrationInTreeUnits;
// in the event that one of our entities is non-moving, then fix up these ratios
if (wantToMoveA && !wantToMoveB) {
massRatioA = 2.0f;
massRatioB = 0.0f;
}
EntityItemProperties propertiesA = entityA->getProperties();
EntityItemID idA(entityA->getID());
propertiesA.setVelocity(newVelocityA * (float)TREE_SCALE);
propertiesA.setPosition(newPositionA * (float)TREE_SCALE);
propertiesA.setLastEdited(now);
if (!wantToMoveA && wantToMoveB) {
massRatioA = 0.0f;
massRatioB = 2.0f;
}
_entities->updateEntity(idA, propertiesA);
_packetSender->queueEditEntityMessage(PacketTypeEntityAddOrEdit, idA, propertiesA);
// unless the entity is configured to not be moved by collision, calculate it's new position
// and velocity and apply it
if (wantToMoveA) {
// handle Entity A
glm::vec3 newVelocityA = entityA->getVelocity() - axialVelocity * massRatioA;
glm::vec3 newPositionA = entityA->getPosition() - 0.5f * penetrationInTreeUnits;
glm::vec3 newVelocityB = entityB->getVelocity() + axialVelocity * (2.0f * massA / totalMass);
glm::vec3 newPositionB = entityB->getPosition() + 0.5f * penetrationInTreeUnits;
EntityItemProperties propertiesA = entityA->getProperties();
EntityItemID idA(entityA->getID());
propertiesA.setVelocity(newVelocityA * (float)TREE_SCALE);
propertiesA.setPosition(newPositionA * (float)TREE_SCALE);
propertiesA.setLastEdited(now);
EntityItemProperties propertiesB = entityB->getProperties();
_entities->updateEntity(idA, propertiesA);
_packetSender->queueEditEntityMessage(PacketTypeEntityAddOrEdit, idA, propertiesA);
}
EntityItemID idB(entityB->getID());
propertiesB.setVelocity(newVelocityB * (float)TREE_SCALE);
propertiesB.setPosition(newPositionB * (float)TREE_SCALE);
propertiesB.setLastEdited(now);
// unless the entity is configured to not be moved by collision, calculate it's new position
// and velocity and apply it
if (wantToMoveB) {
glm::vec3 newVelocityB = entityB->getVelocity() + axialVelocity * massRatioB;
glm::vec3 newPositionB = entityB->getPosition() + 0.5f * penetrationInTreeUnits;
_entities->updateEntity(idB, propertiesB);
_packetSender->queueEditEntityMessage(PacketTypeEntityAddOrEdit, idB, propertiesB);
// TODO: Do we need this?
//_packetSender->releaseQueuedMessages();
EntityItemProperties propertiesB = entityB->getProperties();
EntityItemID idB(entityB->getID());
propertiesB.setVelocity(newVelocityB * (float)TREE_SCALE);
propertiesB.setPosition(newPositionB * (float)TREE_SCALE);
propertiesB.setLastEdited(now);
_entities->updateEntity(idB, propertiesB);
_packetSender->queueEditEntityMessage(PacketTypeEntityAddOrEdit, idB, propertiesB);
}
}
}
}
}
@ -192,6 +225,10 @@ void EntityCollisionSystem::updateCollisionWithAvatars(EntityItem* entity) {
return;
}
if (entity->getIgnoreForCollisions() || !entity->getCollisionsWillMove()) {
return; // bail early if this entity is to be ignored or wont move
}
glm::vec3 center = entity->getPosition() * (float)(TREE_SCALE);
float radius = entity->getRadius() * (float)(TREE_SCALE);
const float ELASTICITY = 0.9f;

View file

@ -42,6 +42,8 @@ const glm::vec3 EntityItem::NO_ANGULAR_VELOCITY = glm::vec3(0.0f, 0.0f, 0.0f);
const glm::vec3 EntityItem::DEFAULT_ANGULAR_VELOCITY = NO_ANGULAR_VELOCITY;
const float EntityItem::DEFAULT_ANGULAR_DAMPING = 0.5f;
const bool EntityItem::DEFAULT_VISIBLE = true;
const bool EntityItem::DEFAULT_IGNORE_FOR_COLLISIONS = false;
const bool EntityItem::DEFAULT_COLLISIONS_WILL_MOVE = false;
void EntityItem::initFromEntityItemID(const EntityItemID& entityItemID) {
_id = entityItemID.id;
@ -70,6 +72,8 @@ void EntityItem::initFromEntityItemID(const EntityItemID& entityItemID) {
_angularVelocity = DEFAULT_ANGULAR_VELOCITY;
_angularDamping = DEFAULT_ANGULAR_DAMPING;
_visible = DEFAULT_VISIBLE;
_ignoreForCollisions = DEFAULT_IGNORE_FOR_COLLISIONS;
_collisionsWillMove = DEFAULT_COLLISIONS_WILL_MOVE;
recalculateCollisionShape();
}
@ -111,6 +115,8 @@ EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& param
requestedProperties += PROP_ANGULAR_VELOCITY;
requestedProperties += PROP_ANGULAR_DAMPING;
requestedProperties += PROP_VISIBLE;
requestedProperties += PROP_IGNORE_FOR_COLLISIONS;
requestedProperties += PROP_COLLISIONS_WILL_MOVE;
return requestedProperties;
}
@ -224,6 +230,8 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet
APPEND_ENTITY_PROPERTY(PROP_ANGULAR_VELOCITY, appendValue, getAngularVelocity());
APPEND_ENTITY_PROPERTY(PROP_ANGULAR_DAMPING, appendValue, getAngularDamping());
APPEND_ENTITY_PROPERTY(PROP_VISIBLE, appendValue, getVisible());
APPEND_ENTITY_PROPERTY(PROP_IGNORE_FOR_COLLISIONS, appendValue, getIgnoreForCollisions());
APPEND_ENTITY_PROPERTY(PROP_COLLISIONS_WILL_MOVE, appendValue, getCollisionsWillMove());
appendSubclassData(packetData, params, entityTreeElementExtraEncodeData,
requestedProperties,
@ -484,10 +492,14 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
READ_ENTITY_PROPERTY(PROP_ANGULAR_VELOCITY, glm::vec3, _angularVelocity);
READ_ENTITY_PROPERTY(PROP_ANGULAR_DAMPING, float, _angularDamping);
READ_ENTITY_PROPERTY(PROP_VISIBLE, bool, _visible);
READ_ENTITY_PROPERTY(PROP_IGNORE_FOR_COLLISIONS, bool, _ignoreForCollisions);
READ_ENTITY_PROPERTY(PROP_COLLISIONS_WILL_MOVE, bool, _collisionsWillMove);
if (wantDebug) {
qDebug() << " readEntityDataFromBuffer() _registrationPoint:" << _registrationPoint;
qDebug() << " readEntityDataFromBuffer() _visible:" << _visible;
qDebug() << " readEntityDataFromBuffer() _ignoreForCollisions:" << _ignoreForCollisions;
qDebug() << " readEntityDataFromBuffer() _collisionsWillMove:" << _collisionsWillMove;
}
bytesRead += readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args, propertyFlags, overwriteLocalData);
@ -734,6 +746,8 @@ EntityItemProperties EntityItem::getProperties() const {
COPY_ENTITY_PROPERTY_TO_PROPERTIES(angularDamping, getAngularDamping);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(glowLevel, getGlowLevel);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(visible, getVisible);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(ignoreForCollisions, getIgnoreForCollisions);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(collisionsWillMove, getCollisionsWillMove);
properties._defaultSettings = false;
@ -766,6 +780,8 @@ bool EntityItem::setProperties(const EntityItemProperties& properties, bool forc
SET_ENTITY_PROPERTY_FROM_PROPERTIES(angularDamping, setAngularDamping);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(glowLevel, setGlowLevel);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(visible, setVisible);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(ignoreForCollisions, setIgnoreForCollisions);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(collisionsWillMove, setCollisionsWillMove);
if (somethingChanged) {
somethingChangedNotification(); // notify derived classes that something has changed

View file

@ -230,6 +230,14 @@ public:
void setVisible(bool value) { _visible = value; }
bool isVisible() const { return _visible; }
bool isInvisible() const { return !_visible; }
static const bool DEFAULT_IGNORE_FOR_COLLISIONS;
bool getIgnoreForCollisions() const { return _ignoreForCollisions; }
void setIgnoreForCollisions(bool value) { _ignoreForCollisions = value; }
static const bool DEFAULT_COLLISIONS_WILL_MOVE;
bool getCollisionsWillMove() const { return _collisionsWillMove; }
void setCollisionsWillMove(bool value) { _collisionsWillMove = value; }
// TODO: We need to get rid of these users of getRadius()...
float getRadius() const;
@ -265,6 +273,8 @@ protected:
glm::vec3 _angularVelocity;
float _angularDamping;
bool _visible;
bool _ignoreForCollisions;
bool _collisionsWillMove;
// NOTE: Radius support is obsolete, but these private helper functions are available for this class to
// parse old data streams

View file

@ -41,6 +41,8 @@ EntityItemProperties::EntityItemProperties() :
_angularVelocity(EntityItem::DEFAULT_ANGULAR_VELOCITY),
_angularDamping(EntityItem::DEFAULT_ANGULAR_DAMPING),
_visible(EntityItem::DEFAULT_VISIBLE),
_ignoreForCollisions(EntityItem::DEFAULT_IGNORE_FOR_COLLISIONS),
_collisionsWillMove(EntityItem::DEFAULT_COLLISIONS_WILL_MOVE),
_positionChanged(false),
_dimensionsChanged(false),
@ -55,6 +57,8 @@ EntityItemProperties::EntityItemProperties() :
_angularVelocityChanged(false),
_angularDampingChanged(false),
_visibleChanged(false),
_ignoreForCollisionsChanged(false),
_collisionsWillMoveChanged(false),
_color(),
_modelURL(""),
@ -112,6 +116,8 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
CHECK_PROPERTY_CHANGE(PROP_REGISTRATION_POINT, registrationPoint);
CHECK_PROPERTY_CHANGE(PROP_ANGULAR_VELOCITY, angularVelocity);
CHECK_PROPERTY_CHANGE(PROP_ANGULAR_DAMPING, angularDamping);
CHECK_PROPERTY_CHANGE(PROP_IGNORE_FOR_COLLISIONS, ignoreForCollisions);
CHECK_PROPERTY_CHANGE(PROP_COLLISIONS_WILL_MOVE, collisionsWillMove);
return changedProperties;
}
@ -150,6 +156,8 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine) cons
COPY_PROPERTY_TO_QSCRIPTVALUE(animationFrameIndex);
COPY_PROPERTY_TO_QSCRIPTVALUE(animationFPS);
COPY_PROPERTY_TO_QSCRIPTVALUE(glowLevel);
COPY_PROPERTY_TO_QSCRIPTVALUE(ignoreForCollisions);
COPY_PROPERTY_TO_QSCRIPTVALUE(collisionsWillMove);
// Sitting properties support
QScriptValue sittingPoints = engine->newObject();
@ -194,6 +202,8 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object) {
COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(animationFPS, setAnimationFPS);
COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(animationFrameIndex, setAnimationFrameIndex);
COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(glowLevel, setGlowLevel);
COPY_PROPERTY_FROM_QSCRIPTVALUE_BOOL(ignoreForCollisions, setIgnoreForCollisions);
COPY_PROPERTY_FROM_QSCRIPTVALUE_BOOL(collisionsWillMove, setCollisionsWillMove);
_lastEdited = usecTimestampNow();
}
@ -342,6 +352,8 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem
APPEND_ENTITY_PROPERTY(PROP_ANGULAR_VELOCITY, appendValue, properties.getAngularVelocity());
APPEND_ENTITY_PROPERTY(PROP_ANGULAR_DAMPING, appendValue, properties.getAngularDamping());
APPEND_ENTITY_PROPERTY(PROP_VISIBLE, appendValue, properties.getVisible());
APPEND_ENTITY_PROPERTY(PROP_IGNORE_FOR_COLLISIONS, appendValue, properties.getIgnoreForCollisions());
APPEND_ENTITY_PROPERTY(PROP_COLLISIONS_WILL_MOVE, appendValue, properties.getCollisionsWillMove());
}
if (propertyCount > 0) {
int endOfEntityItemData = packetData->getUncompressedByteOffset();
@ -538,6 +550,8 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ANGULAR_VELOCITY, glm::vec3, setAngularVelocity);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ANGULAR_DAMPING, float, setAngularDamping);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_VISIBLE, bool, setVisible);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_IGNORE_FOR_COLLISIONS, bool, setIgnoreForCollisions);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COLLISIONS_WILL_MOVE, bool, setCollisionsWillMove);
return valid;
}

View file

@ -62,8 +62,10 @@ enum EntityPropertyList {
PROP_REGISTRATION_POINT,
PROP_ANGULAR_VELOCITY,
PROP_ANGULAR_DAMPING,
PROP_IGNORE_FOR_COLLISIONS,
PROP_COLLISIONS_WILL_MOVE,
PROP_LAST_ITEM = PROP_ANGULAR_DAMPING
PROP_LAST_ITEM = PROP_COLLISIONS_WILL_MOVE
};
typedef PropertyFlags<EntityPropertyList> EntityPropertyFlags;
@ -221,6 +223,12 @@ public:
bool getVisible() const { return _visible; }
void setVisible(bool value) { _visible = value; _visibleChanged = true; }
bool getIgnoreForCollisions() const { return _ignoreForCollisions; }
void setIgnoreForCollisions(bool value) { _ignoreForCollisions = value; _ignoreForCollisionsChanged = true; }
bool getCollisionsWillMove() const { return _collisionsWillMove; }
void setCollisionsWillMove(bool value) { _collisionsWillMove = value; _collisionsWillMoveChanged = true; }
void setLastEdited(quint64 usecTime) { _lastEdited = usecTime; }
private:
@ -247,6 +255,8 @@ private:
glm::vec3 _angularVelocity;
float _angularDamping;
bool _visible;
bool _ignoreForCollisions;
bool _collisionsWillMove;
bool _positionChanged;
bool _dimensionsChanged;
@ -261,6 +271,8 @@ private:
bool _angularVelocityChanged;
bool _angularDampingChanged;
bool _visibleChanged;
bool _ignoreForCollisionsChanged;
bool _collisionsWillMoveChanged;
// TODO: this need to be more generic. for now, we're going to have the properties class support these as
// named getter/setters, but we want to move them to generic types...

View file

@ -555,8 +555,12 @@ bool EntityTreeElement::findShapeCollisions(const Shape* shape, CollisionList& c
QList<EntityItem*>::const_iterator entityEnd = _entityItems->end();
while(entityItr != entityEnd) {
EntityItem* entity = (*entityItr);
// entities that are set for ignore for collisions then don't consider them for collision
const Shape* otherCollisionShape = &entity->getCollisionShapeInMeters();
if (shape != otherCollisionShape) {
bool ignoreForCollisions = entity->getIgnoreForCollisions();
if (shape != otherCollisionShape && !ignoreForCollisions) {
if (ShapeCollider::collideShapes(shape, otherCollisionShape, collisions)) {
CollisionInfo* lastCollision = collisions.getLastCollision();
lastCollision->_extraData = entity;

View file

@ -80,6 +80,7 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl) {
// if this is a relative path then handle it as a relative viewpoint
handleRelativeViewpoint(lookupUrl.path());
emit lookupResultsFinished();
}
return false;
@ -149,6 +150,7 @@ void AddressManager::handleAPIResponse(const QJsonObject &jsonObject) {
// we've been told that this result exists but is offline, emit our signal so the application can handle
emit lookupResultIsOffline();
}
emit lookupResultsFinished();
}
void AddressManager::handleAPIError(QNetworkReply& errorReply) {
@ -157,6 +159,7 @@ void AddressManager::handleAPIError(QNetworkReply& errorReply) {
if (errorReply.error() == QNetworkReply::ContentNotFoundError) {
emit lookupResultIsNotFound();
}
emit lookupResultsFinished();
}
const QString GET_PLACE = "/api/v1/places/%1";
@ -164,9 +167,9 @@ const QString GET_PLACE = "/api/v1/places/%1";
void AddressManager::attemptPlaceNameLookup(const QString& lookupString) {
// assume this is a place name and see if we can get any info on it
QString placeName = QUrl::toPercentEncoding(lookupString);
AccountManager::getInstance().authenticatedRequest(GET_PLACE.arg(placeName),
QNetworkAccessManager::GetOperation,
apiCallbackParameters());
AccountManager::getInstance().unauthenticatedRequest(GET_PLACE.arg(placeName),
QNetworkAccessManager::GetOperation,
apiCallbackParameters());
}
bool AddressManager::handleNetworkAddress(const QString& lookupString) {
@ -180,6 +183,7 @@ bool AddressManager::handleNetworkAddress(const QString& lookupString) {
if (hostnameRegex.indexIn(lookupString) != -1) {
emit possibleDomainChangeRequired(hostnameRegex.cap(0));
emit lookupResultsFinished();
return true;
}
@ -187,6 +191,7 @@ bool AddressManager::handleNetworkAddress(const QString& lookupString) {
if (ipAddressRegex.indexIn(lookupString) != -1) {
emit possibleDomainChangeRequired(ipAddressRegex.cap(0));
emit lookupResultsFinished();
return true;
}
@ -263,7 +268,7 @@ bool AddressManager::handleUsername(const QString& lookupString) {
void AddressManager::goToUser(const QString& username) {
QString formattedUsername = QUrl::toPercentEncoding(username);
// this is a username - pull the captured name and lookup that user's location
AccountManager::getInstance().authenticatedRequest(GET_USER_LOCATION.arg(formattedUsername),
QNetworkAccessManager::GetOperation,
apiCallbackParameters());
AccountManager::getInstance().unauthenticatedRequest(GET_USER_LOCATION.arg(formattedUsername),
QNetworkAccessManager::GetOperation,
apiCallbackParameters());
}

View file

@ -37,6 +37,7 @@ public slots:
void handleAPIError(QNetworkReply& errorReply);
void goToUser(const QString& username);
signals:
void lookupResultsFinished();
void lookupResultIsOffline();
void lookupResultIsNotFound();
void possibleDomainChangeRequired(const QString& newHostname);

View file

@ -15,6 +15,7 @@
#include <QtCore/QJsonDocument>
#include <QtCore/QJsonArray>
#include <QtCore/QJsonObject>
#include <QtCore/QStandardPaths>
#include <QtCore/QVariant>
#include "HifiConfigVariantMap.h"
@ -75,30 +76,100 @@ QVariantMap HifiConfigVariantMap::mergeCLParametersWithJSONConfig(const QStringL
// we have a config file - try and read it
configFilePath = argumentList[configIndex + 1];
} else {
// no config file - try to read a file at resources/config.json
configFilePath = QCoreApplication::applicationDirPath() + "/resources/config.json";
// no config file - try to read a file config.json at the system config path
configFilePath = QString("%1/%2/%3/config.json").arg(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation),
QCoreApplication::organizationName(),
QCoreApplication::applicationName());
}
QFile configFile(configFilePath);
if (configFile.exists()) {
qDebug() << "Reading JSON config file at" << configFilePath;
configFile.open(QIODevice::ReadOnly);
QJsonDocument configDocument = QJsonDocument::fromJson(configFile.readAll());
QJsonObject rootObject = configDocument.object();
// enumerate the keys of the configDocument object
foreach(const QString& key, rootObject.keys()) {
if (!mergedMap.contains(key)) {
// no match in existing list, add it
mergedMap.insert(key, QVariant(rootObject[key]));
}
}
} else {
qDebug() << "Could not find JSON config file at" << configFilePath;
}
return mergedMap;
}
QVariantMap HifiConfigVariantMap::mergeMasterConfigWithUserConfig(const QStringList& argumentList) {
// check if there is a master config file
const QString MASTER_CONFIG_FILE_OPTION = "--master-config";
QVariantMap configVariantMap;
int masterConfigIndex = argumentList.indexOf(MASTER_CONFIG_FILE_OPTION);
if (masterConfigIndex != -1) {
QString masterConfigFilepath = argumentList[masterConfigIndex + 1];
mergeMapWithJSONFile(configVariantMap, masterConfigFilepath);
}
// merge the existing configVariantMap with the user config file
mergeMapWithJSONFile(configVariantMap, userConfigFilepath(argumentList));
return configVariantMap;
}
QString HifiConfigVariantMap::userConfigFilepath(const QStringList& argumentList) {
// we've loaded up the master config file, now fill in anything it didn't have with the user config file
const QString USER_CONFIG_FILE_OPTION = "--user-config";
int userConfigIndex = argumentList.indexOf(USER_CONFIG_FILE_OPTION);
QString userConfigFilepath;
if (userConfigIndex != -1) {
userConfigFilepath = argumentList[userConfigIndex + 1];
} else {
userConfigFilepath = QString("%1/%2/%3/config.json").arg(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation),
QCoreApplication::organizationName(),
QCoreApplication::applicationName());
}
return userConfigFilepath;
}
void HifiConfigVariantMap::mergeMapWithJSONFile(QVariantMap& existingMap, const QString& filename) {
QFile configFile(filename);
if (configFile.exists()) {
qDebug() << "Reading JSON config file at" << filename;
configFile.open(QIODevice::ReadOnly);
QJsonDocument configDocument = QJsonDocument::fromJson(configFile.readAll());
if (existingMap.isEmpty()) {
existingMap = configDocument.toVariant().toMap();
} else {
addMissingValuesToExistingMap(existingMap, configDocument.toVariant().toMap());
}
} else {
qDebug() << "Could not find JSON config file at" << filename;
}
}
void HifiConfigVariantMap::addMissingValuesToExistingMap(QVariantMap& existingMap, const QVariantMap& newMap) {
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());
}
} else {
existingMap[key] = newMap[key];
}
}
}
const 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];
} else if (variantMap[firstKey].canConvert(QMetaType::QVariantMap)) {
return valueForKeyPath(*static_cast<QVariantMap*>(variantMap[firstKey].data()), keyPath.mid(dotIndex + 1));
}
}
return NULL;
}

View file

@ -17,6 +17,13 @@
class HifiConfigVariantMap {
public:
static QVariantMap mergeCLParametersWithJSONConfig(const QStringList& argumentList);
static QVariantMap mergeMasterConfigWithUserConfig(const QStringList& argumentList);
static QString userConfigFilepath(const QStringList& argumentList);
private:
static void mergeMapWithJSONFile(QVariantMap& existingMap, const QString& filename);
static void addMissingValuesToExistingMap(QVariantMap& existingMap, const QVariantMap& newMap);
};
const QVariant* valueForKeyPath(QVariantMap& variantMap, const QString& keyPath);
#endif // hifi_HifiConfigVariantMap_h